diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..c1e1e45 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,40 @@ +name: CI + +on: + push: + branches: ['*'] + pull_request: + branches: [master] + +jobs: + lint-format-test: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + + - name: Install Poetry + run: curl -sSL https://install.python-poetry.org | python3 - + + - name: Configure Poetry Path + run: echo "$HOME/.local/bin" >> $GITHUB_PATH + + - name: Install Dependencies + run: poetry install --with dev + + - name: Check Code Format (black) + run: poetry run black --check tfworker tests + + - name: Check Imports Sorted (isort) + run: poetry run isort --check-only tfworker tests + + - name: Run Lint (flake8) + run: poetry run flake8 --ignore E501,W503 tfworker tests + + - name: Run Tests + run: poetry run pytest -p no:warnings --disable-socket diff --git a/.isort.cfg b/.isort.cfg index bbba6d5..11ea026 100644 --- a/.isort.cfg +++ b/.isort.cfg @@ -1,4 +1,4 @@ [settings] -known_third_party = atlassian,boto3,botocore,click,google,hcl2,jinja2,lark,moto,packaging,pydantic,pydantic_core,pydantic_settings,pytest,tfworker,yaml +known_third_party = atlassian,boto3,botocore,click,google,hcl2,jinja2,lark,moto,packaging,pydantic,pydantic_core,pydantic_settings,pytest,yaml profile = black skip = ["*/__init__.py"] diff --git a/Makefile b/Makefile index 12ddd81..a171eb2 100644 --- a/Makefile +++ b/Makefile @@ -31,3 +31,19 @@ clean: @rm -rf build dist .eggs terraform_worker.egg-info @find . -name *.pyc -exec rm {} \; @find . -name __pycache__ -type d -exec rmdir {} \; + +triage-export: + @echo "πŸ“₯ Exporting open, untriaged issues for AI review..." + gh issue list --repo ephur/terraform-worker --state open --limit 1000 --json number,title,body,labels | \ + jq '[.[] | select(.labels | all(.name != "triaged"))]' > open_issues.json + @echo "βœ… Issues written to open_issues.json" + +triage-preview: + @echo "πŸ” Previewing untriaged issue titles..." + @if [ ! -f open_issues.json ]; then \ + $(MAKE) triage-export; \ + fi + jq -r '.[] | "\(.number): \(.title)"' open_issues.json + +ready: lint format test + @echo "βœ… All checks passed. You are ready to commit or push." diff --git a/README.md b/README.md index c0ceb35..85a7ca5 100644 --- a/README.md +++ b/README.md @@ -1,224 +1,258 @@ # terraform-worker -`terraform-worker` is a command line tool for pipelining terraform operations while sharing state between them. The worker consumese a yaml configuration file which is broken up into two sections, definitions (which were really just top level modules) and sub-modules. The definitions are put into a worker config in order, with the terraform variables, and remote state variables. Following is a sample configuration file and command: +[![License: Apache-2.0](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](./LICENSE) +![CI](https://github.com/ephur/terraform-worker/actions/workflows/ci.yml/badge.svg) +![Coverage](https://img.shields.io/badge/coverage-60%25-yellow) +![Python](https://img.shields.io/badge/python-3.11-blue) +![License: Apache 2.0](https://img.shields.io/badge/License-Apache_2.0-blue.svg) +![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg) +![Linted: flake8](https://img.shields.io/badge/linted-flake8-green) +![Built with Poetry](https://img.shields.io/badge/built%20with-poetry-blue) + +**terraform-worker** is a command-line tool purpose-built for orchestrating large-scale, modular Terraform deployments. It enables precise control over provider versions, remote state scaffolding, and shared configuration logic β€” ideal for enterprise infrastructure teams managing complex environments across multiple workspaces and deployment stages. + +Unlike tools such as `terragrunt`, this is not a drop-in wrapper β€” `terraform-worker` introduces a new structure and workflow model, optimized for maintainability, scalability, and declarative control, and integration with CI/CD pipelines. + +--- + +## πŸ“– Table of Contents + +- [πŸš€ Features](#-features) +- [πŸ”„ Example Workflow](#-example-workflow) +- [πŸ“Œ Configuration Notes](#-configuration-notes) +- [🧠 How It Works](#-how-it-works) +- [πŸ§ͺ Development & Testing](#-development--testing) +- [πŸ“€ Releasing](#-releasing) +- [πŸ›  Troubleshooting](#-troubleshooting) +- [πŸͺ Hook Scripts](#-hook-scripts) +- [πŸ”’ Assuming a Role (AWS)](#-assuming-a-role-aws) +- [πŸ“š Documentation](#-documentation) +- [🧭 Design Philosophy](#-design-philosophy) +- [πŸ”— License](#-license) + +## πŸš€ Features + +- πŸ”§ Modularized state management across nested or sequential Terraform operations +- 🧱 Automated scaffolding of provider blocks, remote state configs, and input variables +- ✨ Jinja2 templating for flexible, environment-specific rendering +- πŸ”’ Enforces version consistency of modules and providers +- 🧰 Supports YAML, JSON, and HCL2 for configuration definitions +- πŸ“¦ Clean CLI with pluggable backend and runtime overrides +- πŸ” Designed from the ground up for enterprise environments + +--- + +## πŸ”„ Example Workflow + +Here's a minimal example using `worker.yaml` to scaffold a VPC and RDS database with shared state: -*./worker.yaml* ```yaml terraform: providers: aws: vars: region: {{ aws_region }} - version: "~> 2.61.0" + version: "~> 4.0" - # global level variables terraform_vars: region: {{ aws_region }} environment: dev definitions: - # Either setup a VPC and resources, or deploy into an existing one network: path: /definitions/aws/network-existing database: path: /definitions/aws/rds + remote_vars: + subnet: network.outputs.subnet_id ``` -```sh -% worker --aws-profile default --backend s3 terraform example1 -``` -**NOTE:** When adding a provider from a non-hashicorp source, use a `source` field, as follows -(_the `source` field is only valid for terraform 13+ and is not emitted when using 12_): +Run the deployment: -```yaml -providers: -... - kubectl: - vars: - version: "~> 1.9" - source: "gavinbunney/kubectl" +```sh +% worker --aws-profile default --config-var aws_region=us-west-2 terraform deploy ``` -In addition to using command line options, worker configuration can be specified using a `worker_options` section in -the worker configuration. +Use custom worker options for flexibility: ```yaml terraform: worker_options: backend: s3 backend_prefix: tfstate - terraform_bin: /home/user/bin/terraform - - providers: -... + terraform_bin: /usr/local/bin/terraform ``` -**terraform-worker** requires a configuration file. By default, it will looks for a file named "worker.yaml" in the -current working directory. Together with the `worker_options` listed above, it's possible to specify all options -either in the environment or in the configuration file and simply call the worker command by itself. +--- -```sh - % env | grep AWS - AWS_ACCESS_KEY_ID=somekey - AWS_SECRET_ACCESS_KEY=somesecret - % head ./worker.yaml -terraform: - worker_options: - backend: s3 - backend_prefix: tfstate - terraform_bin: /home/user/bin/terraform - % worker terraform my-deploy +## πŸ“Œ Configuration Notes + +- Config files may be written in YAML, JSON, or HCL2 +- Configs support templating via [Jinja2](https://jinja.palletsprojects.com/) +- `terraform-worker` reads `worker.yaml` by default from the current directory +- Variables like `{{ aws_region }}` are injected via `--config-var` CLI args + +--- + +## 🧠 How It Works + +1. Parses your configuration into **definitions** (state modules) +2. Injects provider/version/variable blocks automatically +3. Handles remote state linking across module outputs +4. Creates ephemeral execution directories (unless `--no-clean` is used) +5. Delegates to native Terraform for execution (`init`, `plan`, `apply`, etc.) + +--- + +## πŸ§ͺ Development & Testing + +Initialize the environment: + +```bash +poetry install +make init ``` -## Assuming a Role +Run tests: -The first step in assuming a role is to create the role to be assumed as documented in [Creating a role to delegate permissions to an IAM user ](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_create_for-user.html) and then granting permissions to assume the role as documented in [Granting a user permissions to switch roles ](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_use_permissions-to-switch.html). +```bash +pytest +``` -To have the worker assume a role once the role and permissions are configured the `--aws-role-arn` and `--aws-external-id` flags need to be provided to the worker along with the credentials for the trusted account. Since neither the role ARN nor the ExternalId are secret, this allows running under another set of credentials without providing any additional secrets. +--- -## Development +## πŸ“€ Releasing -```sh - # virtualenv setup stuff... and then: - % pip install poetry && make init +Update the version and publish to PyPI: + +```bash +poetry version +poetry publish --build ``` -## Releasing +Configure credentials for Poetry using [these instructions](https://python-poetry.org/docs/repositories/#configuring-credentials). -Publishing a release to PYPI is done locally through poetry. Instructions on how to configure credentials for poetry can be found [here](https://python-poetry.org/docs/repositories/#configuring-credentials). +--- + +## πŸ›  Troubleshooting + +Use `--no-clean` to retain generated terraform files: -Bump the version of the worker and commit the change: ```sh - % poetry version +worker --no-clean terraform deploy ``` -Build and publish the package to PYPI: -```sh - % poetry publish --build +The tool will print the temporary directory it used, e.g.: + +```text +using temporary Directory: /tmp/tmpew44uopp ``` -## Configuration +You can `cd` into the generated definition to run manual Terraform commands for debugging. -A project is configured through a worker config, a yaml, json, or hcl2 file that specifies the definitions, inputs, outputs, providers and all other necessary configuration. The worker config is what specifies how state is shared among your definitions. The config support jinja templating that can be used to conditionally pass state or pass in env variables through the command line via the `--config-var` option. +--- -*./worker.yaml* -```yaml -terraform: - providers: - aws: - vars: - region: {{ aws_region }} - version: "~> 2.61.1" +## πŸͺ Hook Scripts - # global level variables - terraform_vars: - region: {{ aws_region }} - environment: dev +Each Terraform action (`init`, `plan`, `apply`) can be extended via optional lifecycle hook scripts, enabling advanced custom logic around deployments. - definitions: - # Either setup a VPC and resources, or deploy into an existing one - network: - path: /definitions/aws/network-existing +### Supported Hook Types - database: - path: /definitions/aws/rds - remote_vars: - subnet: network.outputs.subnet_id -``` +For every definition, the following scripts are recognized and executed if present: + +- `pre_init`, `post_init` +- `pre_plan`, `post_plan` +- `pre_apply`, `post_apply` + +Scripts must be placed within the corresponding definition directory. + +### Language & Execution + +Hook scripts can be written in **any language** β€” they are executed as standalone shell commands. Ensure the script has execution permissions (e.g., `chmod +x`). -```json -{ - "terraform": { - "providers": { - "aws": { - "vars": { - "region": "{{ aws_region }}", - "version": "~> 2.61" - } - } - }, - "terraform_vars": { - "region": "{{ aws_region }}", - "environment": "dev" - }, - "definitions": { - "network": { - "path": "/definitions/aws/network-existing" - }, - "database": { - "path": "/definitions/aws/rds", - "remote_vars": { - "subnet": "network.outputs.subnet_id" - } - } - } - } -} +Example: +```bash +#!/usr/bin/env python3 +import os +print("Pre-apply hook running for", os.environ.get("TF_WORKER_DEFINITION")) ``` -```hcl -terraform { - providers { - aws = { - vars = { - region = "{{ aws_region }}" - version = "2.63.0" - } - } - } - - terraform_vars { - environment = "dev" - region = "{{ aws_region }}" - } - - definitions { - network = { - path = "/definitions/aws/network-existing" - } - - database = { - path = "/definitions/aws/rds" - - remote_vars = { - subnet = "network.outputs.subnet_id" - } - } - } -} +### Environment Variable Access + +The following environment variables are automatically injected into all hook scripts: + +- `TF_`-prefixed variables for: + - Terraform input variables (`TF_REGION`, `TF_ENVIRONMENT`, etc.) + - Rendered template variables (e.g., Jinja context) + - Remote state outputs +- Authentication credentials (e.g., `AWS_ACCESS_KEY_ID`, `GOOGLE_APPLICATION_CREDENTIALS`, etc.) + +This allows hook scripts to dynamically read and interact with Terraform configuration and authentication context. + +### Use Cases + +- Pre-checks or assertions before deploys +- Dynamic secrets injection +- Notifications or logging +- External system integration (e.g., Slack, monitoring) + +Hook scripts provide a powerful extension point for customizing behavior around each Terraform stage without modifying core logic. + +--- + +## πŸ”’ Assuming a Role (AWS) + +To execute with a specific IAM role: + +```sh +worker --aws-role-arn arn:aws:iam::1234567890:role/example \ + --aws-external-id my-id \ + terraform deploy ``` -In this config, the worker manages two separate terraform modules, a `network` and a `database` definition, and shares an output from the network definition with the database definition. This is made available inside of the `database` definition through the `local.subnet` value. +Configure the role and trust relationship per [AWS IAM guidelines](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_create_for-user.html). -`aws_region` is substituted at runtime for the value of `--aws-region` passed through the command line. +--- -## Troubleshooting +## πŸ“š Documentation -Running the worker with the `--no-clean` option will keep around the terraform files that the worker generates. You can use these generated files to directly run terraform commands for that definition. This is useful for when you need to do things like troubleshoot or delete items from the remote state. After running the worker with --no-clean, cd into the definition directory where the terraform-worker generates it's tf files. The worker should tell you where it's putting these for example: +Full documentation is built using [Sphinx](https://www.sphinx-doc.org/en/master/): +```bash +cd docs +make html ``` -... -building deployment mfaitest -using temporary Directory: /tmp/tmpew44uopp -... + +Open the local docs via: + +```sh +open ./docs/build/index.html ``` -In order to troubleshoot this definition, you would cd /tmp/tmpew44uopp/definitions/my_definition/ and then perform any terraform commands from there. +--- -## Background +## 🧭 Design Philosophy -The terraform worker was a weekend project to run terraform against a series of definitions (modules). The idea was the configuration vars, provider configuration, remote state, and variables from remote state would all be dynamically generated. The purpose was for building kubernetes deployments, and allowing all of the configuration information to be stored as either yamnl files in github, or have the worker configuration generated by an API which stored all of the deployment configurations in a database. +**terraform-worker** was created from the ground up for production-first use in enterprise CI/CD systems, including: +- Large deployments with complex dependency graphs +- Strict version controls and reproducibility +- Integration with GitHub or API-driven configuration management -## Documentation +While it excels at scale, it’s also a powerful CLI for individuals who need structure without sacrificing flexibility. -Documentation uses the [Sphinx](https://www.sphinx-doc.org/en/master/index.html) documentation fromework. +--- -To build HTML documentation: +## πŸ” Legal Summary β€” Apache License 2.0 +Apache License 2.0 β€” see [LICENSE](./LICENSE) for details. -```bash -% cd docs -% make clean && make html -``` +This project is licensed under the Apache License 2.0, which means: + +- βœ… You can use the code freely, including in commercial applications +- βœ… You can modify it, fork it, and redistribute it +- βœ… You **do not** have to open source your own modifications +- βœ… It includes a patent grant to protect you from contributors asserting patent claims +- ❗ You **must** include a copy of the license and provide proper attribution +- ❗ You **must** note significant changes if you modify the code -The documentation can be viewed locally by open `./docs/build/index.html` in a browser. +This license is designed to encourage broad use while protecting both users and contributors. diff --git a/poetry.lock b/poetry.lock index a1d5299..97fcca8 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.1.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. [[package]] name = "alabaster" @@ -6,7 +6,6 @@ version = "0.7.16" description = "A light, configurable Sphinx theme" optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "alabaster-0.7.16-py3-none-any.whl", hash = "sha256:b46733c07dce03ae4e150330b975c75737fa60f0a7c591b6c8bf4928a28e2c92"}, {file = "alabaster-0.7.16.tar.gz", hash = "sha256:75a8b99c28a5dad50dd7f8ccdd447a121ddb3892da9e53d1ca5cca3106d58d65"}, @@ -18,7 +17,6 @@ version = "0.7.0" description = "Reusable constraint types to use with typing.Annotated" optional = false python-versions = ">=3.8" -groups = ["main"] files = [ {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, @@ -30,7 +28,6 @@ version = "3.0.2" description = "Utilities for refactoring imports in python-like syntax." optional = false python-versions = ">=3.7" -groups = ["dev"] files = [ {file = "aspy.refactor_imports-3.0.2-py2.py3-none-any.whl", hash = "sha256:f306037682479945df61b2e6d01bf97256d68f3e704742768deef549e0d61fbb"}, {file = "aspy.refactor_imports-3.0.2.tar.gz", hash = "sha256:3c7329cdb2613c46fcd757c8e45120efbc3d4b9db805092911eb605c19c5795c"}, @@ -42,7 +39,6 @@ version = "3.0.0" description = "Annotate AST trees with source code positions" optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "asttokens-3.0.0-py3-none-any.whl", hash = "sha256:e3078351a059199dd5138cb1c706e6430c05eff2ff136af5eb4790f9d28932e2"}, {file = "asttokens-3.0.0.tar.gz", hash = "sha256:0dcd8baa8d62b0c1d118b399b2ddba3c4aff271d0d7a9e0d4c1681c79035bbc7"}, @@ -58,7 +54,6 @@ version = "3.41.21" description = "Python Atlassian REST API Wrapper" optional = false python-versions = "*" -groups = ["main"] files = [ {file = "atlassian_python_api-3.41.21-py3-none-any.whl", hash = "sha256:6397bfa08d19acf08a766d3db5dd782348e8a3efca49cbf4ada870290a7fcf07"}, {file = "atlassian_python_api-3.41.21.tar.gz", hash = "sha256:30117504ebdcbe229c6a15f7a6b89076e7f20e5feeb425fbce2b4b0b18b0e639"}, @@ -82,14 +77,13 @@ version = "2.17.0" description = "Internationalization utilities" optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2"}, {file = "babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d"}, ] [package.extras] -dev = ["backports.zoneinfo ; python_version < \"3.9\"", "freezegun (>=1.0,<2.0)", "jinja2 (>=3.0)", "pytest (>=6.0)", "pytest-cov", "pytz", "setuptools", "tzdata ; sys_platform == \"win32\""] +dev = ["backports.zoneinfo", "freezegun (>=1.0,<2.0)", "jinja2 (>=3.0)", "pytest (>=6.0)", "pytest-cov", "pytz", "setuptools", "tzdata"] [[package]] name = "beautifulsoup4" @@ -97,7 +91,6 @@ version = "4.13.4" description = "Screen-scraping library" optional = false python-versions = ">=3.7.0" -groups = ["main"] files = [ {file = "beautifulsoup4-4.13.4-py3-none-any.whl", hash = "sha256:9bbbb14bfde9d79f38b8cd5f8c7c85f4b8f2523190ebed90e950a8dea4cb1c4b"}, {file = "beautifulsoup4-4.13.4.tar.gz", hash = "sha256:dbb3c4e1ceae6aefebdaf2423247260cd062430a410e38c66f2baa50a8437195"}, @@ -120,7 +113,6 @@ version = "24.10.0" description = "The uncompromising code formatter." optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "black-24.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e6668650ea4b685440857138e5fe40cde4d652633b1bdffc62933d0db4ed9812"}, {file = "black-24.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1c536fcf674217e87b8cc3657b81809d3c085d7bf3ef262ead700da345bfa6ea"}, @@ -165,7 +157,6 @@ version = "1.38.23" description = "The AWS SDK for Python" optional = false python-versions = ">=3.9" -groups = ["main", "dev"] files = [ {file = "boto3-1.38.23-py3-none-any.whl", hash = "sha256:70ab8364f1f6f0a7e0eaf97f62fbdacf9c1e4cc1de330faf1c146ef9ab01e7d0"}, {file = "boto3-1.38.23.tar.gz", hash = "sha256:bcf73aca469add09e165b8793be18e7578db8d2604d82505ab13dc2495bad982"}, @@ -185,7 +176,6 @@ version = "1.38.23" description = "Low-level, data-driven core of boto 3." optional = false python-versions = ">=3.9" -groups = ["main", "dev"] files = [ {file = "botocore-1.38.23-py3-none-any.whl", hash = "sha256:a7f818672f10d7a080c2c4558428011c3e0abc1039a047d27ac76ec846158457"}, {file = "botocore-1.38.23.tar.gz", hash = "sha256:29685c91050a870c3809238dc5da1ac65a48a3a20b4bca46b6057dcb6b39c72a"}, @@ -205,7 +195,6 @@ version = "5.5.2" description = "Extensible memoizing collections and decorators" optional = false python-versions = ">=3.7" -groups = ["main"] files = [ {file = "cachetools-5.5.2-py3-none-any.whl", hash = "sha256:d26a22bcc62eb95c3beabd9f1ee5e820d3d2704fe2967cbe350e20c8ffcd3f0a"}, {file = "cachetools-5.5.2.tar.gz", hash = "sha256:1a661caa9175d26759571b2e19580f9d6393969e5dfca11fdb1f947a23e640d4"}, @@ -217,7 +206,6 @@ version = "2025.4.26" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" -groups = ["main", "dev"] files = [ {file = "certifi-2025.4.26-py3-none-any.whl", hash = "sha256:30350364dfe371162649852c63336a15c70c6510c2ad5015b21c2345311805f3"}, {file = "certifi-2025.4.26.tar.gz", hash = "sha256:0a816057ea3cdefcef70270d2c515e4506bbc954f417fa5ade2021213bb8f0c6"}, @@ -229,8 +217,6 @@ version = "1.17.1" description = "Foreign Function Interface for Python calling C code." optional = false python-versions = ">=3.8" -groups = ["dev"] -markers = "platform_python_implementation != \"PyPy\"" files = [ {file = "cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14"}, {file = "cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67"}, @@ -310,7 +296,6 @@ version = "3.4.2" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7" -groups = ["main", "dev"] files = [ {file = "charset_normalizer-3.4.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7c48ed483eb946e6c04ccbe02c6b4d1d48e51944b6db70f697e089c193404941"}, {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2d318c11350e10662026ad0eb71bb51c7812fc8590825304ae0bdd4ac283acd"}, @@ -412,7 +397,6 @@ version = "8.2.1" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.10" -groups = ["main", "dev"] files = [ {file = "click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b"}, {file = "click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202"}, @@ -427,12 +411,10 @@ version = "0.4.6" description = "Cross-platform colored terminal text." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -groups = ["main", "dev"] files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] -markers = {main = "platform_system == \"Windows\""} [[package]] name = "coverage" @@ -440,7 +422,6 @@ version = "7.8.2" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "coverage-7.8.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bd8ec21e1443fd7a447881332f7ce9d35b8fbd2849e761bb290b584535636b0a"}, {file = "coverage-7.8.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4c26c2396674816deaeae7ded0e2b42c26537280f8fe313335858ffff35019be"}, @@ -512,7 +493,7 @@ files = [ ] [package.extras] -toml = ["tomli ; python_full_version <= \"3.11.0a6\""] +toml = ["tomli"] [[package]] name = "cryptography" @@ -520,7 +501,6 @@ version = "45.0.3" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = false python-versions = "!=3.9.0,!=3.9.1,>=3.7" -groups = ["dev"] files = [ {file = "cryptography-45.0.3-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:7573d9eebaeceeb55285205dbbb8753ac1e962af3d9640791d12b36864065e71"}, {file = "cryptography-45.0.3-cp311-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d377dde61c5d67eb4311eace661c3efda46c62113ff56bf05e2d679e02aebb5b"}, @@ -565,10 +545,10 @@ files = [ cffi = {version = ">=1.14", markers = "platform_python_implementation != \"PyPy\""} [package.extras] -docs = ["sphinx (>=5.3.0)", "sphinx-inline-tabs ; python_full_version >= \"3.8.0\"", "sphinx-rtd-theme (>=3.0.0) ; python_full_version >= \"3.8.0\""] +docs = ["sphinx (>=5.3.0)", "sphinx-inline-tabs", "sphinx-rtd-theme (>=3.0.0)"] docstest = ["pyenchant (>=3)", "readme-renderer (>=30.0)", "sphinxcontrib-spelling (>=7.3.1)"] -nox = ["nox (>=2024.4.15)", "nox[uv] (>=2024.3.2) ; python_full_version >= \"3.8.0\""] -pep8test = ["check-sdist ; python_full_version >= \"3.8.0\"", "click (>=8.0.1)", "mypy (>=1.4)", "ruff (>=0.3.6)"] +nox = ["nox (>=2024.4.15)", "nox[uv] (>=2024.3.2)"] +pep8test = ["check-sdist", "click (>=8.0.1)", "mypy (>=1.4)", "ruff (>=0.3.6)"] sdist = ["build (>=1.0.0)"] ssh = ["bcrypt (>=3.1.5)"] test = ["certifi (>=2024)", "cryptography-vectors (==45.0.3)", "pretend (>=0.7)", "pytest (>=7.4.0)", "pytest-benchmark (>=4.0)", "pytest-cov (>=2.10.1)", "pytest-xdist (>=3.5.0)"] @@ -580,7 +560,6 @@ version = "5.2.1" description = "Decorators for Humans" optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "decorator-5.2.1-py3-none-any.whl", hash = "sha256:d316bb415a2d9e2d2b3abcc4084c6502fc09240e292cd76a76afc106a1c8e04a"}, {file = "decorator-5.2.1.tar.gz", hash = "sha256:65f266143752f734b0a7cc83c46f4618af75b8c5911b00ccb61d0ac9b6da0360"}, @@ -592,7 +571,6 @@ version = "7.0.1" description = "Deep Difference and Search of any Python object/data. Recreate objects by adding adding deltas to each other." optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "deepdiff-7.0.1-py3-none-any.whl", hash = "sha256:447760081918216aa4fd4ca78a4b6a848b81307b2ea94c810255334b759e1dc3"}, {file = "deepdiff-7.0.1.tar.gz", hash = "sha256:260c16f052d4badbf60351b4f77e8390bee03a0b516246f6839bc813fb429ddf"}, @@ -611,7 +589,6 @@ version = "1.2.18" description = "Python @deprecated decorator to deprecate old python classes, functions or methods." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" -groups = ["main"] files = [ {file = "Deprecated-1.2.18-py2.py3-none-any.whl", hash = "sha256:bd5011788200372a32418f888e326a09ff80d0214bd961147cfed01b5c018eec"}, {file = "deprecated-1.2.18.tar.gz", hash = "sha256:422b6f6d859da6f2ef57857761bfb392480502a64c3028ca9bbe86085d72115d"}, @@ -621,7 +598,7 @@ files = [ wrapt = ">=1.10,<2" [package.extras] -dev = ["PyTest", "PyTest-Cov", "bump2version (<1)", "setuptools ; python_version >= \"3.12\"", "tox"] +dev = ["PyTest", "PyTest-Cov", "bump2version (<1)", "setuptools", "tox"] [[package]] name = "docker" @@ -629,7 +606,6 @@ version = "7.1.0" description = "A Python library for the Docker Engine API." optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "docker-7.1.0-py3-none-any.whl", hash = "sha256:c96b93b7f0a746f9e77d325bcfb87422a3d8bd4f03136ae8a85b37f1898d5fc0"}, {file = "docker-7.1.0.tar.gz", hash = "sha256:ad8c70e6e3f8926cb8a92619b832b4ea5299e2831c14284663184e200546fa6c"}, @@ -652,7 +628,6 @@ version = "0.21.2" description = "Docutils -- Python Documentation Utilities" optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "docutils-0.21.2-py3-none-any.whl", hash = "sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2"}, {file = "docutils-0.21.2.tar.gz", hash = "sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f"}, @@ -664,14 +639,13 @@ version = "2.2.0" description = "Get the currently executing AST node of a frame, and other information" optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "executing-2.2.0-py2.py3-none-any.whl", hash = "sha256:11387150cad388d62750327a53d3339fad4888b39a6fe233c3afbb54ecffd3aa"}, {file = "executing-2.2.0.tar.gz", hash = "sha256:5d108c028108fe2551d1a7b2e8b713341e2cb4fc0aa7dcf966fa4327a5226755"}, ] [package.extras] -tests = ["asttokens (>=2.1.0)", "coverage", "coverage-enable-subprocess", "ipython", "littleutils", "pytest", "rich ; python_version >= \"3.11\""] +tests = ["asttokens (>=2.1.0)", "coverage", "coverage-enable-subprocess", "ipython", "littleutils", "pytest", "rich"] [[package]] name = "flake8" @@ -679,7 +653,6 @@ version = "7.2.0" description = "the modular source code checker: pep8 pyflakes and co" optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "flake8-7.2.0-py2.py3-none-any.whl", hash = "sha256:93b92ba5bdb60754a6da14fa3b93a9361fd00a59632ada61fd7b130436c40343"}, {file = "flake8-7.2.0.tar.gz", hash = "sha256:fa558ae3f6f7dbf2b4f22663e5343b6b6023620461f8d4ff2019ef4b5ee70426"}, @@ -696,7 +669,6 @@ version = "1.2.0" description = "A backport of fstrings to python<3.6" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -groups = ["dev"] files = [ {file = "future_fstrings-1.2.0-py2.py3-none-any.whl", hash = "sha256:90e49598b553d8746c4dc7d9442e0359d038c3039d802c91c0a55505da318c63"}, {file = "future_fstrings-1.2.0.tar.gz", hash = "sha256:6cf41cbe97c398ab5a81168ce0dbb8ad95862d3caf23c21e4430627b90844089"}, @@ -711,7 +683,6 @@ version = "2.24.2" description = "Google API client core library" optional = false python-versions = ">=3.7" -groups = ["main"] files = [ {file = "google_api_core-2.24.2-py3-none-any.whl", hash = "sha256:810a63ac95f3c441b7c0e43d344e372887f62ce9071ba972eacf32672e072de9"}, {file = "google_api_core-2.24.2.tar.gz", hash = "sha256:81718493daf06d96d6bc76a91c23874dbf2fac0adbbf542831b805ee6e974696"}, @@ -729,7 +700,7 @@ requests = ">=2.18.0,<3.0.0" [package.extras] async-rest = ["google-auth[aiohttp] (>=2.35.0,<3.0.dev0)"] -grpc = ["grpcio (>=1.33.2,<2.0dev)", "grpcio (>=1.49.1,<2.0dev) ; python_version >= \"3.11\"", "grpcio-status (>=1.33.2,<2.0.dev0)", "grpcio-status (>=1.49.1,<2.0.dev0) ; python_version >= \"3.11\""] +grpc = ["grpcio (>=1.33.2,<2.0dev)", "grpcio (>=1.49.1,<2.0dev)", "grpcio-status (>=1.33.2,<2.0.dev0)", "grpcio-status (>=1.49.1,<2.0.dev0)"] grpcgcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"] grpcio-gcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"] @@ -739,7 +710,6 @@ version = "2.40.2" description = "Google Authentication Library" optional = false python-versions = ">=3.7" -groups = ["main"] files = [ {file = "google_auth-2.40.2-py2.py3-none-any.whl", hash = "sha256:f7e568d42eedfded58734f6a60c58321896a621f7c116c411550a4b4a13da90b"}, {file = "google_auth-2.40.2.tar.gz", hash = "sha256:a33cde547a2134273226fa4b853883559947ebe9207521f7afc707efbf690f58"}, @@ -753,11 +723,11 @@ rsa = ">=3.1.4,<5" [package.extras] aiohttp = ["aiohttp (>=3.6.2,<4.0.0)", "requests (>=2.20.0,<3.0.0)"] enterprise-cert = ["cryptography", "pyopenssl"] -pyjwt = ["cryptography (<39.0.0) ; python_version < \"3.8\"", "cryptography (>=38.0.3)", "pyjwt (>=2.0)"] -pyopenssl = ["cryptography (<39.0.0) ; python_version < \"3.8\"", "cryptography (>=38.0.3)", "pyopenssl (>=20.0.0)"] +pyjwt = ["cryptography (<39.0.0)", "cryptography (>=38.0.3)", "pyjwt (>=2.0)"] +pyopenssl = ["cryptography (<39.0.0)", "cryptography (>=38.0.3)", "pyopenssl (>=20.0.0)"] reauth = ["pyu2f (>=0.1.5)"] requests = ["requests (>=2.20.0,<3.0.0)"] -testing = ["aiohttp (<3.10.0)", "aiohttp (>=3.6.2,<4.0.0)", "aioresponses", "cryptography (<39.0.0) ; python_version < \"3.8\"", "cryptography (>=38.0.3)", "flask", "freezegun", "grpcio", "mock", "oauth2client", "packaging", "pyjwt (>=2.0)", "pyopenssl (<24.3.0)", "pyopenssl (>=20.0.0)", "pytest", "pytest-asyncio", "pytest-cov", "pytest-localserver", "pyu2f (>=0.1.5)", "requests (>=2.20.0,<3.0.0)", "responses", "urllib3"] +testing = ["aiohttp (<3.10.0)", "aiohttp (>=3.6.2,<4.0.0)", "aioresponses", "cryptography (<39.0.0)", "cryptography (>=38.0.3)", "flask", "freezegun", "grpcio", "mock", "oauth2client", "packaging", "pyjwt (>=2.0)", "pyopenssl (<24.3.0)", "pyopenssl (>=20.0.0)", "pytest", "pytest-asyncio", "pytest-cov", "pytest-localserver", "pyu2f (>=0.1.5)", "requests (>=2.20.0,<3.0.0)", "responses", "urllib3"] urllib3 = ["packaging", "urllib3"] [[package]] @@ -766,7 +736,6 @@ version = "2.4.3" description = "Google Cloud API client core library" optional = false python-versions = ">=3.7" -groups = ["main"] files = [ {file = "google_cloud_core-2.4.3-py2.py3-none-any.whl", hash = "sha256:5130f9f4c14b4fafdff75c79448f9495cfade0d8775facf1b09c3bf67e027f6e"}, {file = "google_cloud_core-2.4.3.tar.gz", hash = "sha256:1fab62d7102844b278fe6dead3af32408b1df3eb06f5c7e8634cbd40edc4da53"}, @@ -785,7 +754,6 @@ version = "2.19.0" description = "Google Cloud Storage API client library" optional = false python-versions = ">=3.7" -groups = ["main"] files = [ {file = "google_cloud_storage-2.19.0-py2.py3-none-any.whl", hash = "sha256:aeb971b5c29cf8ab98445082cbfe7b161a1f48ed275822f59ed3f1524ea54fba"}, {file = "google_cloud_storage-2.19.0.tar.gz", hash = "sha256:cd05e9e7191ba6cb68934d8eb76054d9be4562aa89dbc4236feee4d7d51342b2"}, @@ -809,7 +777,6 @@ version = "1.7.1" description = "A python wrapper of the C library 'Google CRC32C'" optional = false python-versions = ">=3.9" -groups = ["main"] files = [ {file = "google_crc32c-1.7.1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:b07d48faf8292b4db7c3d64ab86f950c2e94e93a11fd47271c28ba458e4a0d76"}, {file = "google_crc32c-1.7.1-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:7cc81b3a2fbd932a4313eb53cc7d9dde424088ca3a0337160f35d91826880c1d"}, @@ -856,7 +823,6 @@ version = "2.7.2" description = "Utilities for Google Media Downloads and Resumable Uploads" optional = false python-versions = ">=3.7" -groups = ["main"] files = [ {file = "google_resumable_media-2.7.2-py2.py3-none-any.whl", hash = "sha256:3ce7551e9fe6d99e9a126101d2536612bb73486721951e9562fee0f90c6ababa"}, {file = "google_resumable_media-2.7.2.tar.gz", hash = "sha256:5280aed4629f2b60b847b0d42f9857fd4935c11af266744df33d8074cae92fe0"}, @@ -875,7 +841,6 @@ version = "1.70.0" description = "Common protobufs used in Google APIs" optional = false python-versions = ">=3.7" -groups = ["main"] files = [ {file = "googleapis_common_protos-1.70.0-py3-none-any.whl", hash = "sha256:b8bfcca8c25a2bb253e0e0b0adaf8c00773e5e6af6fd92397576680b807e0fd8"}, {file = "googleapis_common_protos-1.70.0.tar.gz", hash = "sha256:0e1b44e0ea153e6594f9f394fef15193a68aaaea2d843f83e2742717ca753257"}, @@ -893,7 +858,6 @@ version = "3.10" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.6" -groups = ["main", "dev"] files = [ {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, @@ -908,7 +872,6 @@ version = "1.4.1" description = "Getting image size from png/jpeg/jpeg2000/gif file" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -groups = ["dev"] files = [ {file = "imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b"}, {file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"}, @@ -920,7 +883,6 @@ version = "2.1.0" description = "brain-dead simple config-ini parsing" optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760"}, {file = "iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7"}, @@ -932,7 +894,6 @@ version = "8.36.0" description = "IPython: Productive Interactive Computing" optional = false python-versions = ">=3.10" -groups = ["dev"] files = [ {file = "ipython-8.36.0-py3-none-any.whl", hash = "sha256:12b913914d010dcffa2711505ec8be4bf0180742d97f1e5175e51f22086428c1"}, {file = "ipython-8.36.0.tar.gz", hash = "sha256:24658e9fe5c5c819455043235ba59cfffded4a35936eefceceab6b192f7092ff"}, @@ -953,7 +914,7 @@ typing_extensions = {version = ">=4.6", markers = "python_version < \"3.12\""} [package.extras] all = ["ipython[black,doc,kernel,matplotlib,nbconvert,nbformat,notebook,parallel,qtconsole]", "ipython[test,test-extra]"] black = ["black"] -doc = ["docrepr", "exceptiongroup", "intersphinx_registry", "ipykernel", "ipython[test]", "matplotlib", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "sphinxcontrib-jquery", "tomli ; python_version < \"3.11\"", "typing_extensions"] +doc = ["docrepr", "exceptiongroup", "intersphinx_registry", "ipykernel", "ipython[test]", "matplotlib", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "sphinxcontrib-jquery", "tomli", "typing_extensions"] kernel = ["ipykernel"] matplotlib = ["matplotlib"] nbconvert = ["nbconvert"] @@ -970,7 +931,6 @@ version = "5.13.2" description = "A Python utility / library to sort Python imports." optional = false python-versions = ">=3.8.0" -groups = ["dev"] files = [ {file = "isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6"}, {file = "isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109"}, @@ -985,7 +945,6 @@ version = "0.19.2" description = "An autocompletion tool for Python that can be used for text editors." optional = false python-versions = ">=3.6" -groups = ["dev"] files = [ {file = "jedi-0.19.2-py2.py3-none-any.whl", hash = "sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9"}, {file = "jedi-0.19.2.tar.gz", hash = "sha256:4770dc3de41bde3966b02eb84fbcf557fb33cce26ad23da12c742fb50ecb11f0"}, @@ -1005,7 +964,6 @@ version = "3.1.6" description = "A very fast and expressive template engine." optional = false python-versions = ">=3.7" -groups = ["main", "dev"] files = [ {file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"}, {file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"}, @@ -1023,7 +981,6 @@ version = "1.0.1" description = "JSON Matching Expressions" optional = false python-versions = ">=3.7" -groups = ["main", "dev"] files = [ {file = "jmespath-1.0.1-py3-none-any.whl", hash = "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980"}, {file = "jmespath-1.0.1.tar.gz", hash = "sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe"}, @@ -1035,7 +992,6 @@ version = "1.2.2" description = "a modern parsing library" optional = false python-versions = ">=3.8" -groups = ["main"] files = [ {file = "lark-1.2.2-py3-none-any.whl", hash = "sha256:c2276486b02f0f1b90be155f2c8ba4a8e194d42775786db622faccd652d8e80c"}, {file = "lark-1.2.2.tar.gz", hash = "sha256:ca807d0162cd16cef15a8feecb862d7319e7a09bdb13aef927968e45040fed80"}, @@ -1053,7 +1009,6 @@ version = "3.0.2" description = "Safely add untrusted strings to HTML/XML markup." optional = false python-versions = ">=3.9" -groups = ["main", "dev"] files = [ {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8"}, {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158"}, @@ -1124,7 +1079,6 @@ version = "0.1.7" description = "Inline Matplotlib backend for Jupyter" optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "matplotlib_inline-0.1.7-py3-none-any.whl", hash = "sha256:df192d39a4ff8f21b1895d72e6a13f5fcc5099f00fa84384e0ea28c2cc0653ca"}, {file = "matplotlib_inline-0.1.7.tar.gz", hash = "sha256:8423b23ec666be3d16e16b60bdd8ac4e86e840ebd1dd11a30b9f117f2fa0ab90"}, @@ -1139,7 +1093,6 @@ version = "0.7.0" description = "McCabe checker, plugin for flake8" optional = false python-versions = ">=3.6" -groups = ["dev"] files = [ {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, @@ -1151,7 +1104,6 @@ version = "1.3.4" description = "A deep merge function for 🐍." optional = false python-versions = ">=3.6" -groups = ["main"] files = [ {file = "mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307"}, {file = "mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8"}, @@ -1163,7 +1115,6 @@ version = "5.1.5" description = "A library that allows you to easily mock out tests based on AWS infrastructure" optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "moto-5.1.5-py3-none-any.whl", hash = "sha256:866ae85eb5efe11a78f991127531878fd7f49177eb4a6680f47060430eb8932d"}, {file = "moto-5.1.5.tar.gz", hash = "sha256:42b362ea9a16181e8e7b615ac212c294b882f020e9ae02f01230f167926df84e"}, @@ -1212,7 +1163,6 @@ version = "1.1.0" description = "Type system extensions for programs checked with the mypy type checker." optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505"}, {file = "mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558"}, @@ -1224,7 +1174,6 @@ version = "3.4.2" description = "Python package for creating and manipulating graphs and networks" optional = false python-versions = ">=3.10" -groups = ["dev"] files = [ {file = "networkx-3.4.2-py3-none-any.whl", hash = "sha256:df5d4365b724cf81b8c6a7312509d0c22386097011ad1abe274afd5e9d3bbc5f"}, {file = "networkx-3.4.2.tar.gz", hash = "sha256:307c3669428c5362aab27c8a1260aa8f47c4e91d3891f48be0141738d8d053e1"}, @@ -1244,7 +1193,6 @@ version = "3.2.2" description = "A generic, spec-compliant, thorough implementation of the OAuth request-signing logic" optional = false python-versions = ">=3.6" -groups = ["main"] files = [ {file = "oauthlib-3.2.2-py3-none-any.whl", hash = "sha256:8139f29aac13e25d502680e9e19963e83f16838d48a0d71c287fe40e7067fbca"}, {file = "oauthlib-3.2.2.tar.gz", hash = "sha256:9859c40929662bec5d64f34d01c99e093149682a3f38915dc0655d5a633dd918"}, @@ -1261,7 +1209,6 @@ version = "4.1.0" description = "An OrderedSet is a custom MutableSet that remembers its order, so that every" optional = false python-versions = ">=3.7" -groups = ["dev"] files = [ {file = "ordered-set-4.1.0.tar.gz", hash = "sha256:694a8e44c87657c59292ede72891eb91d34131f6531463aab3009191c77364a8"}, {file = "ordered_set-4.1.0-py3-none-any.whl", hash = "sha256:046e1132c71fcf3330438a539928932caf51ddbc582496833e23de611de14562"}, @@ -1276,7 +1223,6 @@ version = "24.2" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" -groups = ["main", "dev"] files = [ {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"}, {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, @@ -1288,7 +1234,6 @@ version = "0.8.4" description = "A Python Parser" optional = false python-versions = ">=3.6" -groups = ["dev"] files = [ {file = "parso-0.8.4-py2.py3-none-any.whl", hash = "sha256:a418670a20291dacd2dddc80c377c5c3791378ee1e8d12bffc35420643d43f18"}, {file = "parso-0.8.4.tar.gz", hash = "sha256:eb3a7b58240fb99099a345571deecc0f9540ea5f4dd2fe14c2a99d6b281ab92d"}, @@ -1304,7 +1249,6 @@ version = "0.12.1" description = "Utility library for gitignore style pattern matching of file paths." optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, @@ -1316,8 +1260,6 @@ version = "4.9.0" description = "Pexpect allows easy control of interactive console applications." optional = false python-versions = "*" -groups = ["dev"] -markers = "sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523"}, {file = "pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f"}, @@ -1332,7 +1274,6 @@ version = "4.3.8" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4"}, {file = "platformdirs-4.3.8.tar.gz", hash = "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc"}, @@ -1349,7 +1290,6 @@ version = "1.6.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746"}, {file = "pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3"}, @@ -1365,7 +1305,6 @@ version = "3.0.51" description = "Library for building powerful interactive command lines in Python" optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "prompt_toolkit-3.0.51-py3-none-any.whl", hash = "sha256:52742911fde84e2d423e2f9a4cf1de7d7ac4e51958f648d9540e0fb8db077b07"}, {file = "prompt_toolkit-3.0.51.tar.gz", hash = "sha256:931a162e3b27fc90c86f1b48bb1fb2c528c2761475e57c9c06de13311c7b54ed"}, @@ -1380,7 +1319,6 @@ version = "1.26.1" description = "Beautiful, Pythonic protocol buffers" optional = false python-versions = ">=3.7" -groups = ["main"] files = [ {file = "proto_plus-1.26.1-py3-none-any.whl", hash = "sha256:13285478c2dcf2abb829db158e1047e2f1e8d63a077d94263c2b88b043c75a66"}, {file = "proto_plus-1.26.1.tar.gz", hash = "sha256:21a515a4c4c0088a773899e23c7bbade3d18f9c66c73edd4c7ee3816bc96a012"}, @@ -1398,7 +1336,6 @@ version = "6.31.0" description = "" optional = false python-versions = ">=3.9" -groups = ["main"] files = [ {file = "protobuf-6.31.0-cp310-abi3-win32.whl", hash = "sha256:10bd62802dfa0588649740a59354090eaf54b8322f772fbdcca19bc78d27f0d6"}, {file = "protobuf-6.31.0-cp310-abi3-win_amd64.whl", hash = "sha256:3e987c99fd634be8347246a02123250f394ba20573c953de133dc8b2c107dd71"}, @@ -1417,8 +1354,6 @@ version = "0.7.0" description = "Run a subprocess in a pseudo terminal" optional = false python-versions = "*" -groups = ["dev"] -markers = "sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"}, {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"}, @@ -1430,7 +1365,6 @@ version = "0.2.3" description = "Safely evaluate AST nodes without side effects" optional = false python-versions = "*" -groups = ["dev"] files = [ {file = "pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0"}, {file = "pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42"}, @@ -1445,7 +1379,6 @@ version = "0.6.1" description = "Pure Python PartiQL Parser" optional = false python-versions = "*" -groups = ["dev"] files = [ {file = "py_partiql_parser-0.6.1-py2.py3-none-any.whl", hash = "sha256:ff6a48067bff23c37e9044021bf1d949c83e195490c17e020715e927fe5b2456"}, {file = "py_partiql_parser-0.6.1.tar.gz", hash = "sha256:8583ff2a0e15560ef3bc3df109a7714d17f87d81d33e8c38b7fed4e58a63215d"}, @@ -1460,7 +1393,6 @@ version = "0.6.1" description = "Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208)" optional = false python-versions = ">=3.8" -groups = ["main"] files = [ {file = "pyasn1-0.6.1-py3-none-any.whl", hash = "sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629"}, {file = "pyasn1-0.6.1.tar.gz", hash = "sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034"}, @@ -1472,7 +1404,6 @@ version = "0.4.2" description = "A collection of ASN.1-based protocols modules" optional = false python-versions = ">=3.8" -groups = ["main"] files = [ {file = "pyasn1_modules-0.4.2-py3-none-any.whl", hash = "sha256:29253a9207ce32b64c3ac6600edc75368f98473906e8fd1043bd6b5b1de2c14a"}, {file = "pyasn1_modules-0.4.2.tar.gz", hash = "sha256:677091de870a80aae844b1ca6134f54652fa2c8c5a52aa396440ac3106e941e6"}, @@ -1487,7 +1418,6 @@ version = "2.13.0" description = "Python style guide checker" optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "pycodestyle-2.13.0-py2.py3-none-any.whl", hash = "sha256:35863c5974a271c7a726ed228a14a4f6daf49df369d8c50cd9a6f58a5e143ba9"}, {file = "pycodestyle-2.13.0.tar.gz", hash = "sha256:c8415bf09abe81d9c7f872502a6eee881fbe85d8763dd5b9924bb0a01d67efae"}, @@ -1499,8 +1429,6 @@ version = "2.22" description = "C parser in Python" optional = false python-versions = ">=3.8" -groups = ["dev"] -markers = "platform_python_implementation != \"PyPy\"" files = [ {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, @@ -1512,7 +1440,6 @@ version = "2.11.5" description = "Data validation using Python type hints" optional = false python-versions = ">=3.9" -groups = ["main"] files = [ {file = "pydantic-2.11.5-py3-none-any.whl", hash = "sha256:f9c26ba06f9747749ca1e5c94d6a85cb84254577553c8785576fd38fa64dc0f7"}, {file = "pydantic-2.11.5.tar.gz", hash = "sha256:7f853db3d0ce78ce8bbb148c401c2cdd6431b3473c0cdff2755c7690952a7b7a"}, @@ -1526,7 +1453,7 @@ typing-inspection = ">=0.4.0" [package.extras] email = ["email-validator (>=2.0.0)"] -timezone = ["tzdata ; python_version >= \"3.9\" and platform_system == \"Windows\""] +timezone = ["tzdata"] [[package]] name = "pydantic-core" @@ -1534,7 +1461,6 @@ version = "2.33.2" description = "Core functionality for Pydantic validation and serialization" optional = false python-versions = ">=3.9" -groups = ["main"] files = [ {file = "pydantic_core-2.33.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2b3d326aaef0c0399d9afffeb6367d5e26ddc24d351dbc9c636840ac355dc5d8"}, {file = "pydantic_core-2.33.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e5b2671f05ba48b94cb90ce55d8bdcaaedb8ba00cc5359f6810fc918713983d"}, @@ -1646,7 +1572,6 @@ version = "2.9.1" description = "Settings management using Pydantic" optional = false python-versions = ">=3.9" -groups = ["main"] files = [ {file = "pydantic_settings-2.9.1-py3-none-any.whl", hash = "sha256:59b4f431b1defb26fe620c71a7d3968a710d719f5f4cdbbdb7926edeb770f6ef"}, {file = "pydantic_settings-2.9.1.tar.gz", hash = "sha256:c509bf79d27563add44e8446233359004ed85066cd096d8b510f715e6ef5d268"}, @@ -1670,7 +1595,6 @@ version = "3.3.2" description = "passive checker of Python programs" optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "pyflakes-3.3.2-py2.py3-none-any.whl", hash = "sha256:5039c8339cbb1944045f4ee5466908906180f13cc99cc9949348d10f82a5c32a"}, {file = "pyflakes-3.3.2.tar.gz", hash = "sha256:6dfd61d87b97fba5dcfaaf781171ac16be16453be6d816147989e7f6e6a9576b"}, @@ -1682,7 +1606,6 @@ version = "2.19.1" description = "Pygments is a syntax highlighting package written in Python." optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c"}, {file = "pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f"}, @@ -1697,7 +1620,6 @@ version = "8.3.5" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820"}, {file = "pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845"}, @@ -1718,7 +1640,6 @@ version = "5.0.0" description = "Pytest plugin for measuring coverage." optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "pytest-cov-5.0.0.tar.gz", hash = "sha256:5837b58e9f6ebd335b0f8060eecce69b662415b16dc503883a02f45dfeb14857"}, {file = "pytest_cov-5.0.0-py3-none-any.whl", hash = "sha256:4f0764a1219df53214206bf1feea4633c3b558a2925c8b59f144f682861ce652"}, @@ -1737,7 +1658,6 @@ version = "1.0.1" description = "Tests that depend on other tests" optional = false python-versions = "*" -groups = ["dev"] files = [ {file = "pytest-depends-1.0.1.tar.gz", hash = "sha256:90a28e2b87b75b18abd128c94015248544acac20e4392e9921e5a86f93319dfe"}, {file = "pytest_depends-1.0.1-py3-none-any.whl", hash = "sha256:a1df072bcc93d77aca3f0946903f5fed8af2d9b0056db1dfc9ed5ac164ab0642"}, @@ -1755,7 +1675,6 @@ version = "3.14.1" description = "Thin-wrapper around the mock package for easier use with pytest" optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "pytest_mock-3.14.1-py3-none-any.whl", hash = "sha256:178aefcd11307d874b4cd3100344e7e2d888d9791a6a1d9bfe90fbc1b74fd1d0"}, {file = "pytest_mock-3.14.1.tar.gz", hash = "sha256:159e9edac4c451ce77a5cdb9fc5d1100708d2dd4ba3c3df572f14097351af80e"}, @@ -1773,7 +1692,6 @@ version = "0.7.0" description = "Pytest Plugin to disable socket calls during tests" optional = false python-versions = ">=3.8,<4.0" -groups = ["dev"] files = [ {file = "pytest_socket-0.7.0-py3-none-any.whl", hash = "sha256:7e0f4642177d55d317bbd58fc68c6bd9048d6eadb2d46a89307fa9221336ce45"}, {file = "pytest_socket-0.7.0.tar.gz", hash = "sha256:71ab048cbbcb085c15a4423b73b619a8b35d6a307f46f78ea46be51b1b7e11b3"}, @@ -1788,7 +1706,6 @@ version = "2.3.1" description = "pytest plugin to abort hanging tests" optional = false python-versions = ">=3.7" -groups = ["dev"] files = [ {file = "pytest-timeout-2.3.1.tar.gz", hash = "sha256:12397729125c6ecbdaca01035b9e5239d4db97352320af155b3f5de1ba5165d9"}, {file = "pytest_timeout-2.3.1-py3-none-any.whl", hash = "sha256:68188cb703edfc6a18fad98dc25a3c61e9f24d644b0b70f33af545219fc7813e"}, @@ -1803,7 +1720,6 @@ version = "2.9.0.post0" description = "Extensions to the standard Python datetime module" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -groups = ["main", "dev"] files = [ {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, @@ -1818,7 +1734,6 @@ version = "1.1.0" description = "Read key-value pairs from a .env file and set them as environment variables" optional = false python-versions = ">=3.9" -groups = ["main"] files = [ {file = "python_dotenv-1.1.0-py3-none-any.whl", hash = "sha256:d7c01d9e2293916c18baf562d95698754b0dbbb5e74d457c45d4f6561fb9d55d"}, {file = "python_dotenv-1.1.0.tar.gz", hash = "sha256:41f90bc6f5f177fb41f53e87666db362025010eb28f60a01c9143bfa33a2b2d5"}, @@ -1833,7 +1748,6 @@ version = "4.3.5" description = "A parser for HCL2" optional = false python-versions = ">=3.7.0" -groups = ["main"] files = [ {file = "python-hcl2-4.3.5.tar.gz", hash = "sha256:71fbe48ee9a13335828f04adff2b267e06045c44c99c737b13d940aa1468d101"}, {file = "python_hcl2-4.3.5-py3-none-any.whl", hash = "sha256:e33795eed675ae59545e725d1de0a8d78b4166b30b6e0bc7699365bd06334068"}, @@ -1848,8 +1762,6 @@ version = "310" description = "Python for Window Extensions" optional = false python-versions = "*" -groups = ["dev"] -markers = "sys_platform == \"win32\"" files = [ {file = "pywin32-310-cp310-cp310-win32.whl", hash = "sha256:6dd97011efc8bf51d6793a82292419eba2c71cf8e7250cfac03bba284454abc1"}, {file = "pywin32-310-cp310-cp310-win_amd64.whl", hash = "sha256:c3e78706e4229b915a0821941a84e7ef420bf2b77e08c9dae3c76fd03fd2ae3d"}, @@ -1875,7 +1787,6 @@ version = "6.0.2" description = "YAML parser and emitter for Python" optional = false python-versions = ">=3.8" -groups = ["main", "dev"] files = [ {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, @@ -1938,7 +1849,6 @@ version = "2.32.3" description = "Python HTTP for Humans." optional = false python-versions = ">=3.8" -groups = ["main", "dev"] files = [ {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, @@ -1960,7 +1870,6 @@ version = "2.0.0" description = "OAuthlib authentication support for Requests." optional = false python-versions = ">=3.4" -groups = ["main"] files = [ {file = "requests-oauthlib-2.0.0.tar.gz", hash = "sha256:b3dffaebd884d8cd778494369603a9e7b58d29111bf6b41bdc2dcd87203af4e9"}, {file = "requests_oauthlib-2.0.0-py2.py3-none-any.whl", hash = "sha256:7dd8a5c40426b779b0868c404bdef9768deccf22749cde15852df527e6269b36"}, @@ -1979,7 +1888,6 @@ version = "0.25.7" description = "A utility library for mocking out the `requests` Python library." optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "responses-0.25.7-py3-none-any.whl", hash = "sha256:92ca17416c90fe6b35921f52179bff29332076bb32694c0df02dcac2c6bc043c"}, {file = "responses-0.25.7.tar.gz", hash = "sha256:8ebae11405d7a5df79ab6fd54277f6f2bc29b2d002d0dd2d5c632594d1ddcedb"}, @@ -1991,7 +1899,7 @@ requests = ">=2.30.0,<3.0" urllib3 = ">=1.25.10,<3.0" [package.extras] -tests = ["coverage (>=6.0.0)", "flake8", "mypy", "pytest (>=7.0.0)", "pytest-asyncio", "pytest-cov", "pytest-httpserver", "tomli ; python_version < \"3.11\"", "tomli-w", "types-PyYAML", "types-requests"] +tests = ["coverage (>=6.0.0)", "flake8", "mypy", "pytest (>=7.0.0)", "pytest-asyncio", "pytest-cov", "pytest-httpserver", "tomli", "tomli-w", "types-PyYAML", "types-requests"] [[package]] name = "rsa" @@ -1999,7 +1907,6 @@ version = "4.9.1" description = "Pure-Python RSA implementation" optional = false python-versions = "<4,>=3.6" -groups = ["main"] files = [ {file = "rsa-4.9.1-py3-none-any.whl", hash = "sha256:68635866661c6836b8d39430f97a996acbd61bfa49406748ea243539fe239762"}, {file = "rsa-4.9.1.tar.gz", hash = "sha256:e7bdbfdb5497da4c07dfd35530e1a902659db6ff241e39d9953cad06ebd0ae75"}, @@ -2014,7 +1921,6 @@ version = "0.13.0" description = "An Amazon S3 Transfer Manager" optional = false python-versions = ">=3.9" -groups = ["main", "dev"] files = [ {file = "s3transfer-0.13.0-py3-none-any.whl", hash = "sha256:0148ef34d6dd964d0d8cf4311b2b21c474693e57c2e069ec708ce043d2b527be"}, {file = "s3transfer-0.13.0.tar.gz", hash = "sha256:f5e6db74eb7776a37208001113ea7aa97695368242b364d73e91c981ac522177"}, @@ -2032,7 +1938,6 @@ version = "2.2.0" description = "Statically populate the `known_third_party` `isort` setting." optional = false python-versions = ">=3.6.1" -groups = ["dev"] files = [ {file = "seed_isort_config-2.2.0-py2.py3-none-any.whl", hash = "sha256:8601fb715a5a4aac39256bbf73c2da6a81f964da9c9d9897ab9074db3663526f"}, {file = "seed_isort_config-2.2.0.tar.gz", hash = "sha256:be4cfef8f9a3fe8ea1817069c6b624538ac0b429636ec746edeb27e98ed628c8"}, @@ -2047,20 +1952,19 @@ version = "78.1.1" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.9" -groups = ["main"] files = [ {file = "setuptools-78.1.1-py3-none-any.whl", hash = "sha256:c3a9c4211ff4c309edb8b8c4f1cbfa7ae324c4ba9f91ff254e3d305b9fd54561"}, {file = "setuptools-78.1.1.tar.gz", hash = "sha256:fcc17fd9cd898242f6b4adfaca46137a9edef687f43e6f78469692a5e70d851d"}, ] [package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\"", "ruff (>=0.8.0) ; sys_platform != \"cygwin\""] -core = ["importlib_metadata (>=6) ; python_version < \"3.10\"", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1) ; python_version < \"3.11\"", "wheel (>=0.43.0)"] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)", "ruff (>=0.8.0)"] +core = ["importlib_metadata (>=6)", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"] enabler = ["pytest-enabler (>=2.2)"] -test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21) ; python_version >= \"3.9\" and sys_platform != \"cygwin\"", "jaraco.envs (>=2.2)", "jaraco.path (>=3.7.2)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf ; sys_platform != \"cygwin\"", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] -type = ["importlib_metadata (>=7.0.2) ; python_version < \"3.10\"", "jaraco.develop (>=7.21) ; sys_platform != \"cygwin\"", "mypy (==1.14.*)", "pytest-mypy"] +test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.7.2)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] +type = ["importlib_metadata (>=7.0.2)", "jaraco.develop (>=7.21)", "mypy (==1.14.*)", "pytest-mypy"] [[package]] name = "six" @@ -2068,7 +1972,6 @@ version = "1.17.0" description = "Python 2 and 3 compatibility utilities" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -groups = ["main", "dev"] files = [ {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, @@ -2080,7 +1983,6 @@ version = "3.0.1" description = "This package provides 32 stemmers for 30 languages generated from Snowball algorithms." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*" -groups = ["dev"] files = [ {file = "snowballstemmer-3.0.1-py3-none-any.whl", hash = "sha256:6cd7b3897da8d6c9ffb968a6781fa6532dce9c3618a4b127d920dab764a19064"}, {file = "snowballstemmer-3.0.1.tar.gz", hash = "sha256:6d5eeeec8e9f84d4d56b847692bacf79bc2c8e90c7f80ca4444ff8b6f2e52895"}, @@ -2092,7 +1994,6 @@ version = "2.7" description = "A modern CSS selector implementation for Beautiful Soup." optional = false python-versions = ">=3.8" -groups = ["main"] files = [ {file = "soupsieve-2.7-py3-none-any.whl", hash = "sha256:6e60cc5c1ffaf1cebcc12e8188320b72071e922c2e897f737cadce79ad5d30c4"}, {file = "soupsieve-2.7.tar.gz", hash = "sha256:ad282f9b6926286d2ead4750552c8a6142bc4c783fd66b0293547c8fe6ae126a"}, @@ -2104,7 +2005,6 @@ version = "7.4.7" description = "Python documentation generator" optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "sphinx-7.4.7-py3-none-any.whl", hash = "sha256:c2419e2135d11f1951cd994d6eb18a1835bd8fdd8429f9ca375dc1f3281bd239"}, {file = "sphinx-7.4.7.tar.gz", hash = "sha256:242f92a7ea7e6c5b406fdc2615413890ba9f699114a9c09192d7dfead2ee9cfe"}, @@ -2139,7 +2039,6 @@ version = "2.0.0" description = "sphinxcontrib-applehelp is a Sphinx extension which outputs Apple help books" optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5"}, {file = "sphinxcontrib_applehelp-2.0.0.tar.gz", hash = "sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1"}, @@ -2156,7 +2055,6 @@ version = "2.0.0" description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp documents" optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "sphinxcontrib_devhelp-2.0.0-py3-none-any.whl", hash = "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2"}, {file = "sphinxcontrib_devhelp-2.0.0.tar.gz", hash = "sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad"}, @@ -2173,7 +2071,6 @@ version = "2.1.0" description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8"}, {file = "sphinxcontrib_htmlhelp-2.1.0.tar.gz", hash = "sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9"}, @@ -2190,7 +2087,6 @@ version = "1.0.1" description = "A sphinx extension which renders display math in HTML via JavaScript" optional = false python-versions = ">=3.5" -groups = ["dev"] files = [ {file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"}, {file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"}, @@ -2205,7 +2101,6 @@ version = "2.0.0" description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp documents" optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "sphinxcontrib_qthelp-2.0.0-py3-none-any.whl", hash = "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb"}, {file = "sphinxcontrib_qthelp-2.0.0.tar.gz", hash = "sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab"}, @@ -2222,7 +2117,6 @@ version = "2.0.0" description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)" optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331"}, {file = "sphinxcontrib_serializinghtml-2.0.0.tar.gz", hash = "sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d"}, @@ -2239,7 +2133,6 @@ version = "0.6.3" description = "Extract data from python stack frames and tracebacks for informative displays" optional = false python-versions = "*" -groups = ["dev"] files = [ {file = "stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695"}, {file = "stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9"}, @@ -2259,7 +2152,6 @@ version = "5.14.3" description = "Traitlets Python configuration system" optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f"}, {file = "traitlets-5.14.3.tar.gz", hash = "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7"}, @@ -2275,12 +2167,10 @@ version = "4.13.2" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" -groups = ["main", "dev"] files = [ {file = "typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c"}, {file = "typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef"}, ] -markers = {dev = "python_version < \"3.12\""} [[package]] name = "typing-inspection" @@ -2288,7 +2178,6 @@ version = "0.4.1" description = "Runtime typing introspection tools" optional = false python-versions = ">=3.9" -groups = ["main"] files = [ {file = "typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51"}, {file = "typing_inspection-0.4.1.tar.gz", hash = "sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28"}, @@ -2303,14 +2192,13 @@ version = "2.4.0" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.9" -groups = ["main", "dev"] files = [ {file = "urllib3-2.4.0-py3-none-any.whl", hash = "sha256:4e16665048960a0900c702d4a66415956a584919c03361cac9f1df5c5dd7e813"}, {file = "urllib3-2.4.0.tar.gz", hash = "sha256:414bc6535b787febd7567804cc015fee39daab8ad86268f1310a9250697de466"}, ] [package.extras] -brotli = ["brotli (>=1.0.9) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\""] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] @@ -2321,7 +2209,6 @@ version = "0.2.13" description = "Measures the displayed width of unicode strings in a terminal" optional = false python-versions = "*" -groups = ["dev"] files = [ {file = "wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859"}, {file = "wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5"}, @@ -2333,7 +2220,6 @@ version = "3.1.3" description = "The comprehensive WSGI web application library." optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "werkzeug-3.1.3-py3-none-any.whl", hash = "sha256:54b78bf3716d19a65be4fceccc0d1d7b89e608834989dfae50ea87564639213e"}, {file = "werkzeug-3.1.3.tar.gz", hash = "sha256:60723ce945c19328679790e3282cc758aa4a6040e4bb330f53d30fa546d44746"}, @@ -2351,7 +2237,6 @@ version = "0.43.0" description = "A built-package format for Python" optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "wheel-0.43.0-py3-none-any.whl", hash = "sha256:55c570405f142630c6b9f72fe09d9b67cf1477fcf543ae5b8dcb1f5b7377da81"}, {file = "wheel-0.43.0.tar.gz", hash = "sha256:465ef92c69fa5c5da2d1cf8ac40559a8c940886afcef87dcf14b9470862f1d85"}, @@ -2366,7 +2251,6 @@ version = "1.17.2" description = "Module for decorators, wrappers and monkey patching." optional = false python-versions = ">=3.8" -groups = ["main"] files = [ {file = "wrapt-1.17.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3d57c572081fed831ad2d26fd430d565b76aa277ed1d30ff4d40670b1c0dd984"}, {file = "wrapt-1.17.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b5e251054542ae57ac7f3fba5d10bfff615b6c2fb09abeb37d2f1463f841ae22"}, @@ -2455,13 +2339,12 @@ version = "0.14.2" description = "Makes working with XML feel like you are working with JSON" optional = false python-versions = ">=3.6" -groups = ["dev"] files = [ {file = "xmltodict-0.14.2-py2.py3-none-any.whl", hash = "sha256:20cc7d723ed729276e808f26fb6b3599f786cbc37e06c65e192ba77c40f20aac"}, {file = "xmltodict-0.14.2.tar.gz", hash = "sha256:201e7c28bb210e374999d1dde6382923ab0ed1a8a5faeece48ab525b7810a553"}, ] [metadata] -lock-version = "2.1" +lock-version = "2.0" python-versions = "^3.11" -content-hash = "9a133720a39af1d95f81a685dfc3a28b1813cd10955b83677298cd2832588bc8" +content-hash = "62ac9918e041ffb709b6cf4ef040938f826b4d151e83aee0d610dae7569e7bf2" diff --git a/pyproject.toml b/pyproject.toml index 236e147..62d4ccd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,7 +37,7 @@ mergedeep = "^1.3" pydantic = "^2.7" python-hcl2 = "^4.3" pyyaml = "^6.0" -setuptools = ">=70,<79" +setuptools = "^78.1" pydantic-settings = "^2.3.4" packaging = "^24.1" diff --git a/tests/authenticators/test_auth_aws.py b/tests/authenticators/test_auth_aws.py index 8695368..ea5449e 100644 --- a/tests/authenticators/test_auth_aws.py +++ b/tests/authenticators/test_auth_aws.py @@ -5,6 +5,7 @@ from botocore.exceptions import NoCredentialsError from moto import mock_aws from pydantic import ValidationError + from tfworker.authenticators import AWSAuthenticator, AWSAuthenticatorConfig from tfworker.authenticators.aws import ( _assume_role_session, diff --git a/tests/authenticators/test_authenticators_collection.py b/tests/authenticators/test_authenticators_collection.py index 82af026..2de07f3 100644 --- a/tests/authenticators/test_authenticators_collection.py +++ b/tests/authenticators/test_authenticators_collection.py @@ -3,6 +3,7 @@ import pytest from pydantic import ValidationError from pydantic_core import InitErrorDetails + from tfworker.authenticators.base import BaseAuthenticator, BaseAuthenticatorConfig from tfworker.authenticators.collection import AuthenticatorsCollection from tfworker.exceptions import FrozenInstanceError, UnknownAuthenticator diff --git a/tests/backends/test_backends_base.py b/tests/backends/test_backends_base.py index 2afe806..fa908fd 100644 --- a/tests/backends/test_backends_base.py +++ b/tests/backends/test_backends_base.py @@ -1,6 +1,7 @@ import json import pytest + from tfworker.backends.base import validate_backend_empty from tfworker.exceptions import BackendError diff --git a/tests/backends/test_backends_s3.py b/tests/backends/test_backends_s3.py index fe99097..80cf257 100644 --- a/tests/backends/test_backends_s3.py +++ b/tests/backends/test_backends_s3.py @@ -3,8 +3,9 @@ import boto3 import botocore import pytest -import tfworker.util.log as log from moto import mock_aws + +import tfworker.util.log as log from tfworker.backends.s3 import S3Backend from tfworker.exceptions import BackendError diff --git a/tests/conftest.py b/tests/conftest.py index c5b32f1..1ba222c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -6,6 +6,7 @@ import click import pytest from moto import mock_aws + from tfworker.app_state import AppState from tfworker.authenticators import AuthenticatorsCollection from tfworker.cli_options import CLIOptionsClean, CLIOptionsRoot, CLIOptionsTerraform diff --git a/tests/copier/test_copier_factory.py b/tests/copier/test_copier_factory.py index 682b139..64454aa 100644 --- a/tests/copier/test_copier_factory.py +++ b/tests/copier/test_copier_factory.py @@ -4,6 +4,7 @@ from unittest.mock import patch import pytest + from tfworker.copier.factory import Copier, CopyFactory C_CONFLICTS = ["test.txt", "foo", "test.tf"] diff --git a/tests/copier/test_copier_fs.py b/tests/copier/test_copier_fs.py index 7fd73ac..076f1a2 100644 --- a/tests/copier/test_copier_fs.py +++ b/tests/copier/test_copier_fs.py @@ -2,6 +2,7 @@ import tempfile import pytest + from tfworker.copier import FileSystemCopier diff --git a/tests/copier/test_copier_git.py b/tests/copier/test_copier_git.py index fde1c60..54d5406 100644 --- a/tests/copier/test_copier_git.py +++ b/tests/copier/test_copier_git.py @@ -6,6 +6,7 @@ from unittest import mock import pytest + from tfworker.copier import GitCopier C_CONFLICTS = ["test.txt", "foo", "test.tf"] diff --git a/tests/definitions/test_definitions_collection.py b/tests/definitions/test_definitions_collection.py index 7c1ed85..4d5defb 100644 --- a/tests/definitions/test_definitions_collection.py +++ b/tests/definitions/test_definitions_collection.py @@ -1,4 +1,5 @@ import pytest + from tfworker.definitions import Definition, DefinitionsCollection from tfworker.exceptions import FrozenInstanceError diff --git a/tests/definitions/test_definitions_model.py b/tests/definitions/test_definitions_model.py index 374930b..3aa7ebf 100644 --- a/tests/definitions/test_definitions_model.py +++ b/tests/definitions/test_definitions_model.py @@ -1,4 +1,5 @@ import pytest + from tfworker.definitions.model import Definition, DefinitionRemoteOptions diff --git a/tests/definitions/test_definitions_prepare.py b/tests/definitions/test_definitions_prepare.py index 3fe0ccf..c1f50b0 100644 --- a/tests/definitions/test_definitions_prepare.py +++ b/tests/definitions/test_definitions_prepare.py @@ -1,4 +1,5 @@ import pytest + from tfworker.definitions import Definition, DefinitionsCollection from tfworker.definitions.prepare import DefinitionPrepare from tfworker.exceptions import ReservedFileError, TFWorkerException diff --git a/tests/handlers/test_snyk_scan.py b/tests/handlers/test_snyk_scan.py index d99a4cb..e10c7d1 100644 --- a/tests/handlers/test_snyk_scan.py +++ b/tests/handlers/test_snyk_scan.py @@ -1,12 +1,14 @@ -import pytest import shutil from pathlib import Path -from unittest.mock import Mock, patch, MagicMock +from unittest.mock import MagicMock, Mock, patch + +import pytest + +from tfworker.commands.terraform import TerraformResult from tfworker.definitions import Definition from tfworker.exceptions import HandlerError -from tfworker.commands.terraform import TerraformResult -from tfworker.types import TerraformAction, TerraformStage from tfworker.handlers import SnykConfig, SnykHandler +from tfworker.types import TerraformAction, TerraformStage mock_path_exists = Mock() mock_path_exists_fn = Mock(return_value=True) @@ -16,15 +18,15 @@ mock_path_not_exists_fn = Mock(return_value=False) mock_path_not_exists.exists = mock_path_not_exists_fn -mock_access_allowed = Mock(return_value=True) +mock_access_allowed = Mock(return_value=True) mock_access_denied = Mock(return_value=False) mock_environ = MagicMock() -mock_environ_dict = {'SNYK_TOKEN': "1234567890"} +mock_environ_dict = {"SNYK_TOKEN": "1234567890"} mock_environ.__getitem__.side_effect = mock_environ_dict.__getitem__ mock_environ_no_token = MagicMock() -mock_environ_no_token.__getitem__.side_effect = {'SNYK_TOKEN': None}.__getitem__ +mock_environ_no_token.__getitem__.side_effect = {"SNYK_TOKEN": None}.__getitem__ tmp_dir = f"{Path.cwd()}/.pytest-tmp" snyk_path = f"{tmp_dir}/snyk" @@ -37,19 +39,22 @@ # Copy success/failure scripts in its place to simulate mock_snyk_success_path = f"{scripts_dir}/mock_snyk_success.sh" mock_snyk_failure_path = f"{scripts_dir}/mock_snyk_failure.sh" + + @pytest.fixture(autouse=True) def required_file_system_paths(): - Path.mkdir(Path(tmp_dir), 0o777, exist_ok=True) - Path.mkdir(f"{tmp_dir}/plans", 0o777, parents=False, exist_ok=True) - Path(plan_path).touch(0o777) - Path.mkdir(Path(definition_dir), mode=0o777, parents=True, exist_ok=True) - yield - shutil.rmtree("./.pytest-tmp") + Path.mkdir(Path(tmp_dir), 0o777, exist_ok=True) + Path.mkdir(f"{tmp_dir}/plans", 0o777, parents=False, exist_ok=True) + Path(plan_path).touch(0o777) + Path.mkdir(Path(definition_dir), mode=0o777, parents=True, exist_ok=True) + yield + shutil.rmtree("./.pytest-tmp") + class TestSnykHandler: - @patch('os.path', mock_path_exists) - @patch('os.access', mock_access_allowed) - @patch('os.environ', mock_environ) + @patch("os.path", mock_path_exists) + @patch("os.access", mock_access_allowed) + @patch("os.environ", mock_environ) def test_init(self): config = Mock() config.model_fields = ["path", "my_custom_field", "required"] @@ -60,12 +65,12 @@ def test_init(self): handler = SnykHandler(config) assert handler._path == "/usr/bin/snyk" assert handler._my_custom_field == "abc123" - assert handler._required == True + assert handler._required is True mock_path_exists_fn.assert_called mock_access_allowed.assert_called - @patch('os.path', mock_path_not_exists) - @patch('os.access', mock_access_allowed) + @patch("os.path", mock_path_not_exists) + @patch("os.access", mock_access_allowed) def test_init_path_not_exists(self): config = Mock() config.model_fields = ["path", "required"] @@ -73,14 +78,14 @@ def test_init_path_not_exists(self): config.required = True with pytest.raises(HandlerError): - handler = SnykHandler(config) - assert handler._path == "/usr/bin/snyk" - assert handler._required == True - mock_path_not_exists_fn.assert_called - mock_access_allowed.assert_not_called - - @patch('os.path', mock_path_exists) - @patch('os.access', mock_access_denied) + handler = SnykHandler(config) + assert handler._path == "/usr/bin/snyk" + assert handler._required is True + mock_path_not_exists_fn.assert_called + mock_access_allowed.assert_not_called + + @patch("os.path", mock_path_exists) + @patch("os.access", mock_access_denied) def test_init_path_not_allowed(self): config = Mock() config.model_fields = ["path", "required"] @@ -88,15 +93,15 @@ def test_init_path_not_allowed(self): config.required = True with pytest.raises(HandlerError): - handler = SnykHandler(config) - assert handler._path == "/usr/bin/snyk" - assert handler._required == True - mock_path_exists_fn.assert_called - mock_access_denied.assert_called - - @patch('os.path', mock_path_exists) - @patch('os.access', mock_access_allowed) - @patch('os.environ', mock_environ_no_token) + handler = SnykHandler(config) + assert handler._path == "/usr/bin/snyk" + assert handler._required is True + mock_path_exists_fn.assert_called + mock_access_denied.assert_called + + @patch("os.path", mock_path_exists) + @patch("os.access", mock_access_allowed) + @patch("os.environ", mock_environ_no_token) def test_init_missing_token(self): config = Mock() config.model_fields = ["path", "required"] @@ -104,32 +109,32 @@ def test_init_missing_token(self): config.required = True with pytest.raises(HandlerError): - handler = SnykHandler(config) - assert handler._path == "/usr/bin/snyk" - assert handler._required == True - mock_path_exists_fn.assert_called - mock_access_allowed.assert_called + handler = SnykHandler(config) + assert handler._path == "/usr/bin/snyk" + assert handler._required is True + mock_path_exists_fn.assert_called + mock_access_allowed.assert_called - @patch('os.environ', mock_environ) + @patch("os.environ", mock_environ) def test_execute_pre_plan(self): # Mock out the snyk call with a successful script shutil.copy(Path(mock_snyk_success_path), Path(snyk_path)) - + config = SnykConfig(path=snyk_path, required=True) handler = SnykHandler(config) handler.execute( action=TerraformAction.PLAN, stage=TerraformStage.PRE, deployment="apps/dev", - definition=Definition(name='test_def', path="apps-dev/test-def"), + definition=Definition(name="test_def", path="apps-dev/test-def"), working_dir=working_dir, ) - @patch('os.environ', mock_environ) + @patch("os.environ", mock_environ) def test_execute_pre_plan_fail(self): # Mock out the snyk call with a failure script shutil.copy(Path(mock_snyk_failure_path), Path(snyk_path)) - + config = SnykConfig(path=snyk_path, required=True) with pytest.raises(HandlerError): @@ -137,35 +142,39 @@ def test_execute_pre_plan_fail(self): action=TerraformAction.PLAN, stage=TerraformStage.PRE, deployment="apps/dev", - definition=Definition(name='test_def', path="apps-dev/test-def"), + definition=Definition(name="test_def", path="apps-dev/test-def"), working_dir=working_dir, ) - @patch('os.environ', mock_environ) + @patch("os.environ", mock_environ) def test_execute_post_plan_no_changes(self): # Mock out the snyk call with a failre script # We shouldn't end up calling it, so it should fail if we do shutil.copy(Path(mock_snyk_failure_path), Path(snyk_path)) - - result = TerraformResult(0, "Plan has no changes".encode("utf-8"), "".encode("utf-8")) + + result = TerraformResult( + 0, "Plan has no changes".encode("utf-8"), "".encode("utf-8") + ) config = SnykConfig(path=snyk_path, required=True) handler = SnykHandler(config) handler.execute( action=TerraformAction.PLAN, stage=TerraformStage.POST, deployment="apps/dev", - definition=Definition(name='test_def', path="apps-dev/test-def"), + definition=Definition(name="test_def", path="apps-dev/test-def"), working_dir=working_dir, - result=result + result=result, ) - @patch('os.environ', mock_environ) + @patch("os.environ", mock_environ) def test_execute_post_plan_changes_fail(self): # Mock out the snyk call with a faliure script # We want to ensure it got called, so we'll expect the failure as an exception shutil.copy(Path(mock_snyk_failure_path), Path(snyk_path)) - - result = TerraformResult(2, "Plan has changes".encode("utf-8"), "".encode("utf-8")) + + result = TerraformResult( + 2, "Plan has changes".encode("utf-8"), "".encode("utf-8") + ) config = SnykConfig(path=snyk_path, required=True) handler = SnykHandler(config) @@ -174,24 +183,21 @@ def test_execute_post_plan_changes_fail(self): action=TerraformAction.PLAN, stage=TerraformStage.POST, deployment="apps/dev", - definition=Definition(name='test_def', path="apps-dev/test-def", plan_file=plan_path), + definition=Definition( + name="test_def", path="apps-dev/test-def", plan_file=plan_path + ), working_dir=working_dir, - result=result + result=result, ) - @patch('os.environ', mock_environ) - @patch('os.access', mock_access_allowed) - @patch('os.environ', mock_environ_no_token) + @patch("os.environ", mock_environ) + @patch("os.access", mock_access_allowed) + @patch("os.environ", mock_environ_no_token) def test_verify_snyk_args(self): - # Mock out the snyk call with a successful script - shutil.copy(Path(mock_snyk_success_path), Path(snyk_path)) - config = SnykConfig(path=snyk_path) - handler = SnykHandler(config) - test_def_dir = f"{definition_dir}/my-test-def" - args = handler._build_snyk_args(test_def_dir) - assert args == [ - snyk_path, - "iac", - "test", - test_def_dir - ] \ No newline at end of file + # Mock out the snyk call with a successful script + shutil.copy(Path(mock_snyk_success_path), Path(snyk_path)) + config = SnykConfig(path=snyk_path) + handler = SnykHandler(config) + test_def_dir = f"{definition_dir}/my-test-def" + args = handler._build_snyk_args(test_def_dir) + assert args == [snyk_path, "iac", "test", test_def_dir] diff --git a/tests/test_app_state.py b/tests/test_app_state.py index fa9a867..ce315e9 100644 --- a/tests/test_app_state.py +++ b/tests/test_app_state.py @@ -1,4 +1,5 @@ import pytest + from tfworker.app_state import AppState from tfworker.cli_options import CLIOptionsTerraform from tfworker.exceptions import FrozenInstanceError diff --git a/tests/test_cli_options.py b/tests/test_cli_options.py index 3403b40..dded6be 100644 --- a/tests/test_cli_options.py +++ b/tests/test_cli_options.py @@ -1,4 +1,5 @@ import pytest + from tfworker import cli_options as c diff --git a/tests/util/test_util_cli.py b/tests/util/test_util_cli.py index 94be0ab..5d88c19 100644 --- a/tests/util/test_util_cli.py +++ b/tests/util/test_util_cli.py @@ -4,6 +4,7 @@ import click import pytest from pydantic import BaseModel + from tfworker.util.cli import pydantic_to_click, validate_host diff --git a/tests/util/test_util_hooks.py b/tests/util/test_util_hooks.py index 107f6cf..f664d22 100644 --- a/tests/util/test_util_hooks.py +++ b/tests/util/test_util_hooks.py @@ -1,6 +1,7 @@ from unittest import mock import pytest + import tfworker.util.hooks as hooks from tfworker.exceptions import HookError from tfworker.types.terraform import TerraformAction, TerraformStage diff --git a/tests/util/test_util_log.py b/tests/util/test_util_log.py index f5cddfb..1b33f75 100644 --- a/tests/util/test_util_log.py +++ b/tests/util/test_util_log.py @@ -1,6 +1,7 @@ from unittest.mock import patch import pytest + import tfworker.util.log as log REDACTED_ITEMS = ["aws_secret_access_key", "aws_session_token", "aws_profile"] diff --git a/tests/util/test_util_system.py b/tests/util/test_util_system.py index e1b7931..faae8d9 100644 --- a/tests/util/test_util_system.py +++ b/tests/util/test_util_system.py @@ -1,6 +1,7 @@ from unittest import mock import pytest + from tfworker.util.system import get_platform, pipe_exec, strip_ansi diff --git a/tests/util/test_util_terraform_helpers.py b/tests/util/test_util_terraform_helpers.py index 863e5f3..0ede7c6 100644 --- a/tests/util/test_util_terraform_helpers.py +++ b/tests/util/test_util_terraform_helpers.py @@ -5,6 +5,7 @@ import pytest from packaging.specifiers import SpecifierSet + from tfworker.exceptions import TFWorkerException from tfworker.providers import ProviderGID from tfworker.providers.collection import ProvidersCollection diff --git a/tfworker/app_state.py b/tfworker/app_state.py index 8188384..f2917c7 100644 --- a/tfworker/app_state.py +++ b/tfworker/app_state.py @@ -1,6 +1,7 @@ from pathlib import Path from pydantic import ConfigDict, Field + from tfworker import cli_options from tfworker.authenticators import AuthenticatorsCollection from tfworker.backends import BaseBackend diff --git a/tfworker/authenticators/aws.py b/tfworker/authenticators/aws.py index daa7109..3c307a9 100644 --- a/tfworker/authenticators/aws.py +++ b/tfworker/authenticators/aws.py @@ -2,9 +2,10 @@ from typing import Dict import boto3 -import tfworker.util.log as log from botocore.credentials import Credentials from pydantic import model_validator + +import tfworker.util.log as log from tfworker.exceptions import TFWorkerException from .base import BaseAuthenticator, BaseAuthenticatorConfig diff --git a/tfworker/authenticators/collection.py b/tfworker/authenticators/collection.py index 8ccac7a..9821012 100644 --- a/tfworker/authenticators/collection.py +++ b/tfworker/authenticators/collection.py @@ -2,8 +2,9 @@ import threading from typing import TYPE_CHECKING -import tfworker.util.log as log from pydantic import ValidationError + +import tfworker.util.log as log from tfworker.exceptions import FrozenInstanceError, UnknownAuthenticator from .aws import AWSAuthenticator # noqa diff --git a/tfworker/backends/gcs.py b/tfworker/backends/gcs.py index f1075e9..9795ca3 100644 --- a/tfworker/backends/gcs.py +++ b/tfworker/backends/gcs.py @@ -2,11 +2,12 @@ from typing import TYPE_CHECKING import click -import tfworker.util.log as log from google.api_core import page_iterator from google.auth.exceptions import DefaultCredentialsError from google.cloud import storage from google.cloud.exceptions import Conflict, NotFound + +import tfworker.util.log as log from tfworker.exceptions import BackendError from tfworker.types import JSONType diff --git a/tfworker/backends/s3.py b/tfworker/backends/s3.py index 278be51..d6b7190 100644 --- a/tfworker/backends/s3.py +++ b/tfworker/backends/s3.py @@ -8,6 +8,7 @@ import botocore.errorfactory import botocore.paginate import click + import tfworker.util.log as log from tfworker.exceptions import BackendError from tfworker.types import JSONType @@ -16,6 +17,7 @@ if TYPE_CHECKING: import boto3 # pragma: no cover # noqa + from tfworker.app_state import AppState # pragma: no cover # noqa from tfworker.authenticators import ( # pragma: no cover # noqa AuthenticatorsCollection, diff --git a/tfworker/cli.py b/tfworker/cli.py index 0038a54..5ca22ae 100644 --- a/tfworker/cli.py +++ b/tfworker/cli.py @@ -1,7 +1,8 @@ #!/usr/bin/env python import click -import tfworker.util.log as log from pydantic import ValidationError + +import tfworker.util.log as log from tfworker.app_state import AppState from tfworker.cli_options import CLIOptionsClean, CLIOptionsRoot, CLIOptionsTerraform from tfworker.commands.clean import CleanCommand diff --git a/tfworker/cli_options.py b/tfworker/cli_options.py index 2d8d9ba..048b997 100644 --- a/tfworker/cli_options.py +++ b/tfworker/cli_options.py @@ -3,7 +3,6 @@ from typing import List, Optional, Union import click -import tfworker.util.log as log from pydantic import ( ConfigDict, Field, @@ -12,6 +11,8 @@ model_validator, ) from pydantic_core import InitErrorDetails + +import tfworker.util.log as log from tfworker import constants as const from tfworker.backends import Backends from tfworker.types import FreezableBaseModel diff --git a/tfworker/commands/base.py b/tfworker/commands/base.py index bcadc58..84be81d 100644 --- a/tfworker/commands/base.py +++ b/tfworker/commands/base.py @@ -1,9 +1,10 @@ from typing import TYPE_CHECKING, Any, Dict, Union import click +from pydantic import BaseModel, ValidationError + import tfworker.commands.config as c import tfworker.util.log as log -from pydantic import BaseModel, ValidationError from tfworker.exceptions import BackendError, HandlerError, TFWorkerException from tfworker.util.cli import handle_config_error diff --git a/tfworker/commands/config.py b/tfworker/commands/config.py index 615fca4..e0182c4 100644 --- a/tfworker/commands/config.py +++ b/tfworker/commands/config.py @@ -8,10 +8,11 @@ import click import hcl2 import jinja2 -import tfworker.util.log as log import yaml from jinja2.runtime import StrictUndefined from pydantic import BaseModel, ValidationError + +import tfworker.util.log as log from tfworker.app_state import AppState from tfworker.types.config_file import ConfigFile from tfworker.util.cli import handle_config_error diff --git a/tfworker/commands/root.py b/tfworker/commands/root.py index 9d67641..508fdc2 100644 --- a/tfworker/commands/root.py +++ b/tfworker/commands/root.py @@ -3,6 +3,7 @@ from typing import Any, Dict import click + import tfworker.util.log as log from tfworker.cli_options import CLIOptionsRoot diff --git a/tfworker/commands/terraform.py b/tfworker/commands/terraform.py index 053ad51..ce47e47 100644 --- a/tfworker/commands/terraform.py +++ b/tfworker/commands/terraform.py @@ -296,7 +296,7 @@ def _generate_plan_output_json(self, name) -> None: f"{self.app_state.terraform_options.terraform_bin} show -json {definition.plan_file}", cwd=working_dir, env=self.terraform_config.env, - stream_output=False + stream_output=False, ) ) diff --git a/tfworker/definitions/collection.py b/tfworker/definitions/collection.py index f468e1b..9ab425a 100644 --- a/tfworker/definitions/collection.py +++ b/tfworker/definitions/collection.py @@ -2,9 +2,10 @@ from collections.abc import Mapping from typing import Dict, List -import tfworker.util.log as log from pydantic import GetCoreSchemaHandler, ValidationError from pydantic_core import CoreSchema, core_schema + +import tfworker.util.log as log from tfworker.exceptions import FrozenInstanceError from tfworker.util.cli import handle_config_error diff --git a/tfworker/definitions/model.py b/tfworker/definitions/model.py index 8981bf5..e4fee15 100644 --- a/tfworker/definitions/model.py +++ b/tfworker/definitions/model.py @@ -1,9 +1,10 @@ from pathlib import Path from typing import Any, Dict, List, Optional, Union -import tfworker.util.log as log from pydantic import BaseModel, ConfigDict, Field +import tfworker.util.log as log + class DefinitionRemoteOptions(BaseModel): """ diff --git a/tfworker/definitions/plan.py b/tfworker/definitions/plan.py index 99cf84c..fde7eef 100644 --- a/tfworker/definitions/plan.py +++ b/tfworker/definitions/plan.py @@ -5,6 +5,7 @@ if TYPE_CHECKING: from click import Context # pragma: no cover # noqa: F401 + from tfworker.app_state import AppState # pragma: no cover # noqa: F401 from tfworker.definitions.model import Definition # pragma: no cover # noqa: F401 diff --git a/tfworker/definitions/prepare.py b/tfworker/definitions/prepare.py index d76e7e6..3deb3ea 100644 --- a/tfworker/definitions/prepare.py +++ b/tfworker/definitions/prepare.py @@ -3,6 +3,7 @@ from typing import TYPE_CHECKING, Dict, Union import jinja2 + import tfworker.util.log as log from tfworker.constants import ( RESERVED_FILES, diff --git a/tfworker/handlers/__init__.py b/tfworker/handlers/__init__.py index acb77f2..22d1aef 100644 --- a/tfworker/handlers/__init__.py +++ b/tfworker/handlers/__init__.py @@ -2,5 +2,5 @@ from .bitbucket import BitbucketConfig, BitbucketHandler # pragma: no cover # noqa from .collection import HandlersCollection # pragma: no cover # noqa from .s3 import S3Handler # pragma: no cover # noqa +from .snyk import SnykConfig, SnykHandler # pragma: no cover # noqa from .trivy import TrivyConfig, TrivyHandler # pragma: no cover # noqa -from .snyk import SnykConfig, SnykHandler # pragma: no cover # noqa diff --git a/tfworker/handlers/bitbucket.py b/tfworker/handlers/bitbucket.py index 8b7af09..a339233 100644 --- a/tfworker/handlers/bitbucket.py +++ b/tfworker/handlers/bitbucket.py @@ -3,6 +3,7 @@ from atlassian.bitbucket import Cloud from pydantic import BaseModel, Field from pydantic_settings import SettingsConfigDict + from tfworker.exceptions import HandlerError from tfworker.types.terraform import TerraformAction, TerraformStage diff --git a/tfworker/handlers/registry.py b/tfworker/handlers/registry.py index 3cf9885..f8174c9 100644 --- a/tfworker/handlers/registry.py +++ b/tfworker/handlers/registry.py @@ -1,6 +1,7 @@ from typing import Callable, Dict, List from pydantic import BaseModel + from tfworker.exceptions import HandlerError from .base import BaseHandler diff --git a/tfworker/handlers/s3.py b/tfworker/handlers/s3.py index 5aaebfb..27dda70 100644 --- a/tfworker/handlers/s3.py +++ b/tfworker/handlers/s3.py @@ -7,6 +7,7 @@ import boto3 import botocore import click + import tfworker.util.log as log from tfworker.backends import Backends from tfworker.exceptions import HandlerError diff --git a/tfworker/handlers/snyk.py b/tfworker/handlers/snyk.py index d05d6ff..2c4791c 100644 --- a/tfworker/handlers/snyk.py +++ b/tfworker/handlers/snyk.py @@ -1,6 +1,7 @@ import os from pathlib import Path from typing import TYPE_CHECKING, Union + from pydantic import BaseModel import tfworker.util.log as log @@ -15,6 +16,7 @@ from tfworker.commands.terraform import TerraformResult from tfworker.definitions.model import Definition + class SnykConfig(BaseModel): args: dict = {} cache_dir: str = "/tmp/snyk_cache" @@ -31,6 +33,7 @@ class SnykConfig(BaseModel): stream_output: bool = True exempt_definitions: list = [] + @HandlerRegistry.register("snyk") class SnykHandler(BaseHandler): """ @@ -80,19 +83,19 @@ def execute( None """ # pre plan; snyk scan the generated definition source - if (action == TerraformAction.PLAN and stage == TerraformStage.PRE): + if action == TerraformAction.PLAN and stage == TerraformStage.PRE: definition_path = definition.get_target_path(working_dir=working_dir) if definition_path is None: raise HandlerError( "definition_path is not provided, can't scan", terminate=self._required, ) - + definition_name = definition.name if self._skip_definition or definition_name in self._exempt_definitions: log.info(f"Skipping snyk scan of definition: {definition_name}") return None - + log.info(f"scanning definition with snyk: {definition_path}") self._scan(definition, Path(definition_path)) @@ -111,12 +114,12 @@ def execute( if self._skip_planfile: log.info(f"Skipping snyk scan of planfile: {planfile}") return None - + definition_name = definition.name if self._skip_definition or definition_name in self._exempt_definitions: log.info(f"Skipping snyk scan of definition: {definition_name}") return None - + jsonfile = Path(planfile).with_suffix(".tfplan.json") log.info(f"scanning planfile with snyk: {jsonfile}") self._scan(definition, Path(jsonfile)) @@ -154,7 +157,7 @@ def _build_snyk_args(self, target_path): snyk_args.append("test") snyk_args.append(str(Path(target_path).resolve())) return snyk_args - + def _handle_results(self, exit_code, stdout, stderr, definition): """Handle the results of the snyk scan diff --git a/tfworker/handlers/trivy.py b/tfworker/handlers/trivy.py index 101a169..171a599 100644 --- a/tfworker/handlers/trivy.py +++ b/tfworker/handlers/trivy.py @@ -2,8 +2,9 @@ from pathlib import Path from typing import TYPE_CHECKING, Union -import tfworker.util.log as log from pydantic import BaseModel + +import tfworker.util.log as log from tfworker.exceptions import HandlerError from tfworker.types.terraform import TerraformAction, TerraformStage diff --git a/tfworker/providers/collection.py b/tfworker/providers/collection.py index 0d579ca..d78de9b 100644 --- a/tfworker/providers/collection.py +++ b/tfworker/providers/collection.py @@ -3,9 +3,10 @@ from collections.abc import Mapping from typing import TYPE_CHECKING, Dict, List -import tfworker.util.log as log from pydantic import GetCoreSchemaHandler, ValidationError from pydantic_core import CoreSchema, core_schema + +import tfworker.util.log as log from tfworker.exceptions import FrozenInstanceError if TYPE_CHECKING: diff --git a/tfworker/providers/model.py b/tfworker/providers/model.py index d2288ac..c191d34 100644 --- a/tfworker/providers/model.py +++ b/tfworker/providers/model.py @@ -1,6 +1,7 @@ from typing import Any, Dict, Optional from pydantic import BaseModel, ConfigDict, model_validator + from tfworker.constants import ( TF_PROVIDER_DEFAULT_HOSTNAME, TF_PROVIDER_DEFAULT_NAMESPACE, diff --git a/tfworker/types/freezable_basemodel.py b/tfworker/types/freezable_basemodel.py index ca6343f..cca1f06 100644 --- a/tfworker/types/freezable_basemodel.py +++ b/tfworker/types/freezable_basemodel.py @@ -1,4 +1,5 @@ from pydantic import BaseModel, PrivateAttr + from tfworker.exceptions import FrozenInstanceError diff --git a/tfworker/util/cli.py b/tfworker/util/cli.py index 9cb42de..9aa5df2 100644 --- a/tfworker/util/cli.py +++ b/tfworker/util/cli.py @@ -2,9 +2,10 @@ from enum import Enum import click -import tfworker.util.log as log from pydantic import BaseModel, ValidationError from pydantic.fields import PydanticUndefined + +import tfworker.util.log as log from tfworker.util.system import get_platform diff --git a/tfworker/util/log.py b/tfworker/util/log.py index 43b12de..bacc4a0 100644 --- a/tfworker/util/log.py +++ b/tfworker/util/log.py @@ -4,6 +4,7 @@ from typing import Any, Dict, List, Union from click import secho + from tfworker.constants import REDACTED_ITEMS @@ -29,7 +30,6 @@ def log( Args: msg () """ - global log_level level_colors = { log_level.TRACE: "cyan", log_level.DEBUG: "blue", diff --git a/tfworker/util/terraform.py b/tfworker/util/terraform.py index cbe916d..1dfe219 100644 --- a/tfworker/util/terraform.py +++ b/tfworker/util/terraform.py @@ -6,6 +6,7 @@ from typing import Dict, List, Union import click + import tfworker.util.log as log import tfworker.util.terraform_helpers as tfhelpers from tfworker.constants import ( diff --git a/tfworker/util/terraform_helpers.py b/tfworker/util/terraform_helpers.py index 628a9d5..da08545 100644 --- a/tfworker/util/terraform_helpers.py +++ b/tfworker/util/terraform_helpers.py @@ -5,9 +5,10 @@ from typing import TYPE_CHECKING, Dict, List import hcl2 -import tfworker.util.log as log from lark.exceptions import UnexpectedToken from packaging.specifiers import InvalidSpecifier, SpecifierSet + +import tfworker.util.log as log from tfworker.exceptions import TFWorkerException from tfworker.util.system import get_platform