From c041769bf635e04bb883f17bed4efc359ce07cde Mon Sep 17 00:00:00 2001 From: Huon Wilson Date: Mon, 6 Mar 2023 08:21:59 +1100 Subject: [PATCH 01/49] Basics working --- .../bin/_generate_all_lockfiles_helper.py | 3 + build-support/bin/generate_docs.py | 1 + build-support/bin/rust/bootstrap_code.sh | 2 +- pants.toml | 4 + .../backend/experimental/tools/semgrep/BUILD | 3 + .../experimental/tools/semgrep/register.py | 25 + src/python/pants/backend/tools/semgrep/BUILD | 6 + .../pants/backend/tools/semgrep/__init__.py | 0 .../pants/backend/tools/semgrep/rules.py | 109 ++ .../pants/backend/tools/semgrep/semgrep.lock | 1613 +++++++++++++++++ .../pants/backend/tools/semgrep/subsystem.py | 91 + src/python/pants/bin/BUILD | 1 + src/python/pants/core/goals/fix.py | 9 +- src/python/pants/core/goals/lint.py | 6 +- 14 files changed, 1869 insertions(+), 4 deletions(-) create mode 100644 src/python/pants/backend/experimental/tools/semgrep/BUILD create mode 100644 src/python/pants/backend/experimental/tools/semgrep/register.py create mode 100644 src/python/pants/backend/tools/semgrep/BUILD create mode 100644 src/python/pants/backend/tools/semgrep/__init__.py create mode 100644 src/python/pants/backend/tools/semgrep/rules.py create mode 100644 src/python/pants/backend/tools/semgrep/semgrep.lock create mode 100644 src/python/pants/backend/tools/semgrep/subsystem.py diff --git a/build-support/bin/_generate_all_lockfiles_helper.py b/build-support/bin/_generate_all_lockfiles_helper.py index 3fff91d6dcb..8fccf3c1da1 100644 --- a/build-support/bin/_generate_all_lockfiles_helper.py +++ b/build-support/bin/_generate_all_lockfiles_helper.py @@ -48,6 +48,7 @@ from pants.backend.scala.subsystems.scalatest import Scalatest from pants.backend.terraform.dependency_inference import TerraformHcl2Parser from pants.backend.tools.yamllint.subsystem import Yamllint +from pants.backend.tools.semgrep.subsystem import Semgrep from pants.jvm.resolve.jvm_tool import JvmToolBase from pants.util.strutil import softwrap @@ -124,6 +125,7 @@ def jvm(cls, tool: type[JvmToolBase], *, backend: str | None = None) -> DefaultT DefaultTool.python(Pylint, backend="pants.backend.python.lint.pylint", source_plugins=True), DefaultTool.python(PythonProtobufMypyPlugin, backend="pants.backend.codegen.protobuf.python"), DefaultTool.python(PyOxidizer), + DefaultTool.python(Semgrep, backend="pants.backend.experimental.tools.semgrep"), DefaultTool.python(Setuptools), DefaultTool.python(SetuptoolsSCM), DefaultTool.python(TerraformHcl2Parser, backend="pants.backend.experimental.terraform"), @@ -233,6 +235,7 @@ def main() -> None: create_parser().print_help() return args = create_parser().parse_args() + assert args.tool if args.all: update_internal_lockfiles(specified=None) diff --git a/build-support/bin/generate_docs.py b/build-support/bin/generate_docs.py index 18b3cea821e..277b3f59791 100644 --- a/build-support/bin/generate_docs.py +++ b/build-support/bin/generate_docs.py @@ -263,6 +263,7 @@ def run_pants_help_all() -> dict[str, Any]: "pants.backend.experimental.scala", "pants.backend.experimental.scala.lint.scalafmt", "pants.backend.experimental.terraform", + "pants.backend.experimental.tools.semgrep", "pants.backend.experimental.tools.yamllint", "pants.backend.google_cloud_function.python", "pants.backend.plugin_development", diff --git a/build-support/bin/rust/bootstrap_code.sh b/build-support/bin/rust/bootstrap_code.sh index b94450f3bf7..87910e275a0 100644 --- a/build-support/bin/rust/bootstrap_code.sh +++ b/build-support/bin/rust/bootstrap_code.sh @@ -39,7 +39,7 @@ readonly NATIVE_CLIENT_TARGET="${NATIVE_ROOT}/target/${MODE}/pants" function _build_native_code() { banner "Building native code..." # NB: See Cargo.toml with regard to the `extension-module` features. - "${REPO_ROOT}/cargo" build \ + "${REPO_ROOT}/cargo" build -v \ --features=extension-module \ ${MODE_FLAG} \ -p engine \ diff --git a/pants.toml b/pants.toml index fe0aeaedf75..fe47e940c44 100644 --- a/pants.toml +++ b/pants.toml @@ -34,6 +34,7 @@ backend_packages.add = [ "pants.backend.experimental.scala.debug_goals", "pants.backend.experimental.visibility", "pants.backend.tools.preamble", + "pants.backend.experimental.tools.semgrep", "pants.explorer.server", "internal_plugins.releases", "internal_plugins.test_lockfile_fixtures", @@ -311,3 +312,6 @@ master = "src/python/pants/notes/master.rst" "2.15.x" = "src/python/pants/notes/2.15.x.md" "2.16.x" = "src/python/pants/notes/2.16.x.md" "2.17.x" = "src/python/pants/notes/2.17.x.md" + +[semgrep] +include = ["**/*.py"] diff --git a/src/python/pants/backend/experimental/tools/semgrep/BUILD b/src/python/pants/backend/experimental/tools/semgrep/BUILD new file mode 100644 index 00000000000..752b6d575e9 --- /dev/null +++ b/src/python/pants/backend/experimental/tools/semgrep/BUILD @@ -0,0 +1,3 @@ +# Copyright 2023 Pants project contributors (see CONTRIBUTORS.md). +# Licensed under the Apache License, Version 2.0 (see LICENSE). +python_sources() diff --git a/src/python/pants/backend/experimental/tools/semgrep/register.py b/src/python/pants/backend/experimental/tools/semgrep/register.py new file mode 100644 index 00000000000..bcb2fec1936 --- /dev/null +++ b/src/python/pants/backend/experimental/tools/semgrep/register.py @@ -0,0 +1,25 @@ +# Copyright 2022 Pants project contributors (see CONTRIBUTORS.md). +# Licensed under the Apache License, Version 2.0 (see LICENSE). + +"""Lightweight static analysis for many languages. Find bug variants with patterns that look like source code. + +See https://semgrep.dev/ for details. +""" + +from __future__ import annotations + +from typing import Iterable + +from pants.backend.python.goals import lockfile as python_lockfile +from pants.backend.tools.semgrep import rules as semgrep_rules +from pants.backend.tools.semgrep import subsystem as subsystem +from pants.engine.rules import Rule +from pants.engine.unions import UnionRule + + +def rules() -> Iterable[Rule | UnionRule]: + return ( + *semgrep_rules.rules(), + *subsystem.rules(), + *python_lockfile.rules(), + ) diff --git a/src/python/pants/backend/tools/semgrep/BUILD b/src/python/pants/backend/tools/semgrep/BUILD new file mode 100644 index 00000000000..37552a307bf --- /dev/null +++ b/src/python/pants/backend/tools/semgrep/BUILD @@ -0,0 +1,6 @@ +# Copyright 2023 Pants project contributors (see CONTRIBUTORS.md). +# Licensed under the Apache License, Version 2.0 (see LICENSE). + +resource(name="lockfile", source="semgrep.lock") + +python_sources(dependencies=[":lockfile"]) diff --git a/src/python/pants/backend/tools/semgrep/__init__.py b/src/python/pants/backend/tools/semgrep/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/python/pants/backend/tools/semgrep/rules.py b/src/python/pants/backend/tools/semgrep/rules.py new file mode 100644 index 00000000000..16a8490e214 --- /dev/null +++ b/src/python/pants/backend/tools/semgrep/rules.py @@ -0,0 +1,109 @@ +# Copyright 2022 Pants project contributors (see CONTRIBUTORS.md). +# Licensed under the Apache License, Version 2.0 (see LICENSE). +from __future__ import annotations + +from dataclasses import dataclass +from typing import Any, Iterable + +from pants.engine.process import FallibleProcessResult +from pants.backend.python.util_rules.pex import VenvPex, PexRequest, VenvPexProcess +from pants.core.util_rules.partitions import Partitions +from pants.core.goals.lint import LintFilesRequest, LintResult +from .subsystem import Semgrep +from pants.option.global_options import GlobalOptions +from pants.core.goals.fix import FixFilesRequest, FixResult +from pants.engine.rules import Rule +from pants.engine.unions import UnionRule +from pants.engine.rules import Get, MultiGet, collect_rules, rule +from pants.util.logging import LogLevel +from pants.core.util_rules.partitions import PartitionerType +from pants.engine.internals.native_engine import FilespecMatcher, Snapshot +from pants.engine.fs import Digest, DigestSubset, MergeDigests, PathGlobs +from pants.util.strutil import pluralize, softwrap + + +class SemgrepRequest(LintFilesRequest): + tool_subsystem = Semgrep + + partitioner_type = PartitionerType.CUSTOM + + +@dataclass(frozen=True) +class SemgrepConfigFilesRequest: + pass + + +@dataclass(frozen=True) +class SemgrepConfigFiles: + snapshot: Snapshot + + +@rule +async def gather_config_files( + request: SemgrepConfigFilesRequest, semgrep: Semgrep +) -> SemgrepConfigFiles: + config_files_snapshot = await Get(Snapshot, PathGlobs(globs=semgrep.config)) + return SemgrepConfigFiles(snapshot=config_files_snapshot) + + +@rule +async def partition(request: SemgrepRequest.PartitionRequest, semgrep: Semgrep) -> Partitions: + if semgrep.skip: + return Partitions() + + matching_files = FilespecMatcher( + includes=semgrep.file_glob_include, excludes=semgrep.file_glob_exclude + ).matches(request.files) + + return Partitions.single_partition(matching_files) + + +@rule(desc="Lint with Semgrep", level=LogLevel.DEBUG) +async def lint( + request: SemgrepRequest.Batch[str, Any], + semgrep: Semgrep, + global_options: GlobalOptions, +) -> LintResult: + config_files, semgrep_pex, input_files = await MultiGet( + Get(SemgrepConfigFiles, SemgrepConfigFilesRequest()), + Get(VenvPex, PexRequest, semgrep.to_pex_request()), + Get(Snapshot, PathGlobs(globs=request.elements)), + ) + + input_digest = await Get( + Digest, MergeDigests((input_files.digest, config_files.snapshot.digest)) + ) + + # TODO: support running this under the fix goal if with --autofix if there's rules that have + # fixes... but not all rules have fixes, so we need to be running with --error/checking exit + # codes, which FixResult doesn't currently support. + + # TODO: concurrent runs occasionally hit "Bad settings format; ... will be overriden" errors + # (https://github.com/returntocorp/semgrep/issues/7102). + result = await Get( + FallibleProcessResult, + VenvPexProcess( + semgrep_pex, + argv=( + "scan", + *(f"--config={f}" for f in config_files.snapshot.files), + "-j", + "{pants_concurrency}", + "--force-color", + "--disable-version-check", + "--error", + *semgrep.args, + *input_files.files, + ), + input_digest=input_digest, + concurrency_available=len(input_files.files), + description=f"Run Semgrep on {pluralize(len(input_files.files), 'file')}.", + level=LogLevel.DEBUG, + ), + ) + + return LintResult.create(request, result, strip_formatting=not global_options.colors) + + +def rules() -> Iterable[Rule | UnionRule]: + return [*collect_rules(), *SemgrepRequest.rules()] diff --git a/src/python/pants/backend/tools/semgrep/semgrep.lock b/src/python/pants/backend/tools/semgrep/semgrep.lock new file mode 100644 index 00000000000..628c3523b0e --- /dev/null +++ b/src/python/pants/backend/tools/semgrep/semgrep.lock @@ -0,0 +1,1613 @@ +// This lockfile was autogenerated by Pants. To regenerate, run: +// +// build-support/bin/generate_all_lockfiles.sh +// +// --- BEGIN PANTS LOCKFILE METADATA: DO NOT EDIT OR REMOVE --- +// { +// "version": 3, +// "valid_for_interpreter_constraints": [ +// "CPython<4,>=3.7" +// ], +// "generated_with_requirements": [ +// "semgrep==1.14.0" +// ], +// "manylinux": "manylinux2014", +// "requirement_constraints": [], +// "only_binary": [], +// "no_binary": [] +// } +// --- END PANTS LOCKFILE METADATA --- + +{ + "allow_builds": true, + "allow_prereleases": false, + "allow_wheels": true, + "build_isolation": true, + "constraints": [], + "locked_resolves": [ + { + "locked_requirements": [ + { + "artifacts": [ + { + "algorithm": "sha256", + "hash": "29e95c7f6778868dbd49170f98f8818f78f3dc5e0e37c0b1f474e3561b240836", + "url": "https://files.pythonhosted.org/packages/fb/6e/6f83bf616d2becdf333a1640f1d463fef3150e2e926b7010cb0f81c95e88/attrs-22.2.0-py3-none-any.whl" + }, + { + "algorithm": "sha256", + "hash": "c9227bfc2f01993c03f68db37d1d15c9690188323c067c641f1a35ca58185f99", + "url": "https://files.pythonhosted.org/packages/21/31/3f468da74c7de4fcf9b25591e682856389b3400b4b62f201e65f15ea3e07/attrs-22.2.0.tar.gz" + } + ], + "project_name": "attrs", + "requires_dists": [ + "attrs[docs,tests]; extra == \"dev\"", + "attrs[tests-no-zope]; extra == \"tests\"", + "attrs[tests]; extra == \"cov\"", + "cloudpickle; platform_python_implementation == \"CPython\" and extra == \"tests-no-zope\"", + "cloudpickle; platform_python_implementation == \"CPython\" and extra == \"tests_no_zope\"", + "coverage-enable-subprocess; extra == \"cov\"", + "coverage[toml]>=5.3; extra == \"cov\"", + "furo; extra == \"docs\"", + "hypothesis; extra == \"tests-no-zope\"", + "hypothesis; extra == \"tests_no_zope\"", + "mypy<0.990,>=0.971; platform_python_implementation == \"CPython\" and extra == \"tests-no-zope\"", + "mypy<0.990,>=0.971; platform_python_implementation == \"CPython\" and extra == \"tests_no_zope\"", + "myst-parser; extra == \"docs\"", + "pympler; extra == \"tests-no-zope\"", + "pympler; extra == \"tests_no_zope\"", + "pytest-mypy-plugins; (platform_python_implementation == \"CPython\" and python_version < \"3.11\") and extra == \"tests-no-zope\"", + "pytest-mypy-plugins; (platform_python_implementation == \"CPython\" and python_version < \"3.11\") and extra == \"tests_no_zope\"", + "pytest-xdist[psutil]; extra == \"tests-no-zope\"", + "pytest-xdist[psutil]; extra == \"tests_no_zope\"", + "pytest>=4.3.0; extra == \"tests-no-zope\"", + "pytest>=4.3.0; extra == \"tests_no_zope\"", + "sphinx-notfound-page; extra == \"docs\"", + "sphinx; extra == \"docs\"", + "sphinxcontrib-towncrier; extra == \"docs\"", + "towncrier; extra == \"docs\"", + "zope.interface; extra == \"docs\"", + "zope.interface; extra == \"tests\"" + ], + "requires_python": ">=3.6", + "version": "22.2.0" + }, + { + "artifacts": [ + { + "algorithm": "sha256", + "hash": "b9bb7b58b2b420bbe11a6025fdef6d3e5edc9f76a42fb467afe7ca212ef9948b", + "url": "https://files.pythonhosted.org/packages/f7/a7/1a31561d10a089fcb46fe286766dd4e053a12f6e23b4fd1c26478aff2475/boltons-21.0.0-py2.py3-none-any.whl" + }, + { + "algorithm": "sha256", + "hash": "65e70a79a731a7fe6e98592ecfb5ccf2115873d01dbc576079874629e5c90f13", + "url": "https://files.pythonhosted.org/packages/ad/1f/6c0608d86e0fc77c982a2923ece80eef85f091f2332fc13cbce41d70d502/boltons-21.0.0.tar.gz" + } + ], + "project_name": "boltons", + "requires_dists": [], + "requires_python": null, + "version": "21.0.0" + }, + { + "artifacts": [ + { + "algorithm": "sha256", + "hash": "351b7f20d56fb9ea91f9b9e9e7664db466eb234188c175fd943f8f755c807e73", + "url": "https://files.pythonhosted.org/packages/26/f5/7c60fb31c9aea37b3424e4206f9f6ed23c1ee0a717fa31749d10665a4eef/bracex-2.3.post1-py3-none-any.whl" + }, + { + "algorithm": "sha256", + "hash": "e7b23fc8b2cd06d3dec0692baabecb249dda94e06a617901ff03a6c56fd71693", + "url": "https://files.pythonhosted.org/packages/b3/96/d53e290ddf6215cfb24f93449a1835eff566f79a1f332cf046a978df0c9e/bracex-2.3.post1.tar.gz" + } + ], + "project_name": "bracex", + "requires_dists": [], + "requires_python": ">=3.7", + "version": "2.3.post1" + }, + { + "artifacts": [ + { + "algorithm": "sha256", + "hash": "4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18", + "url": "https://files.pythonhosted.org/packages/71/4c/3db2b8021bd6f2f0ceb0e088d6b2d49147671f25832fb17970e9b583d742/certifi-2022.12.7-py3-none-any.whl" + }, + { + "algorithm": "sha256", + "hash": "35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3", + "url": "https://files.pythonhosted.org/packages/37/f7/2b1b0ec44fdc30a3d31dfebe52226be9ddc40cd6c0f34ffc8923ba423b69/certifi-2022.12.7.tar.gz" + } + ], + "project_name": "certifi", + "requires_dists": [], + "requires_python": ">=3.6", + "version": "2022.12.7" + }, + { + "artifacts": [ + { + "algorithm": "sha256", + "hash": "7e189e2e1d3ed2f4aebabd2d5b0f931e883676e51c7624826e0a4e5fe8a0bf24", + "url": "https://files.pythonhosted.org/packages/68/2b/02e9d6a98ddb73fa238d559a9edcc30b247b8dc4ee848b6184c936e99dc0/charset_normalizer-3.0.1-py3-none-any.whl" + }, + { + "algorithm": "sha256", + "hash": "9d9153257a3f70d5f69edf2325357251ed20f772b12e593f3b3377b5f78e7ef8", + "url": "https://files.pythonhosted.org/packages/00/35/830c29e5dab61932224c7a6c89427090164a3e425cf03486ce7a3ce60623/charset_normalizer-3.0.1-cp37-cp37m-musllinux_1_1_i686.whl" + }, + { + "algorithm": "sha256", + "hash": "72966d1b297c741541ca8cf1223ff262a6febe52481af742036a0b296e35fa5a", + "url": "https://files.pythonhosted.org/packages/01/ff/9ee4a44e8c32fe96dfc12daa42f29294608a55eadc88f327939327fb20fb/charset_normalizer-3.0.1-cp311-cp311-musllinux_1_1_aarch64.whl" + }, + { + "algorithm": "sha256", + "hash": "87701167f2a5c930b403e9756fab1d31d4d4da52856143b609e30a1ce7160f3c", + "url": "https://files.pythonhosted.org/packages/02/49/78b4c1bc8b1b0e0fc66fb31ce30d8302f10a1412ba75de72c57532f0beb0/charset_normalizer-3.0.1-cp311-cp311-macosx_11_0_arm64.whl" + }, + { + "algorithm": "sha256", + "hash": "39049da0ffb96c8cbb65cbf5c5f3ca3168990adf3551bd1dee10c48fce8ae820", + "url": "https://files.pythonhosted.org/packages/03/5e/e81488c74e86eef85cf085417ed945da2dcca87ed22d76202680c16bd3c3/charset_normalizer-3.0.1-cp38-cp38-musllinux_1_1_x86_64.whl" + }, + { + "algorithm": "sha256", + "hash": "024e606be3ed92216e2b6952ed859d86b4cfa52cd5bc5f050e7dc28f9b43ec42", + "url": "https://files.pythonhosted.org/packages/0e/d3/c5fa421dc69bb77c581ed561f1ec6656109c97731ad1128aa93d8bad3053/charset_normalizer-3.0.1-cp38-cp38-macosx_10_9_universal2.whl" + }, + { + "algorithm": "sha256", + "hash": "503e65837c71b875ecdd733877d852adbc465bd82c768a067badd953bf1bc5a3", + "url": "https://files.pythonhosted.org/packages/0f/45/f462f534dd2853ebbc186ed859661db454665b1dc9ae6c690d982153cda9/charset_normalizer-3.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl" + }, + { + "algorithm": "sha256", + "hash": "0c0a590235ccd933d9892c627dec5bc7511ce6ad6c1011fdf5b11363022746c1", + "url": "https://files.pythonhosted.org/packages/12/e5/aa09a1c39c3e444dd223d63e2c816c18ed78d035cff954143b2a539bdc9e/charset_normalizer-3.0.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl" + }, + { + "algorithm": "sha256", + "hash": "3e45867f1f2ab0711d60c6c71746ac53537f1684baa699f4f668d4c6f6ce8e14", + "url": "https://files.pythonhosted.org/packages/16/bd/671f11f920dfb46de848e9176d84ddb25b3bbdffac6751cbbf691c0b5b17/charset_normalizer-3.0.1-cp37-cp37m-macosx_10_9_x86_64.whl" + }, + { + "algorithm": "sha256", + "hash": "8eade758719add78ec36dc13201483f8e9b5d940329285edcd5f70c0a9edbd7f", + "url": "https://files.pythonhosted.org/packages/17/67/4b25c0358a2e812312b551e734d58855d58f47d0e0e9d1573930003910cb/charset_normalizer-3.0.1-cp39-cp39-macosx_10_9_universal2.whl" + }, + { + "algorithm": "sha256", + "hash": "81d6741ab457d14fdedc215516665050f3822d3e56508921cc7239f8c8e66a58", + "url": "https://files.pythonhosted.org/packages/17/da/fdf8ffc33716c82cae06008159a55a581fa515e8dd02e3395dcad42ff83d/charset_normalizer-3.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl" + }, + { + "algorithm": "sha256", + "hash": "e696f0dd336161fca9adbb846875d40752e6eba585843c768935ba5c9960722b", + "url": "https://files.pythonhosted.org/packages/20/a2/16b2cbf5f73bdd10624b94647b85c008ba25059792a5c7b4fdb8358bceeb/charset_normalizer-3.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl" + }, + { + "algorithm": "sha256", + "hash": "0f438ae3532723fb6ead77e7c604be7c8374094ef4ee2c5e03a3a17f1fca256c", + "url": "https://files.pythonhosted.org/packages/25/19/298089cef2eb82fd3810d982aa239d4226594f99e1fe78494cb9b47b03c9/charset_normalizer-3.0.1-cp39-cp39-musllinux_1_1_s390x.whl" + }, + { + "algorithm": "sha256", + "hash": "911d8a40b2bef5b8bbae2e36a0b103f142ac53557ab421dc16ac4aafee6f53dc", + "url": "https://files.pythonhosted.org/packages/25/b5/f477e419b06e49f3bae446cbdc1fd71d2599be8b12b4d45c641c5a4495b1/charset_normalizer-3.0.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl" + }, + { + "algorithm": "sha256", + "hash": "44ba614de5361b3e5278e1241fda3dc1838deed864b50a10d7ce92983797fa76", + "url": "https://files.pythonhosted.org/packages/2d/02/0f875eb6a1cf347bd3a6098f458f79796aafa3b51090fd7b2784736dc67d/charset_normalizer-3.0.1-cp310-cp310-musllinux_1_1_ppc64le.whl" + }, + { + "algorithm": "sha256", + "hash": "ab5de034a886f616a5668aa5d098af2b5385ed70142090e2a31bcbd0af0fdb3d", + "url": "https://files.pythonhosted.org/packages/31/06/f6330ee70c041a032ee1a5d32785d69748cfa41f64b6d327cc08cae51de9/charset_normalizer-3.0.1-cp39-cp39-musllinux_1_1_aarch64.whl" + }, + { + "algorithm": "sha256", + "hash": "f6f45710b4459401609ebebdbcfb34515da4fc2aa886f95107f556ac69a9147e", + "url": "https://files.pythonhosted.org/packages/31/af/67b7653a35dbd56f6bb9ff54652a551eae8420d1d0545f0042c5bdb15fb0/charset_normalizer-3.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl" + }, + { + "algorithm": "sha256", + "hash": "0298eafff88c99982a4cf66ba2efa1128e4ddaca0b05eec4c456bbc7db691d8d", + "url": "https://files.pythonhosted.org/packages/37/00/ca188e0a2b3cd3184cdd2521b8765cf579327d128caa8aedc3dc7614020a/charset_normalizer-3.0.1-cp311-cp311-macosx_10_9_universal2.whl" + }, + { + "algorithm": "sha256", + "hash": "8b8af03d2e37866d023ad0ddea594edefc31e827fee64f8de5611a1dbc373174", + "url": "https://files.pythonhosted.org/packages/37/60/7a01f3a129d1af1f26ab2c56aae89a72dbf33fd46a467c1aa994ec62b90b/charset_normalizer-3.0.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl" + }, + { + "algorithm": "sha256", + "hash": "31a9ddf4718d10ae04d9b18801bd776693487cbb57d74cc3458a7673f6f34639", + "url": "https://files.pythonhosted.org/packages/55/2b/35619e03725bfa4af4a902e1996c9ee8052d6bce005ff79c9bd988820cb4/charset_normalizer-3.0.1-cp310-cp310-musllinux_1_1_i686.whl" + }, + { + "algorithm": "sha256", + "hash": "9cf4e8ad252f7c38dd1f676b46514f92dc0ebeb0db5552f5f403509705e24753", + "url": "https://files.pythonhosted.org/packages/56/5d/275fb120957dfe5a2262d04f28bc742fd4bcc2bd270d19bb8757e09737ef/charset_normalizer-3.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl" + }, + { + "algorithm": "sha256", + "hash": "11b53acf2411c3b09e6af37e4b9005cba376c872503c8f28218c7243582df45d", + "url": "https://files.pythonhosted.org/packages/5a/d8/9e76846e70e729de85ecc6af21edc584a2adfef202dc5f5ae00a02622e3d/charset_normalizer-3.0.1-cp37-cp37m-musllinux_1_1_x86_64.whl" + }, + { + "algorithm": "sha256", + "hash": "109487860ef6a328f3eec66f2bf78b0b72400280d8f8ea05f69c51644ba6521a", + "url": "https://files.pythonhosted.org/packages/5b/e7/5527effca09d873e07e128d3daac7c531203b5105cb4e2956c2b7a8cc41c/charset_normalizer-3.0.1-cp38-cp38-musllinux_1_1_aarch64.whl" + }, + { + "algorithm": "sha256", + "hash": "8499ca8f4502af841f68135133d8258f7b32a53a1d594aa98cc52013fff55678", + "url": "https://files.pythonhosted.org/packages/6a/ab/3a00ecbddabe25132c20c1bd45e6f90c537b5f7a0b5bcaba094c4922928c/charset_normalizer-3.0.1-cp39-cp39-macosx_10_9_x86_64.whl" + }, + { + "algorithm": "sha256", + "hash": "772b87914ff1152b92a197ef4ea40efe27a378606c39446ded52c8f80f79702e", + "url": "https://files.pythonhosted.org/packages/6e/d7/1d4035fcbf7d0f2e89588a142628355d8d1cd652a227acefb9ec85908cd4/charset_normalizer-3.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl" + }, + { + "algorithm": "sha256", + "hash": "16a8663d6e281208d78806dbe14ee9903715361cf81f6d4309944e4d1e59ac5b", + "url": "https://files.pythonhosted.org/packages/71/67/79be03bf7ab4198d994c2e8da869ca354487bfa25656b95cf289cf6338a2/charset_normalizer-3.0.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl" + }, + { + "algorithm": "sha256", + "hash": "4a8fcf28c05c1f6d7e177a9a46a1c52798bfe2ad80681d275b10dcf317deaf0b", + "url": "https://files.pythonhosted.org/packages/80/54/183163f9910936e57a60ee618f4f5cc91c2f8333ee2d4ebc6c50f6c8684d/charset_normalizer-3.0.1-cp311-cp311-musllinux_1_1_s390x.whl" + }, + { + "algorithm": "sha256", + "hash": "761e8904c07ad053d285670f36dd94e1b6ab7f16ce62b9805c475b7aa1cffde6", + "url": "https://files.pythonhosted.org/packages/82/49/ab81421d5aa25bc8535896a017c93204cb4051f2a4e72b1ad8f3b594e072/charset_normalizer-3.0.1-cp311-cp311-musllinux_1_1_x86_64.whl" + }, + { + "algorithm": "sha256", + "hash": "8ac7b6a045b814cf0c47f3623d21ebd88b3e8cf216a14790b455ea7ff0135d18", + "url": "https://files.pythonhosted.org/packages/84/ff/78a4942ef1ea4d1c464cc9a132122b36c5390c5cf6301ed0f9e3e6e24bd9/charset_normalizer-3.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl" + }, + { + "algorithm": "sha256", + "hash": "5995f0164fa7df59db4746112fec3f49c461dd6b31b841873443bdb077c13cfc", + "url": "https://files.pythonhosted.org/packages/86/eb/31c9025b4ed7eddd930c5f2ac269efb953de33140608c7539675d74a2081/charset_normalizer-3.0.1-cp311-cp311-musllinux_1_1_ppc64le.whl" + }, + { + "algorithm": "sha256", + "hash": "f9d0c5c045a3ca9bedfc35dca8526798eb91a07aa7a2c0fee134c6c6f321cbd7", + "url": "https://files.pythonhosted.org/packages/89/87/c237a299a658b35d19fd531eeb8247480627fc2fb4b7a471334b48850f45/charset_normalizer-3.0.1-cp311-cp311-musllinux_1_1_i686.whl" + }, + { + "algorithm": "sha256", + "hash": "a8d0fc946c784ff7f7c3742310cc8a57c5c6dc31631269876a88b809dbeff3d3", + "url": "https://files.pythonhosted.org/packages/90/59/941e2e5ae6828a688c6437ad16e026eb3606d0cfdd13ea5c9090980f3ffd/charset_normalizer-3.0.1-cp311-cp311-macosx_10_9_x86_64.whl" + }, + { + "algorithm": "sha256", + "hash": "12db3b2c533c23ab812c2b25934f60383361f8a376ae272665f8e48b88e8e1c6", + "url": "https://files.pythonhosted.org/packages/92/00/b8dc8dd725297b05f1ab4929c9d7e879f31746131534221c5c8948bc7563/charset_normalizer-3.0.1-cp310-cp310-musllinux_1_1_s390x.whl" + }, + { + "algorithm": "sha256", + "hash": "02a51034802cbf38db3f89c66fb5d2ec57e6fe7ef2f4a44d070a593c3688667b", + "url": "https://files.pythonhosted.org/packages/93/d1/569445a704414e150f198737c245ab96b40d28d5b68045a62c414a5157de/charset_normalizer-3.0.1-cp37-cp37m-musllinux_1_1_ppc64le.whl" + }, + { + "algorithm": "sha256", + "hash": "ebea339af930f8ca5d7a699b921106c6e29c617fe9606fa7baa043c1cdae326f", + "url": "https://files.pythonhosted.org/packages/96/d7/1675d9089a1f4677df5eb29c3f8b064aa1e70c1251a0a8a127803158942d/charset-normalizer-3.0.1.tar.gz" + }, + { + "algorithm": "sha256", + "hash": "c512accbd6ff0270939b9ac214b84fb5ada5f0409c44298361b2f5e13f9aed9e", + "url": "https://files.pythonhosted.org/packages/98/e4/d4685870fda1cc7c5e29899ec329500460418e54f4f5df76ee520e30689a/charset_normalizer-3.0.1-cp310-cp310-musllinux_1_1_x86_64.whl" + }, + { + "algorithm": "sha256", + "hash": "3b590df687e3c5ee0deef9fc8c547d81986d9a1b56073d82de008744452d6541", + "url": "https://files.pythonhosted.org/packages/99/24/eb846dc9a797da58e6e5b3b5a71d3ff17264de3f424fb29aaa5d27173b55/charset_normalizer-3.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl" + }, + { + "algorithm": "sha256", + "hash": "4b0d02d7102dd0f997580b51edc4cebcf2ab6397a7edf89f1c73b586c614272c", + "url": "https://files.pythonhosted.org/packages/9c/42/c1ebc736c57459aab28bfb8aa28c6a047796f2ea46050a3b129b4920dbe4/charset_normalizer-3.0.1-cp38-cp38-macosx_10_9_x86_64.whl" + }, + { + "algorithm": "sha256", + "hash": "2edb64ee7bf1ed524a1da60cdcd2e1f6e2b4f66ef7c077680739f1641f62f555", + "url": "https://files.pythonhosted.org/packages/9f/5a/9dc8932d1e5f8eeaa502e3c3fce91c86be20c04eb3ec202d2b7d74b567e5/charset_normalizer-3.0.1-cp310-cp310-musllinux_1_1_aarch64.whl" + }, + { + "algorithm": "sha256", + "hash": "a16418ecf1329f71df119e8a65f3aa68004a3f9383821edcb20f0702934d8087", + "url": "https://files.pythonhosted.org/packages/a2/93/0b1aa4dbc0ae2aa2e1b2e6d037ab8984dc09912d6b26d63ced14da07e3a7/charset_normalizer-3.0.1-cp37-cp37m-musllinux_1_1_aarch64.whl" + }, + { + "algorithm": "sha256", + "hash": "358a7c4cb8ba9b46c453b1dd8d9e431452d5249072e4f56cfda3149f6ab1405e", + "url": "https://files.pythonhosted.org/packages/a2/a7/adc963ad8f8fddadd6be088e636972705ec9d1d92d1b45e6119eb02b7e9e/charset_normalizer-3.0.1-cp38-cp38-macosx_11_0_arm64.whl" + }, + { + "algorithm": "sha256", + "hash": "2e396d70bc4ef5325b72b593a72c8979999aa52fb8bcf03f701c1b03e1166918", + "url": "https://files.pythonhosted.org/packages/a3/09/a837b27b122e710dfad15b0b5df04cd0623c8d8d3382e4298f50798fb84a/charset_normalizer-3.0.1-cp37-cp37m-musllinux_1_1_s390x.whl" + }, + { + "algorithm": "sha256", + "hash": "62595ab75873d50d57323a91dd03e6966eb79c41fa834b7a1661ed043b2d404d", + "url": "https://files.pythonhosted.org/packages/a3/4b/f565c852163312a0991c30598f403fd06796a12e408d7839cc46ca8d7f4a/charset_normalizer-3.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl" + }, + { + "algorithm": "sha256", + "hash": "f97e83fa6c25693c7a35de154681fcc257c1c41b38beb0304b9c4d2d9e164479", + "url": "https://files.pythonhosted.org/packages/aa/a4/2d6255d4db5d4558a92458fd8dacddfdda2fb4ad9c0a87db6f6034aded34/charset_normalizer-3.0.1-cp38-cp38-musllinux_1_1_ppc64le.whl" + }, + { + "algorithm": "sha256", + "hash": "70990b9c51340e4044cfc394a81f614f3f90d41397104d226f21e66de668730d", + "url": "https://files.pythonhosted.org/packages/af/63/2c00ff4e657fb9bb76306ffbc7878fd52067e39716f5e8b0dd5582caf1fa/charset_normalizer-3.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl" + }, + { + "algorithm": "sha256", + "hash": "88600c72ef7587fe1708fd242b385b6ed4b8904976d5da0893e31df8b3480cb6", + "url": "https://files.pythonhosted.org/packages/b2/4c/9a4f30042bfee22d34d80daf75f51817cdd23180d718e0160aab235c4faf/charset_normalizer-3.0.1-cp310-cp310-macosx_10_9_universal2.whl" + }, + { + "algorithm": "sha256", + "hash": "00d3ffdaafe92a5dc603cb9bd5111aaa36dfa187c8285c543be562e61b755f6b", + "url": "https://files.pythonhosted.org/packages/b5/1a/932d86fde86bb0d2992c74552c9a422883fe0890132bbc9a5e2211f03318/charset_normalizer-3.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl" + }, + { + "algorithm": "sha256", + "hash": "14e76c0f23218b8f46c4d87018ca2e441535aed3632ca134b10239dfb6dadd6b", + "url": "https://files.pythonhosted.org/packages/c0/4d/6b82099e3f25a9ed87431e2f51156c14f3a9ce8fad73880a3856cd95f1d5/charset_normalizer-3.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl" + }, + { + "algorithm": "sha256", + "hash": "292d5e8ba896bbfd6334b096e34bffb56161c81408d6d036a7dfa6929cff8783", + "url": "https://files.pythonhosted.org/packages/c1/06/b7b1d3d186e0f288500b8a1161ede6b38a0abbf878c2033d667e815e6bd7/charset_normalizer-3.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl" + }, + { + "algorithm": "sha256", + "hash": "a60332922359f920193b1d4826953c507a877b523b2395ad7bc716ddd386d866", + "url": "https://files.pythonhosted.org/packages/c1/b2/d81606aebeb7e9a33dc877ff3a206c9946f5bb374c99d22d4a28825aa270/charset_normalizer-3.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl" + }, + { + "algorithm": "sha256", + "hash": "9cb3032517f1627cc012dbc80a8ec976ae76d93ea2b5feaa9d2a5b8882597579", + "url": "https://files.pythonhosted.org/packages/c4/d4/94f1ea460cce04483d2460efba6fd4d66e6f60ad6fc6075dba13e3501e48/charset_normalizer-3.0.1-cp39-cp39-musllinux_1_1_i686.whl" + }, + { + "algorithm": "sha256", + "hash": "c22d3fe05ce11d3671297dc8973267daa0f938b93ec716e12e0f6dee81591dc1", + "url": "https://files.pythonhosted.org/packages/c8/a2/8f873138c99423de3b402daf8ccd7a538632c83d0c129444a6a18ef34e03/charset_normalizer-3.0.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl" + }, + { + "algorithm": "sha256", + "hash": "608862a7bf6957f2333fc54ab4399e405baad0163dc9f8d99cb236816db169d4", + "url": "https://files.pythonhosted.org/packages/c9/dd/80a5e8c080b7e1cc2b0ca35f0d6aeedafd7bbd06d25031ac20868b1366d6/charset_normalizer-3.0.1-cp39-cp39-musllinux_1_1_ppc64le.whl" + }, + { + "algorithm": "sha256", + "hash": "ff6f3db31555657f3163b15a6b7c6938d08df7adbfc9dd13d9d19edad678f1e8", + "url": "https://files.pythonhosted.org/packages/d3/5b/4031145fcfb9ceaf49dad2fbf9a44e062eb2c08aff36f71d8aafbecf4567/charset_normalizer-3.0.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl" + }, + { + "algorithm": "sha256", + "hash": "79909e27e8e4fcc9db4addea88aa63f6423ebb171db091fb4373e3312cb6d603", + "url": "https://files.pythonhosted.org/packages/d9/7a/60d45c9453212b30eebbf8b5cddbdef330eebddfcf335bce7920c43fb72e/charset_normalizer-3.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl" + }, + { + "algorithm": "sha256", + "hash": "c2ac1b08635a8cd4e0cbeaf6f5e922085908d48eb05d44c5ae9eabab148512ca", + "url": "https://files.pythonhosted.org/packages/dc/ff/2c7655d83b1d6d6a0e132d50d54131fcb8da763b417ccc6c4a506aa0e08c/charset_normalizer-3.0.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl" + }, + { + "algorithm": "sha256", + "hash": "cadaeaba78750d58d3cc6ac4d1fd867da6fc73c88156b7a3212a3cd4819d679d", + "url": "https://files.pythonhosted.org/packages/df/2f/4806e155191f75e720aca98a969581c6b2676f0379dd315c34c388bbf8b5/charset_normalizer-3.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl" + }, + { + "algorithm": "sha256", + "hash": "8c7fe7afa480e3e82eed58e0ca89f751cd14d767638e2550c77a92a9e749c317", + "url": "https://files.pythonhosted.org/packages/df/c5/dd3a17a615775d0ffc3e12b0e47833d8b7e0a4871431dad87a3f92382a19/charset_normalizer-3.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl" + }, + { + "algorithm": "sha256", + "hash": "c75ffc45f25324e68ab238cb4b5c0a38cd1c3d7f1fb1f72b5541de469e2247db", + "url": "https://files.pythonhosted.org/packages/e1/7f/64b51f144fa9e74da63fa690d9563eae627f4df6cc6ae5185a781e1912e5/charset_normalizer-3.0.1-cp310-cp310-macosx_10_9_x86_64.whl" + }, + { + "algorithm": "sha256", + "hash": "3ae1de54a77dc0d6d5fcf623290af4266412a7c4be0b1ff7444394f03f5c54e3", + "url": "https://files.pythonhosted.org/packages/e3/96/8cdbce165c96cce5f2c9c7748f7ed8e0cf0c5d03e213bbc90b7c3e918bf5/charset_normalizer-3.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl" + }, + { + "algorithm": "sha256", + "hash": "37f8febc8ec50c14f3ec9637505f28e58d4f66752207ea177c1d67df25da5aed", + "url": "https://files.pythonhosted.org/packages/e8/80/141f6af05332cbb811ab469f64deb1e1d4cc9e8b0c003aa8a38d689ce84a/charset_normalizer-3.0.1-cp38-cp38-musllinux_1_1_i686.whl" + }, + { + "algorithm": "sha256", + "hash": "db72b07027db150f468fbada4d85b3b2729a3db39178abf5c543b784c1254539", + "url": "https://files.pythonhosted.org/packages/f0/78/30d853a3073c866b47abede6d86b5532aa99ac67a95e86077d20be1ce481/charset_normalizer-3.0.1-cp310-cp310-macosx_11_0_arm64.whl" + }, + { + "algorithm": "sha256", + "hash": "a152f5f33d64a6be73f1d30c9cc82dfc73cec6477ec268e7c6e4c7d23c2d2291", + "url": "https://files.pythonhosted.org/packages/f1/ff/9a1c65d8c44958f45ae40cd558ab63bd499a35198a2014e13c0887c07ed1/charset_normalizer-3.0.1-cp38-cp38-musllinux_1_1_s390x.whl" + }, + { + "algorithm": "sha256", + "hash": "3fc1c4a2ffd64890aebdb3f97e1278b0cc72579a08ca4de8cd2c04799a3a22be", + "url": "https://files.pythonhosted.org/packages/f5/84/cac681144a28114bd9e40d3cdbfd961c14ecc2b56f1baec2094afd6744c7/charset_normalizer-3.0.1-cp39-cp39-macosx_11_0_arm64.whl" + }, + { + "algorithm": "sha256", + "hash": "356541bf4381fa35856dafa6a965916e54bed415ad8a24ee6de6e37deccf2786", + "url": "https://files.pythonhosted.org/packages/f5/ec/a9bed59079bd0267d34ada58a4048c96a59b3621e7f586ea85840d41831d/charset_normalizer-3.0.1-cp39-cp39-musllinux_1_1_x86_64.whl" + } + ], + "project_name": "charset-normalizer", + "requires_dists": [], + "requires_python": null, + "version": "3.0.1" + }, + { + "artifacts": [ + { + "algorithm": "sha256", + "hash": "bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48", + "url": "https://files.pythonhosted.org/packages/c2/f1/df59e28c642d583f7dacffb1e0965d0e00b218e0186d7858ac5233dce840/click-8.1.3-py3-none-any.whl" + }, + { + "algorithm": "sha256", + "hash": "7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e", + "url": "https://files.pythonhosted.org/packages/59/87/84326af34517fca8c58418d148f2403df25303e02736832403587318e9e8/click-8.1.3.tar.gz" + } + ], + "project_name": "click", + "requires_dists": [ + "colorama; platform_system == \"Windows\"", + "importlib-metadata; python_version < \"3.8\"" + ], + "requires_python": ">=3.7", + "version": "8.1.3" + }, + { + "artifacts": [ + { + "algorithm": "sha256", + "hash": "0f8ca79bc9b1d6fcaafdbe194b17ba1a2dde44ddf19087235c3efed2ad288143", + "url": "https://files.pythonhosted.org/packages/cf/b2/808e028b944a1f7c21005205762ee88654c40b73b9de2a04e18384d1c9cd/click_option_group-0.5.5-py3-none-any.whl" + }, + { + "algorithm": "sha256", + "hash": "78ee474f07a0ca0ef6c0317bb3ebe79387aafb0c4a1e03b1d8b2b0be1e42fc78", + "url": "https://files.pythonhosted.org/packages/4c/29/ff7cd69825b5bfb48e39853b75d5dc2e98a581730f2b6c9c014188730755/click-option-group-0.5.5.tar.gz" + } + ], + "project_name": "click-option-group", + "requires_dists": [ + "Click<9,>=7.0", + "Pallets-Sphinx-Themes; extra == \"docs\"", + "coverage<6; extra == \"tests_cov\"", + "coveralls; extra == \"tests_cov\"", + "m2r2; extra == \"docs\"", + "pytest-cov; extra == \"tests_cov\"", + "pytest; extra == \"tests\"", + "pytest; extra == \"tests_cov\"", + "sphinx<6,>=3.0; extra == \"docs\"" + ], + "requires_python": "<4,>=3.6", + "version": "0.5.5" + }, + { + "artifacts": [ + { + "algorithm": "sha256", + "hash": "4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", + "url": "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl" + }, + { + "algorithm": "sha256", + "hash": "08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", + "url": "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz" + } + ], + "project_name": "colorama", + "requires_dists": [], + "requires_python": "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7", + "version": "0.4.6" + }, + { + "artifacts": [ + { + "algorithm": "sha256", + "hash": "a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61", + "url": "https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl" + }, + { + "algorithm": "sha256", + "hash": "1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69", + "url": "https://files.pythonhosted.org/packages/0f/d5/c66da9b79e5bdb124974bfe172b4daf3c984ebd9c2a06e2b8a4dc7331c72/defusedxml-0.7.1.tar.gz" + } + ], + "project_name": "defusedxml", + "requires_dists": [], + "requires_python": "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7", + "version": "0.7.1" + }, + { + "artifacts": [ + { + "algorithm": "sha256", + "hash": "344fe31562d0f6f444a45982418f3793d4b14f9abb98ccca1509d22e0a3e7e35", + "url": "https://files.pythonhosted.org/packages/1e/4e/ba2bdec6e50ddf012fcda6d06083fcaee5798b73fb872d2b72988144ed3d/face-22.0.0-py3-none-any.whl" + }, + { + "algorithm": "sha256", + "hash": "d5d692f90bc8f5987b636e47e36384b9bbda499aaf0a77aa0b0bbe834c76923d", + "url": "https://files.pythonhosted.org/packages/d7/bc/4d0f6c1e095eb977782edd94245f84b69c6f8df152480c78ab310e895098/face-22.0.0.tar.gz" + } + ], + "project_name": "face", + "requires_dists": [ + "boltons>=20.0.0" + ], + "requires_python": null, + "version": "22.0.0" + }, + { + "artifacts": [ + { + "algorithm": "sha256", + "hash": "5339da206bf3532e01a83a35aca202960ea885156986d190574b779598e9e772", + "url": "https://files.pythonhosted.org/packages/27/e8/68e274b2a30e1fdfd25bdc27194382be3f233929c8f727c0440d58ac074f/glom-22.1.0-py2.py3-none-any.whl" + }, + { + "algorithm": "sha256", + "hash": "1510c6587a8f9c64a246641b70033cbc5ebde99f02ad245693678038e821aeb5", + "url": "https://files.pythonhosted.org/packages/3f/d1/69432deefa6f5283ec75b246d0540097ae26f618b915519ee3824c4c5dd6/glom-22.1.0.tar.gz" + } + ], + "project_name": "glom", + "requires_dists": [ + "PyYAML; extra == \"yaml\"", + "attrs", + "boltons>=19.3.0", + "face>=20.1.0" + ], + "requires_python": null, + "version": "22.1.0" + }, + { + "artifacts": [ + { + "algorithm": "sha256", + "hash": "90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2", + "url": "https://files.pythonhosted.org/packages/fc/34/3030de6f1370931b9dbb4dad48f6ab1015ab1d32447850b9fc94e60097be/idna-3.4-py3-none-any.whl" + }, + { + "algorithm": "sha256", + "hash": "814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4", + "url": "https://files.pythonhosted.org/packages/8b/e1/43beb3d38dba6cb420cefa297822eac205a277ab43e5ba5d5c46faf96438/idna-3.4.tar.gz" + } + ], + "project_name": "idna", + "requires_dists": [], + "requires_python": ">=3.5", + "version": "3.4" + }, + { + "artifacts": [ + { + "algorithm": "sha256", + "hash": "7efb448ec9a5e313a57655d35aa54cd3e01b7e1fbcf72dce1bf06119420f5bad", + "url": "https://files.pythonhosted.org/packages/26/a7/9da7d5b23fc98ab3d424ac2c65613d63c1f401efb84ad50f2fa27b2caab4/importlib_metadata-6.0.0-py3-none-any.whl" + }, + { + "algorithm": "sha256", + "hash": "e354bedeb60efa6affdcc8ae121b73544a7aa74156d047311948f6d711cd378d", + "url": "https://files.pythonhosted.org/packages/90/07/6397ad02d31bddf1841c9ad3ec30a693a3ff208e09c2ef45c9a8a5f85156/importlib_metadata-6.0.0.tar.gz" + } + ], + "project_name": "importlib-metadata", + "requires_dists": [ + "flake8<5; extra == \"testing\"", + "flufl.flake8; extra == \"testing\"", + "furo; extra == \"docs\"", + "importlib-resources>=1.3; python_version < \"3.9\" and extra == \"testing\"", + "ipython; extra == \"perf\"", + "jaraco.packaging>=9; extra == \"docs\"", + "jaraco.tidelift>=1.4; extra == \"docs\"", + "packaging; extra == \"testing\"", + "pyfakefs; extra == \"testing\"", + "pytest-black>=0.3.7; platform_python_implementation != \"PyPy\" and extra == \"testing\"", + "pytest-checkdocs>=2.4; extra == \"testing\"", + "pytest-cov; extra == \"testing\"", + "pytest-enabler>=1.3; extra == \"testing\"", + "pytest-flake8; python_version < \"3.12\" and extra == \"testing\"", + "pytest-mypy>=0.9.1; platform_python_implementation != \"PyPy\" and extra == \"testing\"", + "pytest-perf>=0.9.2; extra == \"testing\"", + "pytest>=6; extra == \"testing\"", + "rst.linker>=1.9; extra == \"docs\"", + "sphinx-lint; extra == \"docs\"", + "sphinx>=3.5; extra == \"docs\"", + "typing-extensions>=3.6.4; python_version < \"3.8\"", + "zipp>=0.5" + ], + "requires_python": ">=3.7", + "version": "6.0.0" + }, + { + "artifacts": [ + { + "algorithm": "sha256", + "hash": "7b1deeebbf351c7578e09bf2f63fa2ce8b5ffec296e0d349139d43cca061a81a", + "url": "https://files.pythonhosted.org/packages/38/71/c13ea695a4393639830bf96baea956538ba7a9d06fcce7cef10bfff20f72/importlib_resources-5.12.0-py3-none-any.whl" + }, + { + "algorithm": "sha256", + "hash": "4be82589bf5c1d7999aedf2a45159d10cb3ca4f19b2271f8792bc8e6da7b22f6", + "url": "https://files.pythonhosted.org/packages/4e/a2/3cab1de83f95dd15297c15bdc04d50902391d707247cada1f021bbfe2149/importlib_resources-5.12.0.tar.gz" + } + ], + "project_name": "importlib-resources", + "requires_dists": [ + "flake8<5; extra == \"testing\"", + "furo; extra == \"docs\"", + "jaraco.packaging>=9; extra == \"docs\"", + "jaraco.tidelift>=1.4; extra == \"docs\"", + "pytest-black>=0.3.7; platform_python_implementation != \"PyPy\" and extra == \"testing\"", + "pytest-checkdocs>=2.4; extra == \"testing\"", + "pytest-cov; extra == \"testing\"", + "pytest-enabler>=1.3; extra == \"testing\"", + "pytest-flake8; python_version < \"3.12\" and extra == \"testing\"", + "pytest-mypy>=0.9.1; platform_python_implementation != \"PyPy\" and extra == \"testing\"", + "pytest>=6; extra == \"testing\"", + "rst.linker>=1.9; extra == \"docs\"", + "sphinx-lint; extra == \"docs\"", + "sphinx>=3.5; extra == \"docs\"", + "zipp>=3.1.0; python_version < \"3.10\"" + ], + "requires_python": ">=3.7", + "version": "5.12.0" + }, + { + "artifacts": [ + { + "algorithm": "sha256", + "hash": "a870ad254da1a8ca84b6a2905cac29d265f805acc57af304784962a2aa6508f6", + "url": "https://files.pythonhosted.org/packages/c1/97/c698bd9350f307daad79dd740806e1a59becd693bd11443a0f531e3229b3/jsonschema-4.17.3-py3-none-any.whl" + }, + { + "algorithm": "sha256", + "hash": "0f864437ab8b6076ba6707453ef8f98a6a0d512a80e93f8abdb676f737ecb60d", + "url": "https://files.pythonhosted.org/packages/36/3d/ca032d5ac064dff543aa13c984737795ac81abc9fb130cd2fcff17cfabc7/jsonschema-4.17.3.tar.gz" + } + ], + "project_name": "jsonschema", + "requires_dists": [ + "attrs>=17.4.0", + "fqdn; extra == \"format\"", + "fqdn; extra == \"format-nongpl\"", + "idna; extra == \"format\"", + "idna; extra == \"format-nongpl\"", + "importlib-metadata; python_version < \"3.8\"", + "importlib-resources>=1.4.0; python_version < \"3.9\"", + "isoduration; extra == \"format\"", + "isoduration; extra == \"format-nongpl\"", + "jsonpointer>1.13; extra == \"format\"", + "jsonpointer>1.13; extra == \"format-nongpl\"", + "pkgutil-resolve-name>=1.3.10; python_version < \"3.9\"", + "pyrsistent!=0.17.0,!=0.17.1,!=0.17.2,>=0.14.0", + "rfc3339-validator; extra == \"format\"", + "rfc3339-validator; extra == \"format-nongpl\"", + "rfc3986-validator>0.1.0; extra == \"format-nongpl\"", + "rfc3987; extra == \"format\"", + "typing-extensions; python_version < \"3.8\"", + "uri-template; extra == \"format\"", + "uri-template; extra == \"format-nongpl\"", + "webcolors>=1.11; extra == \"format\"", + "webcolors>=1.11; extra == \"format-nongpl\"" + ], + "requires_python": ">=3.7", + "version": "4.17.3" + }, + { + "artifacts": [ + { + "algorithm": "sha256", + "hash": "5a35f8d1870171d9acc47b99612dc146129b631baf04970128b568f190d0cc30", + "url": "https://files.pythonhosted.org/packages/bf/25/2d88e8feee8e055d015343f9b86e370a1ccbec546f2865c98397aaef24af/markdown_it_py-2.2.0-py3-none-any.whl" + }, + { + "algorithm": "sha256", + "hash": "7c9a5e412688bc771c67432cbfebcdd686c93ce6484913dccf06cb5a0bea35a1", + "url": "https://files.pythonhosted.org/packages/e4/c0/59bd6d0571986f72899288a95d9d6178d0eebd70b6650f1bb3f0da90f8f7/markdown-it-py-2.2.0.tar.gz" + } + ], + "project_name": "markdown-it-py", + "requires_dists": [ + "attrs; extra == \"rtd\"", + "commonmark~=0.9; extra == \"compare\"", + "coverage; extra == \"testing\"", + "gprof2dot; extra == \"profiling\"", + "linkify-it-py<3,>=1; extra == \"linkify\"", + "markdown~=3.4; extra == \"compare\"", + "mdit-py-plugins; extra == \"plugins\"", + "mdurl~=0.1", + "mistletoe~=1.0; extra == \"compare\"", + "mistune~=2.0; extra == \"compare\"", + "myst-parser; extra == \"rtd\"", + "panflute~=2.3; extra == \"compare\"", + "pre-commit~=3.0; extra == \"code_style\"", + "psutil; extra == \"benchmarking\"", + "pytest-benchmark; extra == \"benchmarking\"", + "pytest-cov; extra == \"testing\"", + "pytest-regressions; extra == \"testing\"", + "pytest; extra == \"benchmarking\"", + "pytest; extra == \"testing\"", + "pyyaml; extra == \"rtd\"", + "sphinx-copybutton; extra == \"rtd\"", + "sphinx-design; extra == \"rtd\"", + "sphinx; extra == \"rtd\"", + "sphinx_book_theme; extra == \"rtd\"", + "typing_extensions>=3.7.4; python_version < \"3.8\"" + ], + "requires_python": ">=3.7", + "version": "2.2.0" + }, + { + "artifacts": [ + { + "algorithm": "sha256", + "hash": "84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", + "url": "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl" + }, + { + "algorithm": "sha256", + "hash": "bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", + "url": "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz" + } + ], + "project_name": "mdurl", + "requires_dists": [], + "requires_python": ">=3.7", + "version": "0.1.2" + }, + { + "artifacts": [ + { + "algorithm": "sha256", + "hash": "714ac14496c3e68c99c29b00845f7a2b85f3bb6f1078fd9f72fd20f0570002b2", + "url": "https://files.pythonhosted.org/packages/ed/35/a31aed2993e398f6b09a790a181a7927eb14610ee8bbf02dc14d31677f1c/packaging-23.0-py3-none-any.whl" + }, + { + "algorithm": "sha256", + "hash": "b6ad297f8907de0fa2fe1ccbd26fdaf387f5f47c7275fedf8cce89f99446cf97", + "url": "https://files.pythonhosted.org/packages/47/d5/aca8ff6f49aa5565df1c826e7bf5e85a6df852ee063600c1efa5b932968c/packaging-23.0.tar.gz" + } + ], + "project_name": "packaging", + "requires_dists": [], + "requires_python": ">=3.7", + "version": "23.0" + }, + { + "artifacts": [ + { + "algorithm": "sha256", + "hash": "1800c0a04962ee99d161c07f5a12fc49549caf5cfcda426a9103e34e37f854ba", + "url": "https://files.pythonhosted.org/packages/68/a9/3f23e0d8a05e9ec4f6180dc01ec0e73e207e526161ee74647eb1488b613d/peewee-3.16.0.tar.gz" + } + ], + "project_name": "peewee", + "requires_dists": [], + "requires_python": null, + "version": "3.16.0" + }, + { + "artifacts": [ + { + "algorithm": "sha256", + "hash": "ca27cc078d25c5ad71a9de0a7a330146c4e014c2462d9af19c6b828280649c5e", + "url": "https://files.pythonhosted.org/packages/c9/5c/3d4882ba113fd55bdba9326c1e4c62a15e674a2501de4869e6bd6301f87e/pkgutil_resolve_name-1.3.10-py3-none-any.whl" + }, + { + "algorithm": "sha256", + "hash": "357d6c9e6a755653cfd78893817c0853af365dd51ec97f3d358a819373bbd174", + "url": "https://files.pythonhosted.org/packages/70/f2/f2891a9dc37398696ddd945012b90ef8d0a034f0012e3f83c3f7a70b0f79/pkgutil_resolve_name-1.3.10.tar.gz" + } + ], + "project_name": "pkgutil-resolve-name", + "requires_dists": [], + "requires_python": ">=3.6", + "version": "1.3.10" + }, + { + "artifacts": [ + { + "algorithm": "sha256", + "hash": "fa7bd7bd2771287c0de303af8bfdfc731f51bd2c6a47ab69d117138893b82717", + "url": "https://files.pythonhosted.org/packages/0b/42/d9d95cc461f098f204cd20c85642ae40fbff81f74c300341b8d0e0df14e0/Pygments-2.14.0-py3-none-any.whl" + }, + { + "algorithm": "sha256", + "hash": "b3ed06a9e8ac9a9aae5a6f5dbe78a8a58655d17b43b93c078f094ddc476ae297", + "url": "https://files.pythonhosted.org/packages/da/6a/c427c06913204e24de28de5300d3f0e809933f376e0b7df95194b2bb3f71/Pygments-2.14.0.tar.gz" + } + ], + "project_name": "pygments", + "requires_dists": [ + "importlib-metadata; python_version < \"3.8\" and extra == \"plugins\"" + ], + "requires_python": ">=3.6", + "version": "2.14.0" + }, + { + "artifacts": [ + { + "algorithm": "sha256", + "hash": "ccf0d6bd208f8111179f0c26fdf84ed7c3891982f2edaeae7422575f47e66b64", + "url": "https://files.pythonhosted.org/packages/64/de/375aa14daaee107f987da76ca32f7a907fea00fa8b8afb67dc09bec0de91/pyrsistent-0.19.3-py3-none-any.whl" + }, + { + "algorithm": "sha256", + "hash": "cc5d149f31706762c1f8bda2e8c4f8fead6e80312e3692619a75301d3dbb819a", + "url": "https://files.pythonhosted.org/packages/07/d2/0e72806d668c001d13885e8d7c78fefa5a649c34ad9d77b90eb472096ae7/pyrsistent-0.19.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl" + }, + { + "algorithm": "sha256", + "hash": "aeda827381f5e5d65cced3024126529ddc4289d944f75e090572c77ceb19adbf", + "url": "https://files.pythonhosted.org/packages/0b/c0/5ba658ab88966a5a709e17739d1da02615b95e8210d52041d147f11da5da/pyrsistent-0.19.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl" + }, + { + "algorithm": "sha256", + "hash": "4c18264cb84b5e68e7085a43723f9e4c1fd1d935ab240ce02c0324a8e01ccb64", + "url": "https://files.pythonhosted.org/packages/40/04/f1d7813d4cdb62ed58e75b53e2ef481b47081ab5ad2a104cd284fa507042/pyrsistent-0.19.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl" + }, + { + "algorithm": "sha256", + "hash": "a2471f3f8693101975b1ff85ffd19bb7ca7dd7c38f8a81701f67d6b4f97b87d8", + "url": "https://files.pythonhosted.org/packages/59/4b/b6ea0f5c564c40f2c9d05ad3dbe3b8db6a6f1e7153e49eee29674c3c3bbe/pyrsistent-0.19.3-cp38-cp38-macosx_10_9_universal2.whl" + }, + { + "algorithm": "sha256", + "hash": "3ab2204234c0ecd8b9368dbd6a53e83c3d4f3cab10ecaf6d0e772f456c442393", + "url": "https://files.pythonhosted.org/packages/64/bd/b108e1a288a63871be1cf062176dcd5be922c748f843f316440104a45df3/pyrsistent-0.19.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl" + }, + { + "algorithm": "sha256", + "hash": "4b774f9288dda8d425adb6544e5903f1fb6c273ab3128a355c6b972b7df39dcf", + "url": "https://files.pythonhosted.org/packages/73/55/1e300772f5c24921a81fc1c8b3de8a06a199c4ebb523d7c5a85f4e74a32e/pyrsistent-0.19.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl" + }, + { + "algorithm": "sha256", + "hash": "3311cb4237a341aa52ab8448c27e3a9931e2ee09561ad150ba94e4cfd3fc888c", + "url": "https://files.pythonhosted.org/packages/82/5e/037a808341e4464c702eb45e741c69292516d0ac00e64080269a2e98d12d/pyrsistent-0.19.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl" + }, + { + "algorithm": "sha256", + "hash": "42ac0b2f44607eb92ae88609eda931a4f0dfa03038c44c772e07f43e738bcac9", + "url": "https://files.pythonhosted.org/packages/86/0e/33b4cde936d247024c26772dae0a7c93d650d8ec7ee1824d2752d3d8883c/pyrsistent-0.19.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl" + }, + { + "algorithm": "sha256", + "hash": "e42296a09e83028b3476f7073fcb69ffebac0e66dbbfd1bd847d61f74db30f19", + "url": "https://files.pythonhosted.org/packages/86/f2/fda71652a6baa0147891296a99b4145572538417609c164450beebcf8ebc/pyrsistent-0.19.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl" + }, + { + "algorithm": "sha256", + "hash": "99abb85579e2165bd8522f0c0138864da97847875ecbd45f3e7e2af569bfc6f2", + "url": "https://files.pythonhosted.org/packages/af/3e/7c94e58ade258179c2e13fb254f040830e97654d76dee8288200d30d575d/pyrsistent-0.19.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl" + }, + { + "algorithm": "sha256", + "hash": "f0774bf48631f3a20471dd7c5989657b639fd2d285b861237ea9e82c36a415a9", + "url": "https://files.pythonhosted.org/packages/b1/46/3f9cfa75c46b8a55d3a235456bc129a26431a65e4922fc9af66aa4e2db7e/pyrsistent-0.19.3-cp311-cp311-macosx_10_9_universal2.whl" + }, + { + "algorithm": "sha256", + "hash": "1a2994773706bbb4995c31a97bc94f1418314923bd1048c6d964837040376440", + "url": "https://files.pythonhosted.org/packages/bf/90/445a7dbd275c654c268f47fa9452152709134f61f09605cf776407055a89/pyrsistent-0.19.3.tar.gz" + }, + { + "algorithm": "sha256", + "hash": "b735e538f74ec31378f5a1e3886a26d2ca6351106b4dfde376a26fc32a044edc", + "url": "https://files.pythonhosted.org/packages/d5/bf/6ed2d861e3e94c5e92dbb1399eef672fb6add6e824d8c0f4b55d9cd9e733/pyrsistent-0.19.3-cp39-cp39-macosx_10_9_universal2.whl" + }, + { + "algorithm": "sha256", + "hash": "c4db1bd596fefd66b296a3d5d943c94f4fac5bcd13e99bffe2ba6a759d959a28", + "url": "https://files.pythonhosted.org/packages/dc/c2/994b3e91f22b040fefbb3058d8622e3b45ab78dd1256599575bf36319b6d/pyrsistent-0.19.3-cp37-cp37m-macosx_10_9_x86_64.whl" + }, + { + "algorithm": "sha256", + "hash": "20460ac0ea439a3e79caa1dbd560344b64ed75e85d8703943e0b66c2a6150e4a", + "url": "https://files.pythonhosted.org/packages/ed/7b/7d032130a6838b179b46dff1ee88909c11d518a10ec9bc70c4b72c7c2f80/pyrsistent-0.19.3-cp310-cp310-macosx_10_9_universal2.whl" + }, + { + "algorithm": "sha256", + "hash": "3a8cb235fa6d3fd7aae6a4f1429bbb1fec1577d978098da1252f0489937786f3", + "url": "https://files.pythonhosted.org/packages/f4/43/183384edb4d2788374aa7663b82ace4afe4a0c1fbfee064875eb40ada95b/pyrsistent-0.19.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl" + } + ], + "project_name": "pyrsistent", + "requires_dists": [], + "requires_python": ">=3.7", + "version": "0.19.3" + }, + { + "artifacts": [ + { + "algorithm": "sha256", + "hash": "079b143be64b0a378bdb21dff5e28a8c1393fe7e8a654ef068322d754e545fc7", + "url": "https://files.pythonhosted.org/packages/06/ee/754bfd5f6bfe7162c10d3ecb0aeef6f882f91d3231596c83f761a75efd0b/python_lsp_jsonrpc-1.0.0-py3-none-any.whl" + }, + { + "algorithm": "sha256", + "hash": "7bec170733db628d3506ea3a5288ff76aa33c70215ed223abdb0d95e957660bd", + "url": "https://files.pythonhosted.org/packages/99/45/1c2a272950679af529f7360af6ee567ef266f282e451be926329e8d50d84/python-lsp-jsonrpc-1.0.0.tar.gz" + } + ], + "project_name": "python-lsp-jsonrpc", + "requires_dists": [ + "coverage; extra == \"test\"", + "pycodestyle; extra == \"test\"", + "pyflakes; extra == \"test\"", + "pylint; extra == \"test\"", + "pytest-cov; extra == \"test\"", + "pytest; extra == \"test\"", + "ujson>=3.0.0" + ], + "requires_python": null, + "version": "1.0.0" + }, + { + "artifacts": [ + { + "algorithm": "sha256", + "hash": "64299f4909223da747622c030b781c0d7811e359c37124b4bd368fb8c6518baa", + "url": "https://files.pythonhosted.org/packages/d2/f4/274d1dbe96b41cf4e0efb70cbced278ffd61b5c7bb70338b62af94ccb25b/requests-2.28.2-py3-none-any.whl" + }, + { + "algorithm": "sha256", + "hash": "98b1b2782e3c6c4904938b84c0eb932721069dfdb9134313beff7c83c2df24bf", + "url": "https://files.pythonhosted.org/packages/9d/ee/391076f5937f0a8cdf5e53b701ffc91753e87b07d66bae4a09aa671897bf/requests-2.28.2.tar.gz" + } + ], + "project_name": "requests", + "requires_dists": [ + "PySocks!=1.5.7,>=1.5.6; extra == \"socks\"", + "certifi>=2017.4.17", + "chardet<6,>=3.0.2; extra == \"use_chardet_on_py3\"", + "charset-normalizer<4,>=2", + "idna<4,>=2.5", + "urllib3<1.27,>=1.21.1" + ], + "requires_python": "<4,>=3.7", + "version": "2.28.2" + }, + { + "artifacts": [ + { + "algorithm": "sha256", + "hash": "8aa57747f3fc3e977684f0176a88e789be314a99f99b43b75d1e9cb5dc6db9e9", + "url": "https://files.pythonhosted.org/packages/a8/c6/14b77fe7a5fab66ffbeffd6706f598d00a52702846bce0e2339bcf9dd20c/rich-13.3.1-py3-none-any.whl" + }, + { + "algorithm": "sha256", + "hash": "125d96d20c92b946b983d0d392b84ff945461e5a06d3867e9f9e575f8697b67f", + "url": "https://files.pythonhosted.org/packages/68/31/b8934896818c885001aeb7df388ba0523ea3ec88ad31805983d9b0480a50/rich-13.3.1.tar.gz" + } + ], + "project_name": "rich", + "requires_dists": [ + "ipywidgets<9,>=7.5.1; extra == \"jupyter\"", + "markdown-it-py<3.0.0,>=2.1.0", + "pygments<3.0.0,>=2.14.0", + "typing-extensions<5.0,>=4.0.0; python_version < \"3.9\"" + ], + "requires_python": ">=3.7.0", + "version": "13.3.1" + }, + { + "artifacts": [ + { + "algorithm": "sha256", + "hash": "742b35d3d665023981bd6d16b3d24248ce5df75fdb4e2924e93a05c1f8b61ca7", + "url": "https://files.pythonhosted.org/packages/9e/cb/938214ac358fbef7058343b3765c79a1b7ed0c366f7f992ce7ff38335652/ruamel.yaml-0.17.21-py3-none-any.whl" + }, + { + "algorithm": "sha256", + "hash": "8b7ce697a2f212752a35c1ac414471dc16c424c9573be4926b56ff3f5d23b7af", + "url": "https://files.pythonhosted.org/packages/46/a9/6ed24832095b692a8cecc323230ce2ec3480015fbfa4b79941bd41b23a3c/ruamel.yaml-0.17.21.tar.gz" + } + ], + "project_name": "ruamel-yaml", + "requires_dists": [ + "ruamel.yaml.clib>=0.2.6; platform_python_implementation == \"CPython\" and python_version < \"3.11\"", + "ruamel.yaml.jinja2>=0.2; extra == \"jinja2\"", + "ryd; extra == \"docs\"" + ], + "requires_python": ">=3", + "version": "0.17.21" + }, + { + "artifacts": [ + { + "algorithm": "sha256", + "hash": "a7b301ff08055d73223058b5c46c55638917f04d21577c95e00e0c4d79201a6b", + "url": "https://files.pythonhosted.org/packages/35/bc/a1f58a339ffe891d92492f47b1dfa90b0957ccf8ad11f35f653a1a6b8c4e/ruamel.yaml.clib-0.2.7-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl" + }, + { + "algorithm": "sha256", + "hash": "41d0f1fa4c6830176eef5b276af04c89320ea616655d01327d5ce65e50575c94", + "url": "https://files.pythonhosted.org/packages/11/50/b4ebeac05e40ab40d85659ec8629f9af54ed09f07b3269e17cbfb0fbecd6/ruamel.yaml.clib-0.2.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl" + }, + { + "algorithm": "sha256", + "hash": "2aa261c29a5545adfef9296b7e33941f46aa5bbd21164228e833412af4c9c75f", + "url": "https://files.pythonhosted.org/packages/2e/37/8b463d153586e4c4ac7db54dc36bf7b6f5ce431b5352f9d226e93316abf5/ruamel.yaml.clib-0.2.7-cp37-cp37m-macosx_10_9_x86_64.whl" + }, + { + "algorithm": "sha256", + "hash": "c3ca1fbba4ae962521e5eb66d72998b51f0f4d0f608d3c0347a48e1af262efa7", + "url": "https://files.pythonhosted.org/packages/38/d9/12fd19480b081d0930b82fe15ed1f9e400aa06530b9f722149bb2580a913/ruamel.yaml.clib-0.2.7-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl" + }, + { + "algorithm": "sha256", + "hash": "efa08d63ef03d079dcae1dfe334f6c8847ba8b645d08df286358b1f5293d24ab", + "url": "https://files.pythonhosted.org/packages/51/9d/f6189b21e8669a7c8b693b86cbf235db7de4229ea006d9085bbe5c05eea8/ruamel.yaml.clib-0.2.7-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl" + }, + { + "algorithm": "sha256", + "hash": "40d030e2329ce5286d6b231b8726959ebbe0404c92f0a578c0e2482182e38282", + "url": "https://files.pythonhosted.org/packages/5a/e0/793aa6e5271aadebbb0d22d98c509aae00a0148ceb6ebbd11c137d8b2fbf/ruamel.yaml.clib-0.2.7-cp37-cp37m-manylinux2014_aarch64.whl" + }, + { + "algorithm": "sha256", + "hash": "df5828871e6648db72d1c19b4bd24819b80a755c4541d3409f0f7acd0f335c80", + "url": "https://files.pythonhosted.org/packages/6a/49/66eab405fbf2d086fc616de095a54deedccc970b0f2ff632e77362f3e009/ruamel.yaml.clib-0.2.7-cp310-cp310-manylinux2014_aarch64.whl" + }, + { + "algorithm": "sha256", + "hash": "045e0626baf1c52e5527bd5db361bc83180faaba2ff586e763d3d5982a876a9e", + "url": "https://files.pythonhosted.org/packages/85/09/a8a80a745ffd3d69a8ccc807e22ebe69670f62c7cc5b88d0a4321e0e800c/ruamel.yaml.clib-0.2.7-cp311-cp311-macosx_10_9_universal2.whl" + }, + { + "algorithm": "sha256", + "hash": "99e77daab5d13a48a4054803d052ff40780278240a902b880dd37a51ba01a307", + "url": "https://files.pythonhosted.org/packages/87/a3/38e62187deea524f008f3b7d0b42b0aaa98b1788c47367c6412b172e5cc7/ruamel.yaml.clib-0.2.7-cp38-cp38-macosx_12_0_arm64.whl" + }, + { + "algorithm": "sha256", + "hash": "d5859983f26d8cd7bb5c287ef452e8aacc86501487634573d260968f753e1d71", + "url": "https://files.pythonhosted.org/packages/a0/a6/e4b98ce7e3d4534e690ec8b01a2ed674dc31ca9aaae0c259c7afc0828cb7/ruamel.yaml.clib-0.2.7-cp310-cp310-macosx_10_9_universal2.whl" + }, + { + "algorithm": "sha256", + "hash": "8831a2cedcd0f0927f788c5bdf6567d9dc9cc235646a434986a852af1cb54b4b", + "url": "https://files.pythonhosted.org/packages/aa/53/e963164dcd2e2b0d4ecfd12972c1eaa000a8376e63544adeb0fee2f6f90b/ruamel.yaml.clib-0.2.7-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl" + }, + { + "algorithm": "sha256", + "hash": "debc87a9516b237d0466a711b18b6ebeb17ba9f391eb7f91c649c5c4ec5006c7", + "url": "https://files.pythonhosted.org/packages/b3/43/e5cc1451acaccb765810715af835da560299afb244444105aaadf599a9dd/ruamel.yaml.clib-0.2.7-cp310-cp310-macosx_12_0_arm64.whl" + }, + { + "algorithm": "sha256", + "hash": "3243f48ecd450eddadc2d11b5feb08aca941b5cd98c9b1db14b2fd128be8c697", + "url": "https://files.pythonhosted.org/packages/cd/b9/ee26013ba5cf86454c7e62336b39b7760d71b255546f50c955a86be54c07/ruamel.yaml.clib-0.2.7-cp38-cp38-manylinux2014_aarch64.whl" + }, + { + "algorithm": "sha256", + "hash": "4a4d8d417868d68b979076a9be6a38c676eca060785abaa6709c7b31593c35d1", + "url": "https://files.pythonhosted.org/packages/d2/3e/179eeeabcbbfe173ecd2bb3f958e22011c0a2c18f75674a75f62928f55eb/ruamel.yaml.clib-0.2.7-cp39-cp39-macosx_12_0_arm64.whl" + }, + { + "algorithm": "sha256", + "hash": "1f08fd5a2bea9c4180db71678e850b995d2a5f4537be0e94557668cf0f5f9497", + "url": "https://files.pythonhosted.org/packages/d5/31/a3e6411947eb7a4f1c669f887e9e47d61a68f9d117f10c3c620296694a0b/ruamel.yaml.clib-0.2.7.tar.gz" + }, + { + "algorithm": "sha256", + "hash": "91a789b4aa0097b78c93e3dc4b40040ba55bef518f84a40d4442f713b4094acb", + "url": "https://files.pythonhosted.org/packages/d6/b0/4b7cab1c2ac7bfb31283bc9d00e6e05a118aff1d0c81776215cfc96810ba/ruamel.yaml.clib-0.2.7-cp38-cp38-macosx_10_9_x86_64.whl" + }, + { + "algorithm": "sha256", + "hash": "5bc0667c1eb8f83a3752b71b9c4ba55ef7c7058ae57022dd9b29065186a113d9", + "url": "https://files.pythonhosted.org/packages/f5/ac/dda2d23d652bc2f6db886496ad632957af82e33d22c1088cc0ac87c496b5/ruamel.yaml.clib-0.2.7-cp39-cp39-macosx_10_9_x86_64.whl" + }, + { + "algorithm": "sha256", + "hash": "f01da5790e95815eb5a8a138508c01c758e5f5bc0ce4286c4f7028b8dd7ac3d0", + "url": "https://files.pythonhosted.org/packages/fb/c0/de69d49a6d0a346fb27ddf3114d807380b08a40d8e22e0fbaf19be8b6044/ruamel.yaml.clib-0.2.7-cp37-cp37m-macosx_12_0_arm64.whl" + }, + { + "algorithm": "sha256", + "hash": "721bc4ba4525f53f6a611ec0967bdcee61b31df5a56801281027a3a6d1c2daf5", + "url": "https://files.pythonhosted.org/packages/fc/47/a8e865b6097969e162c2e9efcfed8ded0582ea18305b09426dd648a6b2d4/ruamel.yaml.clib-0.2.7-cp311-cp311-macosx_12_6_arm64.whl" + }, + { + "algorithm": "sha256", + "hash": "bf9a6bc4a0221538b1a7de3ed7bca4c93c02346853f44e1cd764be0023cd3640", + "url": "https://files.pythonhosted.org/packages/ff/66/4c05485243e24c6db5d7305063304c410b5539577becc89e4539d2897e41/ruamel.yaml.clib-0.2.7-cp39-cp39-manylinux2014_aarch64.whl" + } + ], + "project_name": "ruamel-yaml-clib", + "requires_dists": [], + "requires_python": ">=3.6", + "version": "0.2.7" + }, + { + "artifacts": [ + { + "algorithm": "sha256", + "hash": "349adfa431f2f4358f786536f43210d974e6563b9d9794c6398f742314f86700", + "url": "https://files.pythonhosted.org/packages/75/a7/242c85f80ab18c74182c55a2f94d7ba893f2b35a799749a039936580917c/semgrep-1.14.0-cp37.cp38.cp39.py37.py38.py39-none-macosx_11_0_arm64.whl" + }, + { + "algorithm": "sha256", + "hash": "db1214c64644c79bfc84ba41c2d692706ee129be97fa476f99369cbc8fb8f47d", + "url": "https://files.pythonhosted.org/packages/14/34/6ae356e002d48203e875fd71bd401339a7357c9a27667c579b963e1003b2/semgrep-1.14.0-cp37.cp38.cp39.py37.py38.py39-none-any.whl" + }, + { + "algorithm": "sha256", + "hash": "ab917d3c490cbe4345d61325980314df729b1d6b25b2508818b3865e6c56ae81", + "url": "https://files.pythonhosted.org/packages/87/61/34e2bdd32ef2b87262f267b62c2be20f8f1515af91ece737475a6875a6d8/semgrep-1.14.0-cp37.cp38.cp39.py37.py38.py39-none-macosx_10_14_x86_64.whl" + }, + { + "algorithm": "sha256", + "hash": "a788b425d893381b7cd78e21d5e7776cc0cc95b25645b9b0721a87e7f2580f8d", + "url": "https://files.pythonhosted.org/packages/92/15/c812e25602475768ce8a02785d53725b77d8c53ab263a1b21940dfdaf080/semgrep-1.14.0.tar.gz" + } + ], + "project_name": "semgrep", + "requires_dists": [ + "attrs>=21.3", + "boltons~=21.0", + "click-option-group~=0.5", + "click~=8.1", + "colorama~=0.4.0", + "defusedxml~=0.7.1", + "glom~=22.1", + "jsonnet~=0.18; extra == \"experiments\"", + "jsonschema~=4.6", + "packaging>=21.0", + "peewee~=3.14", + "python-lsp-jsonrpc~=1.0.0", + "requests~=2.22", + "rich>=12.6.0", + "ruamel.yaml<0.18,>=0.16.0", + "tomli~=2.0.1", + "typing-extensions~=4.2", + "urllib3~=1.26", + "wcmatch~=8.3" + ], + "requires_python": ">=3.7", + "version": "1.14.0" + }, + { + "artifacts": [ + { + "algorithm": "sha256", + "hash": "939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc", + "url": "https://files.pythonhosted.org/packages/97/75/10a9ebee3fd790d20926a90a2547f0bf78f371b2f13aa822c759680ca7b9/tomli-2.0.1-py3-none-any.whl" + }, + { + "algorithm": "sha256", + "hash": "de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f", + "url": "https://files.pythonhosted.org/packages/c0/3f/d7af728f075fb08564c5949a9c95e44352e23dee646869fa104a3b2060a3/tomli-2.0.1.tar.gz" + } + ], + "project_name": "tomli", + "requires_dists": [], + "requires_python": ">=3.7", + "version": "2.0.1" + }, + { + "artifacts": [ + { + "algorithm": "sha256", + "hash": "fb33085c39dd998ac16d1431ebc293a8b3eedd00fd4a32de0ff79002c19511b4", + "url": "https://files.pythonhosted.org/packages/31/25/5abcd82372d3d4a3932e1fa8c3dbf9efac10cc7c0d16e78467460571b404/typing_extensions-4.5.0-py3-none-any.whl" + }, + { + "algorithm": "sha256", + "hash": "5cb5f4a79139d699607b3ef622a1dedafa84e115ab0024e0d9c044a9479ca7cb", + "url": "https://files.pythonhosted.org/packages/d3/20/06270dac7316220643c32ae61694e451c98f8caf4c8eab3aa80a2bedf0df/typing_extensions-4.5.0.tar.gz" + } + ], + "project_name": "typing-extensions", + "requires_dists": [], + "requires_python": ">=3.7", + "version": "4.5.0" + }, + { + "artifacts": [ + { + "algorithm": "sha256", + "hash": "ea7423d8a2f9e160c5e011119741682414c5b8dce4ae56590a966316a07a4618", + "url": "https://files.pythonhosted.org/packages/b0/9b/7ae752c8f1e2e7bf261c4d5ded14a7e8dd6878350d130c78a74a833f37ac/ujson-5.7.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl" + }, + { + "algorithm": "sha256", + "hash": "581c945b811a3d67c27566539bfcb9705ea09cb27c4be0002f7a553c8886b817", + "url": "https://files.pythonhosted.org/packages/00/8c/ef2884d41cdeb0324c69be5acf4367282e34c0c80b7c255ac6955203b4a8/ujson-5.7.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl" + }, + { + "algorithm": "sha256", + "hash": "5eba5e69e4361ac3a311cf44fa71bc619361b6e0626768a494771aacd1c2f09b", + "url": "https://files.pythonhosted.org/packages/01/ac/d06d6361ffb641cda6ffd16c763a76eed07abb073c49a41fbf007c764242/ujson-5.7.0-cp310-cp310-macosx_10_9_x86_64.whl" + }, + { + "algorithm": "sha256", + "hash": "75204a1dd7ec6158c8db85a2f14a68d2143503f4bafb9a00b63fe09d35762a5e", + "url": "https://files.pythonhosted.org/packages/02/5f/bef7d57cd7dba6c3124ce2c42c215e2194f51835c2e9176e2833ea04e15c/ujson-5.7.0-cp38-cp38-macosx_10_9_x86_64.whl" + }, + { + "algorithm": "sha256", + "hash": "a5d2f44331cf04689eafac7a6596c71d6657967c07ac700b0ae1c921178645da", + "url": "https://files.pythonhosted.org/packages/08/47/41f40896aad1a098b4fea2e0bfe66a3fed8305d2457945f7082b7f493307/ujson-5.7.0-cp37-cp37m-musllinux_1_1_i686.whl" + }, + { + "algorithm": "sha256", + "hash": "b01a9af52a0d5c46b2c68e3f258fdef2eacaa0ce6ae3e9eb97983f5b1166edb6", + "url": "https://files.pythonhosted.org/packages/18/19/2754b8d50affbf4456f31af5a75a1904d40499e89facdb742496b0a9c8c7/ujson-5.7.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl" + }, + { + "algorithm": "sha256", + "hash": "800bf998e78dae655008dd10b22ca8dc93bdcfcc82f620d754a411592da4bbf2", + "url": "https://files.pythonhosted.org/packages/21/0b/9fd1a3dc94175d8cf141c3356776346e1b5fca10571441fc370fbf560e1c/ujson-5.7.0-cp39-cp39-musllinux_1_1_i686.whl" + }, + { + "algorithm": "sha256", + "hash": "e87cec407ec004cf1b04c0ed7219a68c12860123dfb8902ef880d3d87a71c172", + "url": "https://files.pythonhosted.org/packages/22/27/81b6b0537fbc6ff0baaeb175738ee7464d643ad5ff30105e03a9e744682d/ujson-5.7.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl" + }, + { + "algorithm": "sha256", + "hash": "54384ce4920a6d35fa9ea8e580bc6d359e3eb961fa7e43f46c78e3ed162d56ff", + "url": "https://files.pythonhosted.org/packages/23/46/874217a97b822d0d9c072fe49db362ce1ece8e912ea6422a3f12fa5e56e1/ujson-5.7.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl" + }, + { + "algorithm": "sha256", + "hash": "6e80f0d03e7e8646fc3d79ed2d875cebd4c83846e129737fdc4c2532dbd43d9e", + "url": "https://files.pythonhosted.org/packages/29/5f/52a99a8ae0ae74213f1994a8180afd32b4d0cde427e2610f6e1ffb0fa15c/ujson-5.7.0-cp310-cp310-musllinux_1_1_i686.whl" + }, + { + "algorithm": "sha256", + "hash": "bab10165db6a7994e67001733f7f2caf3400b3e11538409d8756bc9b1c64f7e8", + "url": "https://files.pythonhosted.org/packages/2c/fe/855ee750936e9d065e6e49f7340571bd2db756fbcaf338c00456d39dd217/ujson-5.7.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl" + }, + { + "algorithm": "sha256", + "hash": "c0d1f7c3908357ee100aa64c4d1cf91edf99c40ac0069422a4fd5fd23b263263", + "url": "https://files.pythonhosted.org/packages/2e/4a/e802a5f22e0fffdeaceb3d139c79ab7995f118c2fadb8cdb129a7fd83c8d/ujson-5.7.0-cp37-cp37m-musllinux_1_1_aarch64.whl" + }, + { + "algorithm": "sha256", + "hash": "341f891d45dd3814d31764626c55d7ab3fd21af61fbc99d070e9c10c1190680b", + "url": "https://files.pythonhosted.org/packages/30/c3/adb327b07e554f9c14f05df79bbad914532054f31303bb0716744354fe51/ujson-5.7.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl" + }, + { + "algorithm": "sha256", + "hash": "14f9082669f90e18e64792b3fd0bf19f2b15e7fe467534a35ea4b53f3bf4b755", + "url": "https://files.pythonhosted.org/packages/31/5c/c8b9e14583aeaf473596c3861edf20c0c3d850e00873858f8279c6e884f5/ujson-5.7.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl" + }, + { + "algorithm": "sha256", + "hash": "4a3d794afbf134df3056a813e5c8a935208cddeae975bd4bc0ef7e89c52f0ce0", + "url": "https://files.pythonhosted.org/packages/34/ad/98c4bd2cfe2d04330bc7d6b7e3dee5b52b7358430b1cf4973ca25b7413c3/ujson-5.7.0-cp39-cp39-musllinux_1_1_aarch64.whl" + }, + { + "algorithm": "sha256", + "hash": "18679484e3bf9926342b1c43a3bd640f93a9eeeba19ef3d21993af7b0c44785d", + "url": "https://files.pythonhosted.org/packages/37/34/017f0904417617d2af2a30021f0b494535e63cb4a343dc32b05d9f0e96dd/ujson-5.7.0-cp37-cp37m-macosx_10_9_x86_64.whl" + }, + { + "algorithm": "sha256", + "hash": "64772a53f3c4b6122ed930ae145184ebaed38534c60f3d859d8c3f00911eb122", + "url": "https://files.pythonhosted.org/packages/3b/bd/a7ad5d56a4a9491487bd658cda12c2a7a0d5a41c9943086471e6cfa73854/ujson-5.7.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl" + }, + { + "algorithm": "sha256", + "hash": "e788e5d5dcae8f6118ac9b45d0b891a0d55f7ac480eddcb7f07263f2bcf37b23", + "url": "https://files.pythonhosted.org/packages/43/1a/b0a027144aa5c8f4ea654f4afdd634578b450807bb70b9f8bad00d6f6d3c/ujson-5.7.0.tar.gz" + }, + { + "algorithm": "sha256", + "hash": "7312731c7826e6c99cdd3ac503cd9acd300598e7a80bcf41f604fee5f49f566c", + "url": "https://files.pythonhosted.org/packages/47/f8/8e5668e80f7389281954e283222bfaf7f3936809ecf9b9293b9d8b4b40e2/ujson-5.7.0-cp38-cp38-macosx_11_0_arm64.whl" + }, + { + "algorithm": "sha256", + "hash": "f7f241488879d91a136b299e0c4ce091996c684a53775e63bb442d1a8e9ae22a", + "url": "https://files.pythonhosted.org/packages/4a/c4/cabfd64d4b0021285803703af67042aa01e1b1bc646fdf8847cd7e814b15/ujson-5.7.0-cp311-cp311-musllinux_1_1_i686.whl" + }, + { + "algorithm": "sha256", + "hash": "adf445a49d9a97a5a4c9bb1d652a1528de09dd1c48b29f79f3d66cea9f826bf6", + "url": "https://files.pythonhosted.org/packages/4d/f2/035e82d3baacc9c225ca3bae95bed5963bcdd796dd66ffa3fd0a5a087da7/ujson-5.7.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl" + }, + { + "algorithm": "sha256", + "hash": "3d3b3499c55911f70d4e074c626acdb79a56f54262c3c83325ffb210fb03e44d", + "url": "https://files.pythonhosted.org/packages/50/bf/1893d4f5dc6a2acb9a6db7ff018aa1cb7df367c35d491ebef6e30cdcc8ce/ujson-5.7.0-cp39-cp39-macosx_11_0_arm64.whl" + }, + { + "algorithm": "sha256", + "hash": "9b0f2680ce8a70f77f5d70aaf3f013d53e6af6d7058727a35d8ceb4a71cdd4e9", + "url": "https://files.pythonhosted.org/packages/58/57/bbc3e7efa9967247fba684b60a392174b7d18222931edcef2d52565bc0ac/ujson-5.7.0-cp311-cp311-macosx_10_9_x86_64.whl" + }, + { + "algorithm": "sha256", + "hash": "c3af9f9f22a67a8c9466a32115d9073c72a33ae627b11de6f592df0ee09b98b6", + "url": "https://files.pythonhosted.org/packages/59/9e/447bce1a6f29ff1bfd726ea5aa9b934bc02fef9f2b41689a00e17538f436/ujson-5.7.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl" + }, + { + "algorithm": "sha256", + "hash": "b5ac3d5c5825e30b438ea92845380e812a476d6c2a1872b76026f2e9d8060fc2", + "url": "https://files.pythonhosted.org/packages/5a/b1/7edca18e74a218d39fd8d00efc489cfd07c94271959103c647b794ce7bd5/ujson-5.7.0-cp39-cp39-musllinux_1_1_x86_64.whl" + }, + { + "algorithm": "sha256", + "hash": "b7316d3edeba8a403686cdcad4af737b8415493101e7462a70ff73dd0609eafc", + "url": "https://files.pythonhosted.org/packages/61/dd/38fc61ee050bd7cd24126721fae6cd7044b34cd8821e9d12a02c04757b7d/ujson-5.7.0-cp38-cp38-musllinux_1_1_aarch64.whl" + }, + { + "algorithm": "sha256", + "hash": "8b4257307e3662aa65e2644a277ca68783c5d51190ed9c49efebdd3cbfd5fa44", + "url": "https://files.pythonhosted.org/packages/65/89/398648bb869af5fce3d246ba61fb154528d5e71c24d6bcb008676d15fc2c/ujson-5.7.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl" + }, + { + "algorithm": "sha256", + "hash": "2f242eec917bafdc3f73a1021617db85f9958df80f267db69c76d766058f7b19", + "url": "https://files.pythonhosted.org/packages/69/24/a7df580e9981c4f8a28eb96eb897ab7363b96fca7f8a398ddc735bf190ea/ujson-5.7.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl" + }, + { + "algorithm": "sha256", + "hash": "afff311e9f065a8f03c3753db7011bae7beb73a66189c7ea5fcb0456b7041ea4", + "url": "https://files.pythonhosted.org/packages/71/bc/c8851ac5cf2ec8b0a69ef1e220fc27a88f8ff72fe1751fda0d7b28b58604/ujson-5.7.0-cp310-cp310-musllinux_1_1_aarch64.whl" + }, + { + "algorithm": "sha256", + "hash": "dda9aa4c33435147262cd2ea87c6b7a1ca83ba9b3933ff7df34e69fee9fced0c", + "url": "https://files.pythonhosted.org/packages/73/34/8821ac107019227a5ba3a544208cff444fee14bf779e08ec4e3706c91d00/ujson-5.7.0-cp38-cp38-musllinux_1_1_x86_64.whl" + }, + { + "algorithm": "sha256", + "hash": "4ee997799a23227e2319a3f8817ce0b058923dbd31904761b788dc8f53bd3e30", + "url": "https://files.pythonhosted.org/packages/75/82/b08227424871ac0cd739d142a7fd071d2934755dfcf8460e6e13d649f1b1/ujson-5.7.0-cp38-cp38-musllinux_1_1_i686.whl" + }, + { + "algorithm": "sha256", + "hash": "7592f40175c723c032cdbe9fe5165b3b5903604f774ab0849363386e99e1f253", + "url": "https://files.pythonhosted.org/packages/76/23/86820eb933c7d626380881a2d88bf9e395771ce349e5261df1e6760d209c/ujson-5.7.0-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl" + }, + { + "algorithm": "sha256", + "hash": "5593263a7fcfb934107444bcfba9dde8145b282de0ee9f61e285e59a916dda0f", + "url": "https://files.pythonhosted.org/packages/7b/77/14bef9f13f68f93643d1e8f3652bd40e7bdf54fc8968f20976c3c2a1dbe1/ujson-5.7.0-cp311-cp311-musllinux_1_1_x86_64.whl" + }, + { + "algorithm": "sha256", + "hash": "d7ff6ebb43bc81b057724e89550b13c9a30eda0f29c2f506f8b009895438f5a6", + "url": "https://files.pythonhosted.org/packages/7b/f6/f363b991aae592a77fe80f2882753211b59ed6a5174fddbb1ed6f5deccad/ujson-5.7.0-cp311-cp311-musllinux_1_1_aarch64.whl" + }, + { + "algorithm": "sha256", + "hash": "6411aea4c94a8e93c2baac096fbf697af35ba2b2ed410b8b360b3c0957a952d3", + "url": "https://files.pythonhosted.org/packages/85/4a/1db9cc0d4d78d4485a6527cf5ed2602729d87d8c35a4f11ec6890708ac75/ujson-5.7.0-cp39-cp39-macosx_10_9_x86_64.whl" + }, + { + "algorithm": "sha256", + "hash": "67a19fd8e7d8cc58a169bea99fed5666023adf707a536d8f7b0a3c51dd498abf", + "url": "https://files.pythonhosted.org/packages/87/f1/d5ee0307d455aab6fe90fde167a00feeb8f71eaf66292d2926221d1de2fe/ujson-5.7.0-cp311-cp311-macosx_11_0_arm64.whl" + }, + { + "algorithm": "sha256", + "hash": "35209cb2c13fcb9d76d249286105b4897b75a5e7f0efb0c0f4b90f222ce48910", + "url": "https://files.pythonhosted.org/packages/95/fb/fcd8f947f773ea55f650d64acd15240592c5637b3bfea164b4cd83da84c1/ujson-5.7.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl" + }, + { + "algorithm": "sha256", + "hash": "aae4d9e1b4c7b61780f0a006c897a4a1904f862fdab1abb3ea8f45bd11aa58f3", + "url": "https://files.pythonhosted.org/packages/a8/0d/51c70250116700017a3dc84ca910ff7c480c8d22afa76366920e8b1fdbfc/ujson-5.7.0-cp310-cp310-macosx_11_0_arm64.whl" + }, + { + "algorithm": "sha256", + "hash": "00343501dbaa5172e78ef0e37f9ebd08040110e11c12420ff7c1f9f0332d939e", + "url": "https://files.pythonhosted.org/packages/aa/e5/7655459351a1ce26202bbe971a6e6959d366925babe716f3751e1de96920/ujson-5.7.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl" + }, + { + "algorithm": "sha256", + "hash": "b6a6961fc48821d84b1198a09516e396d56551e910d489692126e90bf4887d29", + "url": "https://files.pythonhosted.org/packages/b4/50/5146b9464506718a9372e12d15f2cff330575ee7cf5faf3c51aa83d82e4a/ujson-5.7.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl" + }, + { + "algorithm": "sha256", + "hash": "d8cd622c069368d5074bd93817b31bdb02f8d818e57c29e206f10a1f9c6337dd", + "url": "https://files.pythonhosted.org/packages/b5/27/b8d3830cb60adc08505321b21cd2ae3930fe9d140728026dee17b2f795ef/ujson-5.7.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl" + }, + { + "algorithm": "sha256", + "hash": "d36a807a24c7d44f71686685ae6fbc8793d784bca1adf4c89f5f780b835b6243", + "url": "https://files.pythonhosted.org/packages/c1/39/a4e45a0b9f1be517d0236a52292adb21ffdf6531bd36310488ed1ee07071/ujson-5.7.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl" + }, + { + "algorithm": "sha256", + "hash": "b738282e12a05f400b291966630a98d622da0938caa4bc93cf65adb5f4281c60", + "url": "https://files.pythonhosted.org/packages/d1/7d/ec4dace4c686be92845e3d593f01828465546c5b8254ca296324cbcda8f8/ujson-5.7.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl" + }, + { + "algorithm": "sha256", + "hash": "24ad1aa7fc4e4caa41d3d343512ce68e41411fb92adf7f434a4d4b3749dc8f58", + "url": "https://files.pythonhosted.org/packages/d2/5b/876d7ca50f6be9c72a806a74d55a585043faae36d9a160ca4351f5d64b4d/ujson-5.7.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl" + }, + { + "algorithm": "sha256", + "hash": "d2e43ccdba1cb5c6d3448eadf6fc0dae7be6c77e357a3abc968d1b44e265866d", + "url": "https://files.pythonhosted.org/packages/d4/13/4c59d1dd29f7ec9b80cffb8ac393e735c5171e9430eb9a9af10e8fbc7b66/ujson-5.7.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl" + }, + { + "algorithm": "sha256", + "hash": "137831d8a0db302fb6828ee21c67ad63ac537bddc4376e1aab1c8573756ee21c", + "url": "https://files.pythonhosted.org/packages/d9/3e/507663d97fb574b56b35df2fb3d059516f9d11c270ab0ff170ef9cca2853/ujson-5.7.0-cp310-cp310-musllinux_1_1_x86_64.whl" + }, + { + "algorithm": "sha256", + "hash": "7b9dc5a90e2149643df7f23634fe202fed5ebc787a2a1be95cf23632b4d90651", + "url": "https://files.pythonhosted.org/packages/da/bc/d8b84c6e1156a7cdc4b3269994aff52e90101ddbfc0a8dabebbd8f484f54/ujson-5.7.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl" + }, + { + "algorithm": "sha256", + "hash": "6abb8e6d8f1ae72f0ed18287245f5b6d40094e2656d1eab6d99d666361514074", + "url": "https://files.pythonhosted.org/packages/e3/c1/2e7163fdad47acb63ac2231b70b637cd8dada78c2ad985a438930ef0ac8c/ujson-5.7.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl" + }, + { + "algorithm": "sha256", + "hash": "90712dfc775b2c7a07d4d8e059dd58636bd6ff1776d79857776152e693bddea6", + "url": "https://files.pythonhosted.org/packages/ea/f8/e547383551149f23a9cb40a717d75d2a72c6df50416c68538c64b79cd5bb/ujson-5.7.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl" + }, + { + "algorithm": "sha256", + "hash": "b522be14a28e6ac1cf818599aeff1004a28b42df4ed4d7bc819887b9dac915fc", + "url": "https://files.pythonhosted.org/packages/ef/f5/76dfa7e2e8135213ece8cd18478338bc9a3b4820152ecec5632dce598f66/ujson-5.7.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl" + }, + { + "algorithm": "sha256", + "hash": "16b2254a77b310f118717715259a196662baa6b1f63b1a642d12ab1ff998c3d7", + "url": "https://files.pythonhosted.org/packages/f8/d1/369fceb26e8eb69f9f8792323d123351c187c7866a0457c3ffe90ee9793c/ujson-5.7.0-cp37-cp37m-musllinux_1_1_x86_64.whl" + }, + { + "algorithm": "sha256", + "hash": "0ee295761e1c6c30400641f0a20d381633d7622633cdf83a194f3c876a0e4b7e", + "url": "https://files.pythonhosted.org/packages/fa/d6/01756485dd9c42f12f9b74c6b4b3f3008917e091597390a970cc85486631/ujson-5.7.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl" + } + ], + "project_name": "ujson", + "requires_dists": [], + "requires_python": ">=3.7", + "version": "5.7.0" + }, + { + "artifacts": [ + { + "algorithm": "sha256", + "hash": "75edcdc2f7d85b137124a6c3c9fc3933cdeaa12ecb9a6a959f22797a0feca7e1", + "url": "https://files.pythonhosted.org/packages/fe/ca/466766e20b767ddb9b951202542310cba37ea5f2d792dae7589f1741af58/urllib3-1.26.14-py2.py3-none-any.whl" + }, + { + "algorithm": "sha256", + "hash": "076907bf8fd355cde77728471316625a4d2f7e713c125f51953bb5b3eecf4f72", + "url": "https://files.pythonhosted.org/packages/c5/52/fe421fb7364aa738b3506a2d99e4f3a56e079c0a798e9f4fa5e14c60922f/urllib3-1.26.14.tar.gz" + } + ], + "project_name": "urllib3", + "requires_dists": [ + "PySocks!=1.5.7,<2.0,>=1.5.6; extra == \"socks\"", + "brotli>=1.0.9; ((os_name != \"nt\" or python_version >= \"3\") and platform_python_implementation == \"CPython\") and extra == \"brotli\"", + "brotlicffi>=0.8.0; ((os_name != \"nt\" or python_version >= \"3\") and platform_python_implementation != \"CPython\") and extra == \"brotli\"", + "brotlipy>=0.6.0; (os_name == \"nt\" and python_version < \"3\") and extra == \"brotli\"", + "certifi; extra == \"secure\"", + "cryptography>=1.3.4; extra == \"secure\"", + "idna>=2.0.0; extra == \"secure\"", + "ipaddress; python_version == \"2.7\" and extra == \"secure\"", + "pyOpenSSL>=0.14; extra == \"secure\"", + "urllib3-secure-extra; extra == \"secure\"" + ], + "requires_python": "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7", + "version": "1.26.14" + }, + { + "artifacts": [ + { + "algorithm": "sha256", + "hash": "3476cd107aba7b25ba1d59406938a47dc7eec6cfd0ad09ff77193f21a964dee7", + "url": "https://files.pythonhosted.org/packages/b4/a7/1875f68c4e39f9c68f5ba3aaf80ed1853903e5119941d514789fbc51a80d/wcmatch-8.4.1-py3-none-any.whl" + }, + { + "algorithm": "sha256", + "hash": "b1f042a899ea4c458b7321da1b5e3331e3e0ec781583434de1301946ceadb943", + "url": "https://files.pythonhosted.org/packages/b7/94/5dd083fc972655f6689587c3af705aabc8b8e781bacdf22d6d2282fe6142/wcmatch-8.4.1.tar.gz" + } + ], + "project_name": "wcmatch", + "requires_dists": [ + "bracex>=2.1.1" + ], + "requires_python": ">=3.7", + "version": "8.4.1" + }, + { + "artifacts": [ + { + "algorithm": "sha256", + "hash": "48904fc76a60e542af151aded95726c1a5c34ed43ab4134b597665c86d7ad556", + "url": "https://files.pythonhosted.org/packages/5b/fa/c9e82bbe1af6266adf08afb563905eb87cab83fde00a0a08963510621047/zipp-3.15.0-py3-none-any.whl" + }, + { + "algorithm": "sha256", + "hash": "112929ad649da941c23de50f356a2b5570c954b65150642bccdd66bf194d224b", + "url": "https://files.pythonhosted.org/packages/00/27/f0ac6b846684cecce1ee93d32450c45ab607f65c2e0255f0092032d91f07/zipp-3.15.0.tar.gz" + } + ], + "project_name": "zipp", + "requires_dists": [ + "big-O; extra == \"testing\"", + "flake8<5; extra == \"testing\"", + "furo; extra == \"docs\"", + "jaraco.functools; extra == \"testing\"", + "jaraco.itertools; extra == \"testing\"", + "jaraco.packaging>=9; extra == \"docs\"", + "jaraco.tidelift>=1.4; extra == \"docs\"", + "more-itertools; extra == \"testing\"", + "pytest-black>=0.3.7; platform_python_implementation != \"PyPy\" and extra == \"testing\"", + "pytest-checkdocs>=2.4; extra == \"testing\"", + "pytest-cov; extra == \"testing\"", + "pytest-enabler>=1.3; extra == \"testing\"", + "pytest-flake8; python_version < \"3.12\" and extra == \"testing\"", + "pytest-mypy>=0.9.1; platform_python_implementation != \"PyPy\" and extra == \"testing\"", + "pytest>=6; extra == \"testing\"", + "rst.linker>=1.9; extra == \"docs\"", + "sphinx-lint; extra == \"docs\"", + "sphinx>=3.5; extra == \"docs\"" + ], + "requires_python": ">=3.7", + "version": "3.15.0" + } + ], + "platform_tag": null + } + ], + "path_mappings": {}, + "pex_version": "2.1.124", + "pip_version": "23.0.1", + "prefer_older_binary": false, + "requirements": [ + "semgrep==1.14.0" + ], + "requires_python": [ + "<4,>=3.7" + ], + "resolver_version": "pip-2020-resolver", + "style": "universal", + "target_systems": [ + "linux", + "mac" + ], + "transitive": true, + "use_pep517": null +} diff --git a/src/python/pants/backend/tools/semgrep/subsystem.py b/src/python/pants/backend/tools/semgrep/subsystem.py new file mode 100644 index 00000000000..27bea094d1c --- /dev/null +++ b/src/python/pants/backend/tools/semgrep/subsystem.py @@ -0,0 +1,91 @@ +# Copyright 2022 Pants project contributors (see CONTRIBUTORS.md). +# Licensed under the Apache License, Version 2.0 (see LICENSE). + +from __future__ import annotations + +from typing import Iterable + +from pants.backend.python.goals import lockfile +from pants.backend.python.goals.export import ExportPythonTool, ExportPythonToolSentinel +from pants.backend.python.goals.lockfile import GeneratePythonLockfile +from pants.backend.python.subsystems.python_tool_base import ExportToolOption, PythonToolBase +from pants.backend.python.target_types import ConsoleScript +from pants.backend.python.util_rules.pex_requirements import GeneratePythonToolLockfileSentinel +from pants.core.goals.generate_lockfiles import GenerateToolLockfileSentinel +from pants.engine.rules import Rule, collect_rules, rule +from pants.engine.unions import UnionRule +from pants.option.option_types import ArgsListOption, SkipOption, StrListOption +from pants.util.docutil import git_url + + +class Semgrep(PythonToolBase): + name = "Semgrep" + options_scope = "semgrep" + help = "Lightweight static analysis for many languages. Find bug variants with patterns that look like source code. (https://semgrep.dev/)" + + default_version = "semgrep==1.14.0" + default_main = ConsoleScript("semgrep") + + register_interpreter_constraints = True + default_interpreter_constraints = ["CPython>=3.7,<4"] + + register_lockfile = True + default_lockfile_resource = ("pants.backend.tools.semgrep", "semgrep.lock") + default_lockfile_path = "src/python/pants/backend/tools/semgrep/semgrep.lock" + default_lockfile_url = git_url(default_lockfile_path) + + export = ExportToolOption() + + config = StrListOption( + "--config", + default=[".semgrep.yml", ".semgrep/*.yml", ".semgrep/*.yaml", ".semgrepignore"], + help="Globs of configuration files applicable to semgrep, including rules and ignores" + ) + + file_glob_include = StrListOption( + "--include", + help="Glob for which files to lint.", + ) + + file_glob_exclude = StrListOption( + "--exclude", + default=[], + help="Glob for which files to exclude from linting.", + ) + + args = ArgsListOption(example="--verbose") + + skip = SkipOption("fix") + + +class SemgrepLockfileSentinel(GeneratePythonToolLockfileSentinel): + resolve_name = Semgrep.options_scope + + +@rule +def setup_semgrep_lockfile( + _: SemgrepLockfileSentinel, semgrep: Semgrep +) -> GeneratePythonLockfile: + return GeneratePythonLockfile.from_tool(semgrep) + + +class SemgrepExportSentinel(ExportPythonToolSentinel): + pass + + +@rule +def semgrep_export(_: SemgrepExportSentinel, semgrep: Semgrep) -> ExportPythonTool: + if not semgrep.export: + return ExportPythonTool(resolve_name=semgrep.options_scope, pex_request=None) + return ExportPythonTool( + resolve_name=semgrep.options_scope, pex_request=semgrep.to_pex_request() + ) + + +def rules() -> Iterable[Rule | UnionRule]: + return ( + *collect_rules(), + *lockfile.rules(), + UnionRule(GenerateToolLockfileSentinel, SemgrepLockfileSentinel), + UnionRule(ExportPythonToolSentinel, SemgrepExportSentinel), + ) diff --git a/src/python/pants/bin/BUILD b/src/python/pants/bin/BUILD index 069445661fe..e2f8ed6d6e1 100644 --- a/src/python/pants/bin/BUILD +++ b/src/python/pants/bin/BUILD @@ -62,6 +62,7 @@ target( "src/python/pants/backend/experimental/scala/debug_goals", "src/python/pants/backend/experimental/scala/lint/scalafmt", "src/python/pants/backend/experimental/terraform", + "src/python/pants/backend/experimental/tools/semgrep", "src/python/pants/backend/experimental/tools/yamllint", "src/python/pants/backend/experimental/visibility", "src/python/pants/backend/google_cloud_function/python", diff --git a/src/python/pants/core/goals/fix.py b/src/python/pants/core/goals/fix.py index 0cf6ed3ae85..12943cfc20a 100644 --- a/src/python/pants/core/goals/fix.py +++ b/src/python/pants/core/goals/fix.py @@ -7,10 +7,12 @@ import logging from collections import defaultdict from dataclasses import dataclass -from typing import Any, Callable, Iterable, Iterator, NamedTuple, Sequence, Tuple, Type, TypeVar +from typing import Any, Callable, Iterable, Iterator, NamedTuple, Sequence, Tuple, Type, TypeVar, cast from typing_extensions import Protocol +import colors + from pants.base.specs import Specs from pants.core.goals.lint import ( LintFilesRequest, @@ -55,9 +57,12 @@ async def create( process_result: ProcessResult | FallibleProcessResult, *, strip_chroot_path: bool = False, + strip_formatting: bool = False, ) -> FixResult: def prep_output(s: bytes) -> str: - return strip_v2_chroot_path(s) if strip_chroot_path else s.decode() + chroot = strip_v2_chroot_path(s) if strip_chroot_path else s.decode() + formatting = cast(str, colors.strip_color(chroot)) if strip_formatting else chroot + return formatting return FixResult( input=request.snapshot, diff --git a/src/python/pants/core/goals/lint.py b/src/python/pants/core/goals/lint.py index 32e88197804..d133a2a0cbf 100644 --- a/src/python/pants/core/goals/lint.py +++ b/src/python/pants/core/goals/lint.py @@ -9,6 +9,7 @@ from typing import Any, Callable, ClassVar, Iterable, Iterator, Sequence, TypeVar, cast from typing_extensions import Protocol, final +import colors from pants.base.specs import Specs from pants.core.goals.multi_tool_goal_helper import ( @@ -68,9 +69,12 @@ def create( *, strip_chroot_path: bool = False, report: Digest = EMPTY_DIGEST, + strip_formatting: bool = False, ) -> LintResult: def prep_output(s: bytes) -> str: - return strip_v2_chroot_path(s) if strip_chroot_path else s.decode() + chroot = strip_v2_chroot_path(s) if strip_chroot_path else s.decode() + formatting = cast(str, colors.strip_color(chroot)) if strip_formatting else chroot + return formatting return cls( exit_code=process_result.exit_code, From f4bde7fdb6562690e7c013376b56cb189e63e724 Mon Sep 17 00:00:00 2001 From: Huon Wilson Date: Wed, 15 Mar 2023 21:14:39 +1100 Subject: [PATCH 02/49] Switch to more explicit names --- src/python/pants/backend/tools/semgrep/rules.py | 2 +- src/python/pants/backend/tools/semgrep/subsystem.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/python/pants/backend/tools/semgrep/rules.py b/src/python/pants/backend/tools/semgrep/rules.py index 16a8490e214..2d9f208037b 100644 --- a/src/python/pants/backend/tools/semgrep/rules.py +++ b/src/python/pants/backend/tools/semgrep/rules.py @@ -42,7 +42,7 @@ class SemgrepConfigFiles: async def gather_config_files( request: SemgrepConfigFilesRequest, semgrep: Semgrep ) -> SemgrepConfigFiles: - config_files_snapshot = await Get(Snapshot, PathGlobs(globs=semgrep.config)) + config_files_snapshot = await Get(Snapshot, PathGlobs(globs=[f"**/{name}" for name in semgrep.config_names])) return SemgrepConfigFiles(snapshot=config_files_snapshot) diff --git a/src/python/pants/backend/tools/semgrep/subsystem.py b/src/python/pants/backend/tools/semgrep/subsystem.py index 27bea094d1c..78a8d5c2467 100644 --- a/src/python/pants/backend/tools/semgrep/subsystem.py +++ b/src/python/pants/backend/tools/semgrep/subsystem.py @@ -36,10 +36,10 @@ class Semgrep(PythonToolBase): export = ExportToolOption() - config = StrListOption( - "--config", - default=[".semgrep.yml", ".semgrep/*.yml", ".semgrep/*.yaml", ".semgrepignore"], - help="Globs of configuration files applicable to semgrep, including rules and ignores" + config_names = StrListOption( + "--config-names", + default=[".semgrep.yml", ".semgrep/"], + help="File and directory names that contain that contain semgrep rule configurations" ) file_glob_include = StrListOption( From 8955c5057e51a965536e47baf5cf73a495ebcc61 Mon Sep 17 00:00:00 2001 From: Huon Wilson Date: Sun, 26 Mar 2023 11:36:27 +1100 Subject: [PATCH 03/49] Refine env vars, refine settings --- .../bin/_generate_all_lockfiles_helper.py | 2 +- .../experimental/tools/semgrep/register.py | 3 +- .../pants/backend/tools/semgrep/rules.py | 50 ++++++++++++------- .../pants/backend/tools/semgrep/subsystem.py | 6 +-- src/python/pants/core/goals/fix.py | 16 ++++-- src/python/pants/core/goals/lint.py | 2 +- 6 files changed, 50 insertions(+), 29 deletions(-) diff --git a/build-support/bin/_generate_all_lockfiles_helper.py b/build-support/bin/_generate_all_lockfiles_helper.py index 8fccf3c1da1..7bce498408f 100644 --- a/build-support/bin/_generate_all_lockfiles_helper.py +++ b/build-support/bin/_generate_all_lockfiles_helper.py @@ -47,8 +47,8 @@ from pants.backend.scala.lint.scalafmt.subsystem import ScalafmtSubsystem from pants.backend.scala.subsystems.scalatest import Scalatest from pants.backend.terraform.dependency_inference import TerraformHcl2Parser -from pants.backend.tools.yamllint.subsystem import Yamllint from pants.backend.tools.semgrep.subsystem import Semgrep +from pants.backend.tools.yamllint.subsystem import Yamllint from pants.jvm.resolve.jvm_tool import JvmToolBase from pants.util.strutil import softwrap diff --git a/src/python/pants/backend/experimental/tools/semgrep/register.py b/src/python/pants/backend/experimental/tools/semgrep/register.py index bcb2fec1936..002cb2d0355 100644 --- a/src/python/pants/backend/experimental/tools/semgrep/register.py +++ b/src/python/pants/backend/experimental/tools/semgrep/register.py @@ -1,7 +1,8 @@ # Copyright 2022 Pants project contributors (see CONTRIBUTORS.md). # Licensed under the Apache License, Version 2.0 (see LICENSE). -"""Lightweight static analysis for many languages. Find bug variants with patterns that look like source code. +"""Lightweight static analysis for many languages. Find bug variants with patterns that look like +source code. See https://semgrep.dev/ for details. """ diff --git a/src/python/pants/backend/tools/semgrep/rules.py b/src/python/pants/backend/tools/semgrep/rules.py index 2d9f208037b..3d74959d9ea 100644 --- a/src/python/pants/backend/tools/semgrep/rules.py +++ b/src/python/pants/backend/tools/semgrep/rules.py @@ -5,22 +5,21 @@ from dataclasses import dataclass from typing import Any, Iterable -from pants.engine.process import FallibleProcessResult -from pants.backend.python.util_rules.pex import VenvPex, PexRequest, VenvPexProcess -from pants.core.util_rules.partitions import Partitions -from pants.core.goals.lint import LintFilesRequest, LintResult -from .subsystem import Semgrep -from pants.option.global_options import GlobalOptions +from pants.backend.python.util_rules.pex import PexRequest, VenvPex, VenvPexProcess from pants.core.goals.fix import FixFilesRequest, FixResult -from pants.engine.rules import Rule +from pants.core.goals.lint import LintFilesRequest, LintResult +from pants.core.util_rules.partitions import PartitionerType, Partitions +from pants.engine.fs import CreateDigest, Digest, DigestSubset, FileContent, MergeDigests, PathGlobs +from pants.engine.internals.native_engine import FilespecMatcher, Snapshot +from pants.engine.process import FallibleProcessResult +from pants.engine.rules import Get, MultiGet, Rule, collect_rules, rule from pants.engine.unions import UnionRule -from pants.engine.rules import Get, MultiGet, collect_rules, rule +from pants.option.global_options import GlobalOptions from pants.util.logging import LogLevel -from pants.core.util_rules.partitions import PartitionerType -from pants.engine.internals.native_engine import FilespecMatcher, Snapshot -from pants.engine.fs import Digest, DigestSubset, MergeDigests, PathGlobs from pants.util.strutil import pluralize, softwrap +from .subsystem import Semgrep + class SemgrepRequest(LintFilesRequest): tool_subsystem = Semgrep @@ -42,7 +41,9 @@ class SemgrepConfigFiles: async def gather_config_files( request: SemgrepConfigFilesRequest, semgrep: Semgrep ) -> SemgrepConfigFiles: - config_files_snapshot = await Get(Snapshot, PathGlobs(globs=[f"**/{name}" for name in semgrep.config_names])) + config_files_snapshot = await Get( + Snapshot, PathGlobs(globs=[f"**/{name}" for name in semgrep.config_names]) + ) return SemgrepConfigFiles(snapshot=config_files_snapshot) @@ -58,28 +59,34 @@ async def partition(request: SemgrepRequest.PartitionRequest, semgrep: Semgrep) return Partitions.single_partition(matching_files) +# We have a hard-coded settings file to side-step +# https://github.com/returntocorp/semgrep/issues/7102, and also provide more cacheability. +_DEFAULT_SETTINGS = FileContent( + path="__semgrep_settings.yaml", + content=b"has_shown_metrics_notification: true", +) + + @rule(desc="Lint with Semgrep", level=LogLevel.DEBUG) async def lint( request: SemgrepRequest.Batch[str, Any], semgrep: Semgrep, global_options: GlobalOptions, ) -> LintResult: - config_files, semgrep_pex, input_files = await MultiGet( + config_files, semgrep_pex, input_files, settings = await MultiGet( Get(SemgrepConfigFiles, SemgrepConfigFilesRequest()), Get(VenvPex, PexRequest, semgrep.to_pex_request()), Get(Snapshot, PathGlobs(globs=request.elements)), + Get(Digest, CreateDigest([_DEFAULT_SETTINGS])), ) input_digest = await Get( - Digest, MergeDigests((input_files.digest, config_files.snapshot.digest)) + Digest, MergeDigests((input_files.digest, config_files.snapshot.digest, settings)) ) # TODO: support running this under the fix goal if with --autofix if there's rules that have # fixes... but not all rules have fixes, so we need to be running with --error/checking exit # codes, which FixResult doesn't currently support. - - # TODO: concurrent runs occasionally hit "Bad settings format; ... will be overriden" errors - # (https://github.com/returntocorp/semgrep/issues/7102). result = await Get( FallibleProcessResult, VenvPexProcess( @@ -89,12 +96,17 @@ async def lint( *(f"--config={f}" for f in config_files.snapshot.files), "-j", "{pants_concurrency}", - "--force-color", - "--disable-version-check", "--error", *semgrep.args, *input_files.files, ), + extra_env={ + "SEMGREP_FORCE_COLOR": "true", + # disable various global state/network requests + "SEMGREP_SETTINGS_FILE": _DEFAULT_SETTINGS.path, + "SEMGREP_ENABLE_VERSION_CHECK": "0", + "SEMGREP_SEND_METRICS": "off", + }, input_digest=input_digest, concurrency_available=len(input_files.files), description=f"Run Semgrep on {pluralize(len(input_files.files), 'file')}.", diff --git a/src/python/pants/backend/tools/semgrep/subsystem.py b/src/python/pants/backend/tools/semgrep/subsystem.py index 78a8d5c2467..489284c5e74 100644 --- a/src/python/pants/backend/tools/semgrep/subsystem.py +++ b/src/python/pants/backend/tools/semgrep/subsystem.py @@ -39,7 +39,7 @@ class Semgrep(PythonToolBase): config_names = StrListOption( "--config-names", default=[".semgrep.yml", ".semgrep/"], - help="File and directory names that contain that contain semgrep rule configurations" + help="File and directory names that contain that contain semgrep rule configurations", ) file_glob_include = StrListOption( @@ -63,9 +63,7 @@ class SemgrepLockfileSentinel(GeneratePythonToolLockfileSentinel): @rule -def setup_semgrep_lockfile( - _: SemgrepLockfileSentinel, semgrep: Semgrep -) -> GeneratePythonLockfile: +def setup_semgrep_lockfile(_: SemgrepLockfileSentinel, semgrep: Semgrep) -> GeneratePythonLockfile: return GeneratePythonLockfile.from_tool(semgrep) diff --git a/src/python/pants/core/goals/fix.py b/src/python/pants/core/goals/fix.py index 12943cfc20a..361477e03d4 100644 --- a/src/python/pants/core/goals/fix.py +++ b/src/python/pants/core/goals/fix.py @@ -7,11 +7,21 @@ import logging from collections import defaultdict from dataclasses import dataclass -from typing import Any, Callable, Iterable, Iterator, NamedTuple, Sequence, Tuple, Type, TypeVar, cast - -from typing_extensions import Protocol +from typing import ( + Any, + Callable, + Iterable, + Iterator, + NamedTuple, + Sequence, + Tuple, + Type, + TypeVar, + cast, +) import colors +from typing_extensions import Protocol from pants.base.specs import Specs from pants.core.goals.lint import ( diff --git a/src/python/pants/core/goals/lint.py b/src/python/pants/core/goals/lint.py index d133a2a0cbf..0009ba1fbfe 100644 --- a/src/python/pants/core/goals/lint.py +++ b/src/python/pants/core/goals/lint.py @@ -8,8 +8,8 @@ from dataclasses import dataclass from typing import Any, Callable, ClassVar, Iterable, Iterator, Sequence, TypeVar, cast -from typing_extensions import Protocol, final import colors +from typing_extensions import Protocol, final from pants.base.specs import Specs from pants.core.goals.multi_tool_goal_helper import ( From a2fa07b30f6955a24084a630fb07383c997be717 Mon Sep 17 00:00:00 2001 From: Huon Wilson Date: Sun, 26 Mar 2023 12:44:01 +1100 Subject: [PATCH 04/49] require glob matches --- src/python/pants/backend/tools/semgrep/rules.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/python/pants/backend/tools/semgrep/rules.py b/src/python/pants/backend/tools/semgrep/rules.py index 3d74959d9ea..e66f50fd5c6 100644 --- a/src/python/pants/backend/tools/semgrep/rules.py +++ b/src/python/pants/backend/tools/semgrep/rules.py @@ -9,7 +9,15 @@ from pants.core.goals.fix import FixFilesRequest, FixResult from pants.core.goals.lint import LintFilesRequest, LintResult from pants.core.util_rules.partitions import PartitionerType, Partitions -from pants.engine.fs import CreateDigest, Digest, DigestSubset, FileContent, MergeDigests, PathGlobs +from pants.engine.fs import ( + CreateDigest, + Digest, + DigestSubset, + FileContent, + GlobMatchErrorBehavior, + MergeDigests, + PathGlobs, +) from pants.engine.internals.native_engine import FilespecMatcher, Snapshot from pants.engine.process import FallibleProcessResult from pants.engine.rules import Get, MultiGet, Rule, collect_rules, rule @@ -42,7 +50,12 @@ async def gather_config_files( request: SemgrepConfigFilesRequest, semgrep: Semgrep ) -> SemgrepConfigFiles: config_files_snapshot = await Get( - Snapshot, PathGlobs(globs=[f"**/{name}" for name in semgrep.config_names]) + Snapshot, + PathGlobs( + globs=[f"**/{name}" for name in semgrep.config_names], + glob_match_error_behavior=GlobMatchErrorBehavior.error, + description_of_origin="the option `--semgrep-config-names`", + ), ) return SemgrepConfigFiles(snapshot=config_files_snapshot) From 56dccf963493295b9ada01720e7a6029b408b785 Mon Sep 17 00:00:00 2001 From: Huon Wilson Date: Sun, 26 Mar 2023 12:44:09 +1100 Subject: [PATCH 05/49] rm unused imports --- src/python/pants/backend/tools/semgrep/rules.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/python/pants/backend/tools/semgrep/rules.py b/src/python/pants/backend/tools/semgrep/rules.py index e66f50fd5c6..d0c4213bf45 100644 --- a/src/python/pants/backend/tools/semgrep/rules.py +++ b/src/python/pants/backend/tools/semgrep/rules.py @@ -6,13 +6,11 @@ from typing import Any, Iterable from pants.backend.python.util_rules.pex import PexRequest, VenvPex, VenvPexProcess -from pants.core.goals.fix import FixFilesRequest, FixResult from pants.core.goals.lint import LintFilesRequest, LintResult from pants.core.util_rules.partitions import PartitionerType, Partitions from pants.engine.fs import ( CreateDigest, Digest, - DigestSubset, FileContent, GlobMatchErrorBehavior, MergeDigests, @@ -24,7 +22,7 @@ from pants.engine.unions import UnionRule from pants.option.global_options import GlobalOptions from pants.util.logging import LogLevel -from pants.util.strutil import pluralize, softwrap +from pants.util.strutil import pluralize from .subsystem import Semgrep From 955538e29d853d8e39fccb16f723b05b93c6ff54 Mon Sep 17 00:00:00 2001 From: Huon Wilson Date: Sun, 26 Mar 2023 14:25:55 +1100 Subject: [PATCH 06/49] Glob better --- src/python/pants/backend/tools/semgrep/rules.py | 5 +++-- src/python/pants/backend/tools/semgrep/subsystem.py | 8 ++++---- src/python/pants/option/global_options.py | 2 +- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/python/pants/backend/tools/semgrep/rules.py b/src/python/pants/backend/tools/semgrep/rules.py index d0c4213bf45..1f79daefd32 100644 --- a/src/python/pants/backend/tools/semgrep/rules.py +++ b/src/python/pants/backend/tools/semgrep/rules.py @@ -47,12 +47,13 @@ class SemgrepConfigFiles: async def gather_config_files( request: SemgrepConfigFilesRequest, semgrep: Semgrep ) -> SemgrepConfigFiles: + globs = [f"**/{glob}" for glob in semgrep.config_globs] config_files_snapshot = await Get( Snapshot, PathGlobs( - globs=[f"**/{name}" for name in semgrep.config_names], + globs=globs, glob_match_error_behavior=GlobMatchErrorBehavior.error, - description_of_origin="the option `--semgrep-config-names`", + description_of_origin="the option `--semgrep-config-globs`", ), ) return SemgrepConfigFiles(snapshot=config_files_snapshot) diff --git a/src/python/pants/backend/tools/semgrep/subsystem.py b/src/python/pants/backend/tools/semgrep/subsystem.py index 489284c5e74..05b28474180 100644 --- a/src/python/pants/backend/tools/semgrep/subsystem.py +++ b/src/python/pants/backend/tools/semgrep/subsystem.py @@ -36,10 +36,10 @@ class Semgrep(PythonToolBase): export = ExportToolOption() - config_names = StrListOption( - "--config-names", - default=[".semgrep.yml", ".semgrep/"], - help="File and directory names that contain that contain semgrep rule configurations", + config_globs = StrListOption( + "--config-globs", + default=[".semgrep.yml", ".semgrep/*.yml"], + help="File globs that contain semgrep rule configurations", ) file_glob_include = StrListOption( diff --git a/src/python/pants/option/global_options.py b/src/python/pants/option/global_options.py index b64bb26e97f..0d8095e75e6 100644 --- a/src/python/pants/option/global_options.py +++ b/src/python/pants/option/global_options.py @@ -894,7 +894,7 @@ class BootstrapOptions: ) pants_ignore = StrListOption( advanced=True, - default=[".*/", _default_rel_distdir, "__pycache__"], + default=[".*/", _default_rel_distdir, "__pycache__", "!.semgrep", "!.semgrep.yml"], help=softwrap( """ Paths to ignore for all filesystem operations performed by pants From 68fc026c7e1475abada4d5f5dcdb94119c52f4f5 Mon Sep 17 00:00:00 2001 From: Huon Wilson Date: Sun, 26 Mar 2023 14:27:07 +1100 Subject: [PATCH 07/49] issue link --- src/python/pants/backend/tools/semgrep/rules.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/python/pants/backend/tools/semgrep/rules.py b/src/python/pants/backend/tools/semgrep/rules.py index 1f79daefd32..56ef4b838d9 100644 --- a/src/python/pants/backend/tools/semgrep/rules.py +++ b/src/python/pants/backend/tools/semgrep/rules.py @@ -96,9 +96,9 @@ async def lint( Digest, MergeDigests((input_files.digest, config_files.snapshot.digest, settings)) ) - # TODO: support running this under the fix goal if with --autofix if there's rules that have - # fixes... but not all rules have fixes, so we need to be running with --error/checking exit - # codes, which FixResult doesn't currently support. + # TODO: https://github.com/pantsbuild/pants/issues/18430 support running this with --autofix + # under the fix goal... but not all rules have fixes, so we need to be running with + # --error/checking exit codes, which FixResult doesn't currently support. result = await Get( FallibleProcessResult, VenvPexProcess( From 8be9b21e30435779f585b4b7bbccb918f380770e Mon Sep 17 00:00:00 2001 From: Huon Wilson Date: Sun, 26 Mar 2023 14:32:41 +1100 Subject: [PATCH 08/49] partitioning by config --- src/python/pants/backend/tools/semgrep/rules.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/python/pants/backend/tools/semgrep/rules.py b/src/python/pants/backend/tools/semgrep/rules.py index 56ef4b838d9..03f8c8d9e80 100644 --- a/src/python/pants/backend/tools/semgrep/rules.py +++ b/src/python/pants/backend/tools/semgrep/rules.py @@ -68,6 +68,7 @@ async def partition(request: SemgrepRequest.PartitionRequest, semgrep: Semgrep) includes=semgrep.file_glob_include, excludes=semgrep.file_glob_exclude ).matches(request.files) + # TODO: partition by config return Partitions.single_partition(matching_files) From 594d1ea9a82963eb5ae53e5121db68ae29432914 Mon Sep 17 00:00:00 2001 From: Huon Wilson Date: Sun, 26 Mar 2023 14:46:47 +1100 Subject: [PATCH 09/49] revert spurious changes --- .../bin/_generate_all_lockfiles_helper.py | 1 - build-support/bin/rust/bootstrap_code.sh | 2 +- src/python/pants/core/goals/fix.py | 19 ++----------------- 3 files changed, 3 insertions(+), 19 deletions(-) diff --git a/build-support/bin/_generate_all_lockfiles_helper.py b/build-support/bin/_generate_all_lockfiles_helper.py index 7bce498408f..7ca4648c20b 100644 --- a/build-support/bin/_generate_all_lockfiles_helper.py +++ b/build-support/bin/_generate_all_lockfiles_helper.py @@ -235,7 +235,6 @@ def main() -> None: create_parser().print_help() return args = create_parser().parse_args() - assert args.tool if args.all: update_internal_lockfiles(specified=None) diff --git a/build-support/bin/rust/bootstrap_code.sh b/build-support/bin/rust/bootstrap_code.sh index 87910e275a0..b94450f3bf7 100644 --- a/build-support/bin/rust/bootstrap_code.sh +++ b/build-support/bin/rust/bootstrap_code.sh @@ -39,7 +39,7 @@ readonly NATIVE_CLIENT_TARGET="${NATIVE_ROOT}/target/${MODE}/pants" function _build_native_code() { banner "Building native code..." # NB: See Cargo.toml with regard to the `extension-module` features. - "${REPO_ROOT}/cargo" build -v \ + "${REPO_ROOT}/cargo" build \ --features=extension-module \ ${MODE_FLAG} \ -p engine \ diff --git a/src/python/pants/core/goals/fix.py b/src/python/pants/core/goals/fix.py index 361477e03d4..0cf6ed3ae85 100644 --- a/src/python/pants/core/goals/fix.py +++ b/src/python/pants/core/goals/fix.py @@ -7,20 +7,8 @@ import logging from collections import defaultdict from dataclasses import dataclass -from typing import ( - Any, - Callable, - Iterable, - Iterator, - NamedTuple, - Sequence, - Tuple, - Type, - TypeVar, - cast, -) +from typing import Any, Callable, Iterable, Iterator, NamedTuple, Sequence, Tuple, Type, TypeVar -import colors from typing_extensions import Protocol from pants.base.specs import Specs @@ -67,12 +55,9 @@ async def create( process_result: ProcessResult | FallibleProcessResult, *, strip_chroot_path: bool = False, - strip_formatting: bool = False, ) -> FixResult: def prep_output(s: bytes) -> str: - chroot = strip_v2_chroot_path(s) if strip_chroot_path else s.decode() - formatting = cast(str, colors.strip_color(chroot)) if strip_formatting else chroot - return formatting + return strip_v2_chroot_path(s) if strip_chroot_path else s.decode() return FixResult( input=request.snapshot, From 790fe511466144d8b311a16b9d87f9e9a8ba854f Mon Sep 17 00:00:00 2001 From: Huon Wilson Date: Sun, 26 Mar 2023 14:47:19 +1100 Subject: [PATCH 10/49] stop using semgrep in pants itself --- pants.toml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/pants.toml b/pants.toml index fe47e940c44..fe0aeaedf75 100644 --- a/pants.toml +++ b/pants.toml @@ -34,7 +34,6 @@ backend_packages.add = [ "pants.backend.experimental.scala.debug_goals", "pants.backend.experimental.visibility", "pants.backend.tools.preamble", - "pants.backend.experimental.tools.semgrep", "pants.explorer.server", "internal_plugins.releases", "internal_plugins.test_lockfile_fixtures", @@ -312,6 +311,3 @@ master = "src/python/pants/notes/master.rst" "2.15.x" = "src/python/pants/notes/2.15.x.md" "2.16.x" = "src/python/pants/notes/2.16.x.md" "2.17.x" = "src/python/pants/notes/2.17.x.md" - -[semgrep] -include = ["**/*.py"] From 4f33a5d29603245bc8a844b7080171e386598a7a Mon Sep 17 00:00:00 2001 From: Huon Wilson Date: Sat, 1 Apr 2023 12:09:54 +1100 Subject: [PATCH 11/49] Add target types --- .../backend/tools/semgrep/target_types.py | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 src/python/pants/backend/tools/semgrep/target_types.py diff --git a/src/python/pants/backend/tools/semgrep/target_types.py b/src/python/pants/backend/tools/semgrep/target_types.py new file mode 100644 index 00000000000..672a67369fe --- /dev/null +++ b/src/python/pants/backend/tools/semgrep/target_types.py @@ -0,0 +1,57 @@ +# Copyright 2023 Pants project contributors (see CONTRIBUTORS.md). +# Licensed under the Apache License, Version 2.0 (see LICENSE). + +from __future__ import annotations + +from typing import ClassVar + +from pants.engine.target import ( + COMMON_TARGET_FIELDS, + MultipleSourcesField, + SingleSourceField, + Target, + TargetFilesGenerator, + TargetFilesGeneratorSettingsRequest, + generate_multiple_sources_field_help_message, +) + + +class SemgrepRuleSourceField(SingleSourceField): + expected_file_extensions: ClassVar[tuple[str, ...]] = (".yml", ".yaml") + + +class SemgrepRuleGeneratingSourcesField(MultipleSourcesField): + expected_file_extensions: ClassVar[tuple[str, ...]] = (".yml", ".yaml") + default = [ + ".semgrep.yml", + ".semgrep.yaml", + ".semgrep/*.yml", + ".semgrep/*.yaml", + ] + help = generate_multiple_sources_field_help_message( + "Example: `sources=['.semgrep.yml', '.semgrep.yaml', '.semgrep/*.yml', '.semgrep/*.yaml',]`" + ) + + +class SemgrepRuleSource(Target): + alias = ("semgrep_rule_source",) + core_fields = (*COMMON_TARGET_FIELDS, SemgrepRuleSourceField) + + help = "A single source file containing Semgrep rules" + + +class SemgrepRuleGeneratorSettingsRequest(TargetFilesGeneratorSettingsRequest): + pass + + +class PythonSourcesGeneratorTarget(TargetFilesGenerator): + alias = "semgrep_rule_sources" + core_fields = ( + *COMMON_TARGET_FIELDS, + SemgrepRuleGeneratingSourcesField, + ) + generated_target_cls = SemgrepRuleSource + copied_fields = COMMON_TARGET_FIELDS + moved_fields = () + settings_request_cls = SemgrepRuleGeneratorSettingsRequest + help = "Generate a `semgrep_rule_source` target for each file in the `sources` field." From 89c5b4d792f3d2c74437351979faec4a19c554e8 Mon Sep 17 00:00:00 2001 From: Huon Wilson Date: Sat, 1 Apr 2023 12:45:30 +1100 Subject: [PATCH 12/49] mypy fixes --- src/python/pants/backend/tools/semgrep/subsystem.py | 12 +++++++++--- .../pants/backend/tools/semgrep/target_types.py | 8 ++++---- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/python/pants/backend/tools/semgrep/subsystem.py b/src/python/pants/backend/tools/semgrep/subsystem.py index 05b28474180..790bb29f147 100644 --- a/src/python/pants/backend/tools/semgrep/subsystem.py +++ b/src/python/pants/backend/tools/semgrep/subsystem.py @@ -14,7 +14,7 @@ from pants.core.goals.generate_lockfiles import GenerateToolLockfileSentinel from pants.engine.rules import Rule, collect_rules, rule from pants.engine.unions import UnionRule -from pants.option.option_types import ArgsListOption, SkipOption, StrListOption +from pants.option.option_types import ArgsListOption, BoolOption, SkipOption, StrListOption from pants.util.docutil import git_url @@ -55,7 +55,13 @@ class Semgrep(PythonToolBase): args = ArgsListOption(example="--verbose") - skip = SkipOption("fix") + skip = SkipOption("lint") + + tailor_source_targets = BoolOption( + default=True, + help="If true, add `semgrep_rule_sources` targets with the `tailor` goal.", + advanced=True, + ) class SemgrepLockfileSentinel(GeneratePythonToolLockfileSentinel): @@ -64,7 +70,7 @@ class SemgrepLockfileSentinel(GeneratePythonToolLockfileSentinel): @rule def setup_semgrep_lockfile(_: SemgrepLockfileSentinel, semgrep: Semgrep) -> GeneratePythonLockfile: - return GeneratePythonLockfile.from_tool(semgrep) + return semgrep.to_lockfile_request() class SemgrepExportSentinel(ExportPythonToolSentinel): diff --git a/src/python/pants/backend/tools/semgrep/target_types.py b/src/python/pants/backend/tools/semgrep/target_types.py index 672a67369fe..5b81f0dfc04 100644 --- a/src/python/pants/backend/tools/semgrep/target_types.py +++ b/src/python/pants/backend/tools/semgrep/target_types.py @@ -22,19 +22,19 @@ class SemgrepRuleSourceField(SingleSourceField): class SemgrepRuleGeneratingSourcesField(MultipleSourcesField): expected_file_extensions: ClassVar[tuple[str, ...]] = (".yml", ".yaml") - default = [ + default = ( ".semgrep.yml", ".semgrep.yaml", ".semgrep/*.yml", ".semgrep/*.yaml", - ] + ) help = generate_multiple_sources_field_help_message( "Example: `sources=['.semgrep.yml', '.semgrep.yaml', '.semgrep/*.yml', '.semgrep/*.yaml',]`" ) class SemgrepRuleSource(Target): - alias = ("semgrep_rule_source",) + alias = "semgrep_rule_source" core_fields = (*COMMON_TARGET_FIELDS, SemgrepRuleSourceField) help = "A single source file containing Semgrep rules" @@ -44,7 +44,7 @@ class SemgrepRuleGeneratorSettingsRequest(TargetFilesGeneratorSettingsRequest): pass -class PythonSourcesGeneratorTarget(TargetFilesGenerator): +class SemgrepRuleSourcesGeneratorTarget(TargetFilesGenerator): alias = "semgrep_rule_sources" core_fields = ( *COMMON_TARGET_FIELDS, From 25733f3805a35741420be33d881cae25fefc054c Mon Sep 17 00:00:00 2001 From: Huon Wilson Date: Sat, 1 Apr 2023 13:25:56 +1100 Subject: [PATCH 13/49] tailor semgrep_rule_source(s) --- src/python/pants/backend/tools/semgrep/BUILD | 2 + .../pants/backend/tools/semgrep/subsystem.py | 2 +- .../pants/backend/tools/semgrep/tailor.py | 75 ++++++++++++ .../backend/tools/semgrep/tailor_test.py | 114 ++++++++++++++++++ .../backend/tools/semgrep/target_types.py | 6 - src/python/pants/option/global_options.py | 2 +- 6 files changed, 193 insertions(+), 8 deletions(-) create mode 100644 src/python/pants/backend/tools/semgrep/tailor.py create mode 100644 src/python/pants/backend/tools/semgrep/tailor_test.py diff --git a/src/python/pants/backend/tools/semgrep/BUILD b/src/python/pants/backend/tools/semgrep/BUILD index 37552a307bf..1ae55aee4e5 100644 --- a/src/python/pants/backend/tools/semgrep/BUILD +++ b/src/python/pants/backend/tools/semgrep/BUILD @@ -4,3 +4,5 @@ resource(name="lockfile", source="semgrep.lock") python_sources(dependencies=[":lockfile"]) + +python_tests(name="tests") diff --git a/src/python/pants/backend/tools/semgrep/subsystem.py b/src/python/pants/backend/tools/semgrep/subsystem.py index 790bb29f147..6092e1825a3 100644 --- a/src/python/pants/backend/tools/semgrep/subsystem.py +++ b/src/python/pants/backend/tools/semgrep/subsystem.py @@ -57,7 +57,7 @@ class Semgrep(PythonToolBase): skip = SkipOption("lint") - tailor_source_targets = BoolOption( + tailor_rule_targets = BoolOption( default=True, help="If true, add `semgrep_rule_sources` targets with the `tailor` goal.", advanced=True, diff --git a/src/python/pants/backend/tools/semgrep/tailor.py b/src/python/pants/backend/tools/semgrep/tailor.py new file mode 100644 index 00000000000..361c3237fc3 --- /dev/null +++ b/src/python/pants/backend/tools/semgrep/tailor.py @@ -0,0 +1,75 @@ +# Copyright 2023 Pants project contributors (see CONTRIBUTORS.md). +# Licensed under the Apache License, Version 2.0 (see LICENSE). + +from __future__ import annotations + +import os +from collections import defaultdict +from dataclasses import dataclass +from typing import Iterable + +from pants.backend.tools.semgrep.subsystem import Semgrep +from pants.backend.tools.semgrep.target_types import SemgrepRuleSourcesGeneratorTarget +from pants.core.goals.tailor import ( + AllOwnedSources, + PutativeTarget, + PutativeTargets, + PutativeTargetsRequest, +) +from pants.engine.fs import PathGlobs, Paths +from pants.engine.internals.selectors import Get +from pants.engine.rules import collect_rules, rule +from pants.engine.unions import UnionRule +from pants.util.logging import LogLevel + + +@dataclass(frozen=True) +class PutativeSemgrepTargetsRequest(PutativeTargetsRequest): + pass + + +def _group_by_semgrep_dir(paths: Iterable[str]) -> dict[str, set[str]]: + ret = defaultdict(set) + for path in paths: + dirname, filename = os.path.split(path) + dir2name, dir_basename = os.path.split(dirname) + if dir_basename == ".semgrep": + # rules from foo/bar/.semgrep/ should behave like they're in foo/bar, not + # foo/bar/,semgrep + ret[dir2name].add(os.path.join(dir_basename, filename)) + else: + ret[dirname].add(filename) + + return ret + + +@rule(level=LogLevel.DEBUG, desc="Determine candidate Python targets to create") +async def find_putative_targets( + req: PutativeSemgrepTargetsRequest, + all_owned_sources: AllOwnedSources, + semgrep: Semgrep, +) -> PutativeTargets: + pts = [] + + if semgrep.tailor_rule_targets: + all_files_globs = req.path_globs( + ".semgrep.yml", ".semgrep.yaml", ".semgrep/*.yml", ".semgrep/*.yaml" + ) + all_files = await Get(Paths, PathGlobs, all_files_globs) + unowned = set(all_files.files) - set(all_owned_sources) + + for dirname, filenames in _group_by_semgrep_dir(unowned).items(): + pt = PutativeTarget.for_target_type( + SemgrepRuleSourcesGeneratorTarget, + path=dirname, + name="semgrep", + triggering_sources=sorted(filenames), + ) + + pts.append(pt) + + return PutativeTargets(pts) + + +def rules(): + return [*collect_rules(), UnionRule(PutativeTargetsRequest, PutativeSemgrepTargetsRequest)] diff --git a/src/python/pants/backend/tools/semgrep/tailor_test.py b/src/python/pants/backend/tools/semgrep/tailor_test.py new file mode 100644 index 00000000000..9b8773efc2d --- /dev/null +++ b/src/python/pants/backend/tools/semgrep/tailor_test.py @@ -0,0 +1,114 @@ +# Copyright 2023 Pants project contributors (see CONTRIBUTORS.md). +# Licensed under the Apache License, Version 2.0 (see LICENSE). + +import pytest + +from pants.backend.tools.semgrep import tailor +from pants.backend.tools.semgrep.tailor import PutativeSemgrepTargetsRequest +from pants.backend.tools.semgrep.target_types import SemgrepRuleSourcesGeneratorTarget +from pants.core.goals.tailor import AllOwnedSources, PutativeTarget, PutativeTargets +from pants.engine.rules import QueryRule +from pants.testutil.rule_runner import RuleRunner + + +@pytest.mark.parametrize( + ("paths", "expected"), + [ + ((), {}), + (("foo/bar/.semgrep.yml",), {"foo/bar": {".semgrep.yml"}}), + (("foo/bar/.semgrep/baz.yml",), {"foo/bar": {".semgrep/baz.yml"}}), + ( + ( + "foo/bar/.semgrep.yml", + "foo/bar/.semgrep/baz.yml", + ), + {"foo/bar": {".semgrep.yml", ".semgrep/baz.yml"}}, + ), + ( + ( + "foo/.semgrep/baz.yml", + "foo/bar/.semgrep.yml", + "foo/bar/qux/.semgrep.yml", + ), + { + "foo": {".semgrep/baz.yml"}, + "foo/bar": {".semgrep.yml"}, + "foo/bar/qux": {".semgrep.yml"}, + }, + ), + # at the top level should be okay too + ((".semgrep.yml", ".semgrep/foo.yml"), {"": {".semgrep.yml", ".semgrep/foo.yml"}}), + ], +) +def test_group_by_semgrep_dir(paths: tuple[str, ...], expected: dict[str, set[str]]): + assert tailor._group_by_semgrep_dir(paths) == expected + + +@pytest.fixture +def rule_runner() -> RuleRunner: + return RuleRunner( + rules=[ + *tailor.rules(), + QueryRule(PutativeTargets, (PutativeSemgrepTargetsRequest, AllOwnedSources)), + ], + target_types=[SemgrepRuleSourcesGeneratorTarget], + ) + + +def test_find_putative_targets(rule_runner: RuleRunner) -> None: + rule_runner.write_files( + { + "src/owned/.semgrep.yml": "rules: []", + "src/owned/sub/.semgrep/foo.yaml": "rules: []", + "src/unowned/.semgrep.yaml": "rules: []", + "src/unowned/sub/.semgrep/foo.yml": "rules: []", + "src/unowned/sub/.semgrep/bar.yaml": "rules: []", + "src/unowned/sub/.semgrep.yml": "rules: []", + # other YAML files aren't always Semgrep + "src/unowned/not_obviously_semgrep.yaml": "rules: []", + } + ) + + putative_targets = rule_runner.request( + PutativeTargets, + [ + PutativeSemgrepTargetsRequest(("src/owned", "src/unowned", "src/unowned/sub")), + AllOwnedSources(["src/owned/.semgrep.yml", "src/owned/sub/.semgrep/foo.yaml"]), + ], + ) + + assert putative_targets == PutativeTargets( + [ + PutativeTarget.for_target_type( + SemgrepRuleSourcesGeneratorTarget, + "src/unowned", + "semgrep", + [".semgrep.yaml"], + ), + PutativeTarget.for_target_type( + SemgrepRuleSourcesGeneratorTarget, + "src/unowned/sub", + "semgrep", + [".semgrep.yml", ".semgrep/bar.yaml", ".semgrep/foo.yml"], + ), + ], + ) + + +def test_find_putative_targets_when_disabled(rule_runner: RuleRunner) -> None: + rule_runner.write_files( + { + "src/unowned/.semgrep.yml": "{}", + } + ) + + rule_runner.set_options(["--no-semgrep-tailor-rule-targets"]) + + putative_targets = rule_runner.request( + PutativeTargets, + [ + PutativeSemgrepTargetsRequest(("src/unowned",)), + AllOwnedSources(), + ], + ) + assert putative_targets == PutativeTargets() diff --git a/src/python/pants/backend/tools/semgrep/target_types.py b/src/python/pants/backend/tools/semgrep/target_types.py index 5b81f0dfc04..6557867ee10 100644 --- a/src/python/pants/backend/tools/semgrep/target_types.py +++ b/src/python/pants/backend/tools/semgrep/target_types.py @@ -11,7 +11,6 @@ SingleSourceField, Target, TargetFilesGenerator, - TargetFilesGeneratorSettingsRequest, generate_multiple_sources_field_help_message, ) @@ -40,10 +39,6 @@ class SemgrepRuleSource(Target): help = "A single source file containing Semgrep rules" -class SemgrepRuleGeneratorSettingsRequest(TargetFilesGeneratorSettingsRequest): - pass - - class SemgrepRuleSourcesGeneratorTarget(TargetFilesGenerator): alias = "semgrep_rule_sources" core_fields = ( @@ -53,5 +48,4 @@ class SemgrepRuleSourcesGeneratorTarget(TargetFilesGenerator): generated_target_cls = SemgrepRuleSource copied_fields = COMMON_TARGET_FIELDS moved_fields = () - settings_request_cls = SemgrepRuleGeneratorSettingsRequest help = "Generate a `semgrep_rule_source` target for each file in the `sources` field." diff --git a/src/python/pants/option/global_options.py b/src/python/pants/option/global_options.py index 0d8095e75e6..f48962214f6 100644 --- a/src/python/pants/option/global_options.py +++ b/src/python/pants/option/global_options.py @@ -894,7 +894,7 @@ class BootstrapOptions: ) pants_ignore = StrListOption( advanced=True, - default=[".*/", _default_rel_distdir, "__pycache__", "!.semgrep", "!.semgrep.yml"], + default=[".*/", _default_rel_distdir, "__pycache__", "!.semgrep/"], help=softwrap( """ Paths to ignore for all filesystem operations performed by pants From 2bb067269b0c759499f54c82cd8731e7754c05a8 Mon Sep 17 00:00:00 2001 From: Huon Wilson Date: Sun, 2 Apr 2023 11:13:02 +1000 Subject: [PATCH 14/49] register target types --- .../pants/backend/experimental/tools/semgrep/register.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/python/pants/backend/experimental/tools/semgrep/register.py b/src/python/pants/backend/experimental/tools/semgrep/register.py index 002cb2d0355..6bbd4199a80 100644 --- a/src/python/pants/backend/experimental/tools/semgrep/register.py +++ b/src/python/pants/backend/experimental/tools/semgrep/register.py @@ -14,10 +14,18 @@ from pants.backend.python.goals import lockfile as python_lockfile from pants.backend.tools.semgrep import rules as semgrep_rules from pants.backend.tools.semgrep import subsystem as subsystem +from pants.backend.tools.semgrep.target_types import ( + SemgrepRuleSource, + SemgrepRuleSourcesGeneratorTarget, +) from pants.engine.rules import Rule from pants.engine.unions import UnionRule +def target_types(): + return [SemgrepRuleSource, SemgrepRuleSourcesGeneratorTarget] + + def rules() -> Iterable[Rule | UnionRule]: return ( *semgrep_rules.rules(), From 323f693bed1adf1f60a3d40e084a156f5fe70e80 Mon Sep 17 00:00:00 2001 From: Huon Wilson Date: Sun, 2 Apr 2023 11:20:19 +1000 Subject: [PATCH 15/49] implement dependency inference: NB. full dependency --- .../experimental/tools/semgrep/register.py | 2 + .../tools/semgrep/dependency_inference.py | 78 +++++++++++++++++++ 2 files changed, 80 insertions(+) create mode 100644 src/python/pants/backend/tools/semgrep/dependency_inference.py diff --git a/src/python/pants/backend/experimental/tools/semgrep/register.py b/src/python/pants/backend/experimental/tools/semgrep/register.py index 6bbd4199a80..602b276d012 100644 --- a/src/python/pants/backend/experimental/tools/semgrep/register.py +++ b/src/python/pants/backend/experimental/tools/semgrep/register.py @@ -12,6 +12,7 @@ from typing import Iterable from pants.backend.python.goals import lockfile as python_lockfile +from pants.backend.tools.semgrep import dependency_inference from pants.backend.tools.semgrep import rules as semgrep_rules from pants.backend.tools.semgrep import subsystem as subsystem from pants.backend.tools.semgrep.target_types import ( @@ -31,4 +32,5 @@ def rules() -> Iterable[Rule | UnionRule]: *semgrep_rules.rules(), *subsystem.rules(), *python_lockfile.rules(), + *dependency_inference.rules(), ) diff --git a/src/python/pants/backend/tools/semgrep/dependency_inference.py b/src/python/pants/backend/tools/semgrep/dependency_inference.py new file mode 100644 index 00000000000..448f08d909d --- /dev/null +++ b/src/python/pants/backend/tools/semgrep/dependency_inference.py @@ -0,0 +1,78 @@ +# Copyright 2023 Pants project contributors (see CONTRIBUTORS.md). +# Licensed under the Apache License, Version 2.0 (see LICENSE). +from __future__ import annotations + +import os +from collections import defaultdict +from dataclasses import dataclass + +from pants.backend.tools.semgrep.target_types import SemgrepRuleSourceField +from pants.build_graph.address import Address +from pants.engine.rules import collect_rules, rule +from pants.engine.target import ( + AllTargets, + Dependencies, + FieldSet, + InferDependenciesRequest, + InferredDependencies, + SingleSourceField, + Target, +) +from pants.engine.unions import UnionRule + +# class SemgrepDependenciesField(SpecialCasedDependencies): +# alias = "semgrep_dependencies" +# default = () +# help = "Which semgrep rules to use for this file" + + +class SemgrepDependencyInferenceFieldSet(FieldSet): + required_fields = (SingleSourceField, Dependencies) + + source = SingleSourceField + dependencies = Dependencies + + +@dataclass(frozen=True) +class InferSemgrepDependenciesRequest(InferDependenciesRequest): + infer_from = SemgrepDependencyInferenceFieldSet + + +@dataclass +class AllSemgrepConfigs: + targets: dict[str, list[Target]] + + +@rule +async def find_all_semgrep_configs(all_targets: AllTargets) -> AllSemgrepConfigs: + targets = defaultdict(list) + for tgt in all_targets: + if tgt.has_field(SemgrepRuleSourceField): + targets[tgt.address.spec_path].append(tgt) + return AllSemgrepConfigs(targets) + + +@rule +async def infer_semgrep_dependencies( + request: InferSemgrepDependenciesRequest, all_semgrep: AllSemgrepConfigs +) -> InferredDependencies: + spec = request.field_set.address.spec_path + found: list[Address] = [] + + while True: + found.extend(tgt.address for tgt in all_semgrep.targets.get(spec, [])) + + if not spec: + break + + spec = os.path.dirname(spec) + + return InferredDependencies(include=found) + + +def rules(): + return [ + *collect_rules(), + UnionRule(InferDependenciesRequest, InferSemgrepDependenciesRequest), + # Target.register_plugin_field(SemgrepDependenciesField), # + ] From d08611f6deb7c26fa9c3435d5582ad15b6eeef3e Mon Sep 17 00:00:00 2001 From: Huon Wilson Date: Sun, 2 Apr 2023 13:57:30 +1000 Subject: [PATCH 16/49] Run Semgrep using FieldSet/targets, partitioning by config --- .../pants/backend/tools/semgrep/rules.py | 85 +++++++++---------- .../pants/backend/tools/semgrep/subsystem.py | 14 +++ 2 files changed, 53 insertions(+), 46 deletions(-) diff --git a/src/python/pants/backend/tools/semgrep/rules.py b/src/python/pants/backend/tools/semgrep/rules.py index 03f8c8d9e80..a2e9c367de3 100644 --- a/src/python/pants/backend/tools/semgrep/rules.py +++ b/src/python/pants/backend/tools/semgrep/rules.py @@ -2,74 +2,67 @@ # Licensed under the Apache License, Version 2.0 (see LICENSE). from __future__ import annotations +from collections import defaultdict from dataclasses import dataclass -from typing import Any, Iterable +from typing import Iterable from pants.backend.python.util_rules.pex import PexRequest, VenvPex, VenvPexProcess -from pants.core.goals.lint import LintFilesRequest, LintResult -from pants.core.util_rules.partitions import PartitionerType, Partitions -from pants.engine.fs import ( - CreateDigest, - Digest, - FileContent, - GlobMatchErrorBehavior, - MergeDigests, - PathGlobs, -) -from pants.engine.internals.native_engine import FilespecMatcher, Snapshot +from pants.core.goals.lint import LintResult, LintTargetsRequest +from pants.core.util_rules.partitions import Partition, Partitions +from pants.core.util_rules.source_files import SourceFiles, SourceFilesRequest +from pants.engine.fs import CreateDigest, Digest, FileContent, MergeDigests from pants.engine.process import FallibleProcessResult from pants.engine.rules import Get, MultiGet, Rule, collect_rules, rule +from pants.engine.target import DependenciesRequest, Targets from pants.engine.unions import UnionRule from pants.option.global_options import GlobalOptions from pants.util.logging import LogLevel from pants.util.strutil import pluralize -from .subsystem import Semgrep +from .subsystem import Semgrep, SemgrepFieldSet +from .target_types import SemgrepRuleSourceField -class SemgrepRequest(LintFilesRequest): +class SemgrepRequest(LintTargetsRequest): + field_set_type = SemgrepFieldSet tool_subsystem = Semgrep - partitioner_type = PartitionerType.CUSTOM - @dataclass(frozen=True) -class SemgrepConfigFilesRequest: - pass - +class PartitionMetadata: + config_files: frozenset[SemgrepRuleSourceField] -@dataclass(frozen=True) -class SemgrepConfigFiles: - snapshot: Snapshot + @property + def description(self) -> str: + return ", ".join(sorted(field.value for field in self.config_files)) @rule -async def gather_config_files( - request: SemgrepConfigFilesRequest, semgrep: Semgrep -) -> SemgrepConfigFiles: - globs = [f"**/{glob}" for glob in semgrep.config_globs] - config_files_snapshot = await Get( - Snapshot, - PathGlobs( - globs=globs, - glob_match_error_behavior=GlobMatchErrorBehavior.error, - description_of_origin="the option `--semgrep-config-globs`", - ), +async def partition( + request: SemgrepRequest.PartitionRequest[SemgrepFieldSet], semgrep: Semgrep +) -> Partitions: + if semgrep.skip: + return Partitions() + + dependencies = await MultiGet( + Get(Targets, DependenciesRequest(field_set.dependencies)) + for field_set in request.field_sets ) - return SemgrepConfigFiles(snapshot=config_files_snapshot) + by_config = defaultdict(list) -@rule -async def partition(request: SemgrepRequest.PartitionRequest, semgrep: Semgrep) -> Partitions: - if semgrep.skip: - return Partitions() + for field_set, deps in zip(request.field_sets, dependencies): + semgrep_configs = frozenset( + d[SemgrepRuleSourceField] for d in deps if d.has_field(SemgrepRuleSourceField) + ) - matching_files = FilespecMatcher( - includes=semgrep.file_glob_include, excludes=semgrep.file_glob_exclude - ).matches(request.files) + by_config[semgrep_configs].append(field_set) # TODO: partition by config - return Partitions.single_partition(matching_files) + return Partitions( + Partition(tuple(field_sets), PartitionMetadata(configs)) + for configs, field_sets in by_config.items() + ) # We have a hard-coded settings file to side-step @@ -82,19 +75,19 @@ async def partition(request: SemgrepRequest.PartitionRequest, semgrep: Semgrep) @rule(desc="Lint with Semgrep", level=LogLevel.DEBUG) async def lint( - request: SemgrepRequest.Batch[str, Any], + request: SemgrepRequest.Batch[SemgrepFieldSet, PartitionMetadata], semgrep: Semgrep, global_options: GlobalOptions, ) -> LintResult: config_files, semgrep_pex, input_files, settings = await MultiGet( - Get(SemgrepConfigFiles, SemgrepConfigFilesRequest()), + Get(SourceFiles, SourceFilesRequest(request.partition_metadata.config_files)), Get(VenvPex, PexRequest, semgrep.to_pex_request()), - Get(Snapshot, PathGlobs(globs=request.elements)), + Get(SourceFiles, SourceFilesRequest(field_set.source for field_set in request.elements)), Get(Digest, CreateDigest([_DEFAULT_SETTINGS])), ) input_digest = await Get( - Digest, MergeDigests((input_files.digest, config_files.snapshot.digest, settings)) + Digest, MergeDigests((input_files.snapshot.digest, config_files.snapshot.digest, settings)) ) # TODO: https://github.com/pantsbuild/pants/issues/18430 support running this with --autofix diff --git a/src/python/pants/backend/tools/semgrep/subsystem.py b/src/python/pants/backend/tools/semgrep/subsystem.py index 6092e1825a3..9e054dc1ff5 100644 --- a/src/python/pants/backend/tools/semgrep/subsystem.py +++ b/src/python/pants/backend/tools/semgrep/subsystem.py @@ -3,6 +3,7 @@ from __future__ import annotations +from dataclasses import dataclass from typing import Iterable from pants.backend.python.goals import lockfile @@ -13,11 +14,24 @@ from pants.backend.python.util_rules.pex_requirements import GeneratePythonToolLockfileSentinel from pants.core.goals.generate_lockfiles import GenerateToolLockfileSentinel from pants.engine.rules import Rule, collect_rules, rule +from pants.engine.target import Dependencies, FieldSet, SingleSourceField, Target from pants.engine.unions import UnionRule from pants.option.option_types import ArgsListOption, BoolOption, SkipOption, StrListOption from pants.util.docutil import git_url +@dataclass(frozen=True) +class SemgrepFieldSet(FieldSet): + required_fields = (SingleSourceField, Dependencies) + source: SingleSourceField + dependencies: Dependencies + + @classmethod + def opt_out(cls, tgt: Target) -> bool: + # FIXME: global skip_semgrep field? + return False + + class Semgrep(PythonToolBase): name = "Semgrep" options_scope = "semgrep" From 798ad6c5d3e42a629f599bb5654694b0a5dd1690 Mon Sep 17 00:00:00 2001 From: Huon Wilson Date: Sun, 2 Apr 2023 14:23:22 +1000 Subject: [PATCH 17/49] avoid generating partitions with no configs --- src/python/pants/backend/tools/semgrep/rules.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/python/pants/backend/tools/semgrep/rules.py b/src/python/pants/backend/tools/semgrep/rules.py index a2e9c367de3..422707f3d93 100644 --- a/src/python/pants/backend/tools/semgrep/rules.py +++ b/src/python/pants/backend/tools/semgrep/rules.py @@ -56,7 +56,8 @@ async def partition( d[SemgrepRuleSourceField] for d in deps if d.has_field(SemgrepRuleSourceField) ) - by_config[semgrep_configs].append(field_set) + if semgrep_configs: + by_config[semgrep_configs].append(field_set) # TODO: partition by config return Partitions( From a38171134105929a247969f6afd1aaee09732452 Mon Sep 17 00:00:00 2001 From: Huon Wilson Date: Sun, 2 Apr 2023 14:33:27 +1000 Subject: [PATCH 18/49] ignore files, tweaks --- .../pants/backend/tools/semgrep/rules.py | 48 +++++++++++++++++-- 1 file changed, 43 insertions(+), 5 deletions(-) diff --git a/src/python/pants/backend/tools/semgrep/rules.py b/src/python/pants/backend/tools/semgrep/rules.py index 422707f3d93..966de6dde11 100644 --- a/src/python/pants/backend/tools/semgrep/rules.py +++ b/src/python/pants/backend/tools/semgrep/rules.py @@ -2,6 +2,7 @@ # Licensed under the Apache License, Version 2.0 (see LICENSE). from __future__ import annotations +import logging from collections import defaultdict from dataclasses import dataclass from typing import Iterable @@ -10,18 +11,20 @@ from pants.core.goals.lint import LintResult, LintTargetsRequest from pants.core.util_rules.partitions import Partition, Partitions from pants.core.util_rules.source_files import SourceFiles, SourceFilesRequest -from pants.engine.fs import CreateDigest, Digest, FileContent, MergeDigests +from pants.engine.fs import CreateDigest, Digest, FileContent, MergeDigests, PathGlobs, Snapshot from pants.engine.process import FallibleProcessResult from pants.engine.rules import Get, MultiGet, Rule, collect_rules, rule from pants.engine.target import DependenciesRequest, Targets from pants.engine.unions import UnionRule from pants.option.global_options import GlobalOptions from pants.util.logging import LogLevel -from pants.util.strutil import pluralize +from pants.util.strutil import pluralize, softwrap from .subsystem import Semgrep, SemgrepFieldSet from .target_types import SemgrepRuleSourceField +logger = logging.getLogger(__name__) + class SemgrepRequest(LintTargetsRequest): field_set_type = SemgrepFieldSet @@ -49,8 +52,8 @@ async def partition( for field_set in request.field_sets ) + # partition by the sets of configs that apply to each input by_config = defaultdict(list) - for field_set, deps in zip(request.field_sets, dependencies): semgrep_configs = frozenset( d[SemgrepRuleSourceField] for d in deps if d.has_field(SemgrepRuleSourceField) @@ -59,13 +62,39 @@ async def partition( if semgrep_configs: by_config[semgrep_configs].append(field_set) - # TODO: partition by config return Partitions( Partition(tuple(field_sets), PartitionMetadata(configs)) for configs, field_sets in by_config.items() ) +@dataclass +class SemgrepIgnoreFiles: + digest: Digest + + +@rule +async def find_semgrep_ignore_files() -> SemgrepIgnoreFiles: + # if .semgrep gets support for nested ignore files, these should be involved in config + # partitioning too + ignore_name = ".semgrepignore" + ignore_files = await Get(Snapshot, PathGlobs([f"**/{ignore_name}"])) + non_root_ignores = sorted(name for name in ignore_files.files if name != ignore_name) + if non_root_ignores: + # https://github.com/returntocorp/semgrep/issues/5669 + logger.warning( + softwrap( + f""" + Semgrep does not support {ignore_name} files anywhere other than its working + directory, which is the build root when running under pants. These files will be + ignored: {', '.join(non_root_ignores)} + """ + ) + ) + + return SemgrepIgnoreFiles(digest=ignore_files.digest) + + # We have a hard-coded settings file to side-step # https://github.com/returntocorp/semgrep/issues/7102, and also provide more cacheability. _DEFAULT_SETTINGS = FileContent( @@ -78,6 +107,7 @@ async def partition( async def lint( request: SemgrepRequest.Batch[SemgrepFieldSet, PartitionMetadata], semgrep: Semgrep, + ignore_files: SemgrepIgnoreFiles, global_options: GlobalOptions, ) -> LintResult: config_files, semgrep_pex, input_files, settings = await MultiGet( @@ -88,7 +118,15 @@ async def lint( ) input_digest = await Get( - Digest, MergeDigests((input_files.snapshot.digest, config_files.snapshot.digest, settings)) + Digest, + MergeDigests( + ( + input_files.snapshot.digest, + config_files.snapshot.digest, + settings, + ignore_files.digest, + ) + ), ) # TODO: https://github.com/pantsbuild/pants/issues/18430 support running this with --autofix From 987b4847c6b36abbcabdc87a73851600de6e721e Mon Sep 17 00:00:00 2001 From: Huon Wilson Date: Sun, 2 Apr 2023 19:45:38 +1000 Subject: [PATCH 19/49] allow ignore files to work by not passig all args --- .../pants/backend/tools/semgrep/rules.py | 75 +++++++++++-------- .../pants/backend/tools/semgrep/subsystem.py | 5 ++ 2 files changed, 48 insertions(+), 32 deletions(-) diff --git a/src/python/pants/backend/tools/semgrep/rules.py b/src/python/pants/backend/tools/semgrep/rules.py index 966de6dde11..7e942e9a7c9 100644 --- a/src/python/pants/backend/tools/semgrep/rules.py +++ b/src/python/pants/backend/tools/semgrep/rules.py @@ -34,19 +34,56 @@ class SemgrepRequest(LintTargetsRequest): @dataclass(frozen=True) class PartitionMetadata: config_files: frozenset[SemgrepRuleSourceField] + ignore_files: Snapshot @property def description(self) -> str: return ", ".join(sorted(field.value for field in self.config_files)) +_IGNORE_FILE_NAME = ".semgrepignore" + + +def warn_about_ignore_files_if_required(ignore_files: Snapshot, semgrep: Semgrep) -> None: + non_root_files = sorted(name for name in ignore_files.files if name != _IGNORE_FILE_NAME) + if non_root_files and not semgrep.acknowledge_nested_semgrepignore_files_are_not_used: + # https://github.com/returntocorp/semgrep/issues/5669 + logger.warning( + softwrap( + f""" + Semgrep does not obey {_IGNORE_FILE_NAME} outside the working directory, which is + the build root when run by pants. These files may not have the desired effect: + {', '.join(non_root_files)} + + Set `acknowledge_nested_semgrepignore_files_are_not_used = true` in the `[semgrep]` + section of pants.toml to silence this warning. + """ + ) + ) + + +@dataclass +class SemgrepIgnoreFiles: + snapshot: Snapshot + + +@rule +async def all_semgrep_ignore_files() -> SemgrepIgnoreFiles: + snapshot = await Get(Snapshot, PathGlobs([f"**/{_IGNORE_FILE_NAME}"])) + return SemgrepIgnoreFiles(snapshot) + + @rule async def partition( - request: SemgrepRequest.PartitionRequest[SemgrepFieldSet], semgrep: Semgrep + request: SemgrepRequest.PartitionRequest[SemgrepFieldSet], + semgrep: Semgrep, + ignore_files: SemgrepIgnoreFiles, ) -> Partitions: if semgrep.skip: return Partitions() + warn_about_ignore_files_if_required(ignore_files.snapshot, semgrep) + dependencies = await MultiGet( Get(Targets, DependenciesRequest(field_set.dependencies)) for field_set in request.field_sets @@ -63,38 +100,11 @@ async def partition( by_config[semgrep_configs].append(field_set) return Partitions( - Partition(tuple(field_sets), PartitionMetadata(configs)) + Partition(tuple(field_sets), PartitionMetadata(configs, ignore_files.snapshot)) for configs, field_sets in by_config.items() ) -@dataclass -class SemgrepIgnoreFiles: - digest: Digest - - -@rule -async def find_semgrep_ignore_files() -> SemgrepIgnoreFiles: - # if .semgrep gets support for nested ignore files, these should be involved in config - # partitioning too - ignore_name = ".semgrepignore" - ignore_files = await Get(Snapshot, PathGlobs([f"**/{ignore_name}"])) - non_root_ignores = sorted(name for name in ignore_files.files if name != ignore_name) - if non_root_ignores: - # https://github.com/returntocorp/semgrep/issues/5669 - logger.warning( - softwrap( - f""" - Semgrep does not support {ignore_name} files anywhere other than its working - directory, which is the build root when running under pants. These files will be - ignored: {', '.join(non_root_ignores)} - """ - ) - ) - - return SemgrepIgnoreFiles(digest=ignore_files.digest) - - # We have a hard-coded settings file to side-step # https://github.com/returntocorp/semgrep/issues/7102, and also provide more cacheability. _DEFAULT_SETTINGS = FileContent( @@ -107,7 +117,6 @@ async def find_semgrep_ignore_files() -> SemgrepIgnoreFiles: async def lint( request: SemgrepRequest.Batch[SemgrepFieldSet, PartitionMetadata], semgrep: Semgrep, - ignore_files: SemgrepIgnoreFiles, global_options: GlobalOptions, ) -> LintResult: config_files, semgrep_pex, input_files, settings = await MultiGet( @@ -124,7 +133,7 @@ async def lint( input_files.snapshot.digest, config_files.snapshot.digest, settings, - ignore_files.digest, + request.partition_metadata.ignore_files.digest, ) ), ) @@ -143,7 +152,9 @@ async def lint( "{pants_concurrency}", "--error", *semgrep.args, - *input_files.files, + # we don't pass the target files, because semgrep does its own file traversal, and + # letting it do so is the only way for .semgrepignore files to be obeyed + # https://github.com/returntocorp/semgrep/issues/4978 ), extra_env={ "SEMGREP_FORCE_COLOR": "true", diff --git a/src/python/pants/backend/tools/semgrep/subsystem.py b/src/python/pants/backend/tools/semgrep/subsystem.py index 9e054dc1ff5..b863344b4da 100644 --- a/src/python/pants/backend/tools/semgrep/subsystem.py +++ b/src/python/pants/backend/tools/semgrep/subsystem.py @@ -77,6 +77,11 @@ class Semgrep(PythonToolBase): advanced=True, ) + acknowledge_nested_semgrepignore_files_are_not_used = BoolOption( + default=False, + help="Set to true suppress the warning about `.semgrepignore` files not at the build root not being used", + ) + class SemgrepLockfileSentinel(GeneratePythonToolLockfileSentinel): resolve_name = Semgrep.options_scope From 613fc835947f66dae632225220cbf907a3b6d8d4 Mon Sep 17 00:00:00 2001 From: Huon Wilson Date: Sun, 2 Apr 2023 19:48:03 +1000 Subject: [PATCH 20/49] remove now-unused glob files --- .../pants/backend/tools/semgrep/subsystem.py | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/src/python/pants/backend/tools/semgrep/subsystem.py b/src/python/pants/backend/tools/semgrep/subsystem.py index b863344b4da..d950f0c67ac 100644 --- a/src/python/pants/backend/tools/semgrep/subsystem.py +++ b/src/python/pants/backend/tools/semgrep/subsystem.py @@ -50,23 +50,6 @@ class Semgrep(PythonToolBase): export = ExportToolOption() - config_globs = StrListOption( - "--config-globs", - default=[".semgrep.yml", ".semgrep/*.yml"], - help="File globs that contain semgrep rule configurations", - ) - - file_glob_include = StrListOption( - "--include", - help="Glob for which files to lint.", - ) - - file_glob_exclude = StrListOption( - "--exclude", - default=[], - help="Glob for which files to exclude from linting.", - ) - args = ArgsListOption(example="--verbose") skip = SkipOption("lint") From d0f5d4e2a51819baa050f86a82c103c3176466de Mon Sep 17 00:00:00 2001 From: Huon Wilson Date: Fri, 14 Apr 2023 20:45:34 +1000 Subject: [PATCH 21/49] Add --force option --- src/python/pants/backend/tools/semgrep/rules.py | 5 ++++- src/python/pants/backend/tools/semgrep/subsystem.py | 6 ++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/python/pants/backend/tools/semgrep/rules.py b/src/python/pants/backend/tools/semgrep/rules.py index 7e942e9a7c9..9c761426c56 100644 --- a/src/python/pants/backend/tools/semgrep/rules.py +++ b/src/python/pants/backend/tools/semgrep/rules.py @@ -12,7 +12,7 @@ from pants.core.util_rules.partitions import Partition, Partitions from pants.core.util_rules.source_files import SourceFiles, SourceFilesRequest from pants.engine.fs import CreateDigest, Digest, FileContent, MergeDigests, PathGlobs, Snapshot -from pants.engine.process import FallibleProcessResult +from pants.engine.process import FallibleProcessResult, ProcessCacheScope from pants.engine.rules import Get, MultiGet, Rule, collect_rules, rule from pants.engine.target import DependenciesRequest, Targets from pants.engine.unions import UnionRule @@ -167,6 +167,9 @@ async def lint( concurrency_available=len(input_files.files), description=f"Run Semgrep on {pluralize(len(input_files.files), 'file')}.", level=LogLevel.DEBUG, + cache_scope=ProcessCacheScope.PER_SESSION + if semgrep.force + else ProcessCacheScope.SUCCESSFUL, ), ) diff --git a/src/python/pants/backend/tools/semgrep/subsystem.py b/src/python/pants/backend/tools/semgrep/subsystem.py index d950f0c67ac..26097d260da 100644 --- a/src/python/pants/backend/tools/semgrep/subsystem.py +++ b/src/python/pants/backend/tools/semgrep/subsystem.py @@ -65,6 +65,12 @@ class Semgrep(PythonToolBase): help="Set to true suppress the warning about `.semgrepignore` files not at the build root not being used", ) + force = BoolOption( + default=False, + help="If true, semgrep is always run, even if the input files haven't changed. This can be used to run cloud rulesets like `--semgrep-force --semgrep-args='--config p/python`.", + advanced=True, + ) + class SemgrepLockfileSentinel(GeneratePythonToolLockfileSentinel): resolve_name = Semgrep.options_scope From b86ba09dd07de313e91175298cc6654dbec5eb92 Mon Sep 17 00:00:00 2001 From: Huon Wilson Date: Fri, 14 Apr 2023 20:51:48 +1000 Subject: [PATCH 22/49] register tailor rules --- src/python/pants/backend/experimental/tools/semgrep/register.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/python/pants/backend/experimental/tools/semgrep/register.py b/src/python/pants/backend/experimental/tools/semgrep/register.py index 602b276d012..05bf1ae7599 100644 --- a/src/python/pants/backend/experimental/tools/semgrep/register.py +++ b/src/python/pants/backend/experimental/tools/semgrep/register.py @@ -15,6 +15,7 @@ from pants.backend.tools.semgrep import dependency_inference from pants.backend.tools.semgrep import rules as semgrep_rules from pants.backend.tools.semgrep import subsystem as subsystem +from pants.backend.tools.semgrep import tailor from pants.backend.tools.semgrep.target_types import ( SemgrepRuleSource, SemgrepRuleSourcesGeneratorTarget, @@ -33,4 +34,5 @@ def rules() -> Iterable[Rule | UnionRule]: *subsystem.rules(), *python_lockfile.rules(), *dependency_inference.rules(), + *tailor.rules(), ) From 3efeac6c970d9da430a503c956298f48a972541b Mon Sep 17 00:00:00 2001 From: Huon Wilson Date: Fri, 14 Apr 2023 21:26:42 +1000 Subject: [PATCH 23/49] Start sketching tests --- .../semgrep/dependency_inference_test.py | 8 ++ .../tools/semgrep/rules_integration_test.py | 115 ++++++++++++++++++ 2 files changed, 123 insertions(+) create mode 100644 src/python/pants/backend/tools/semgrep/dependency_inference_test.py create mode 100644 src/python/pants/backend/tools/semgrep/rules_integration_test.py diff --git a/src/python/pants/backend/tools/semgrep/dependency_inference_test.py b/src/python/pants/backend/tools/semgrep/dependency_inference_test.py new file mode 100644 index 00000000000..0d99c846287 --- /dev/null +++ b/src/python/pants/backend/tools/semgrep/dependency_inference_test.py @@ -0,0 +1,8 @@ +# Copyright 2023 Pants project contributors (see CONTRIBUTORS.md). +# Licensed under the Apache License, Version 2.0 (see LICENSE). + +from __future__ import annotations + + +def text_fixme() -> None: + raise NotImplementedError() diff --git a/src/python/pants/backend/tools/semgrep/rules_integration_test.py b/src/python/pants/backend/tools/semgrep/rules_integration_test.py new file mode 100644 index 00000000000..2af8736d333 --- /dev/null +++ b/src/python/pants/backend/tools/semgrep/rules_integration_test.py @@ -0,0 +1,115 @@ +# Copyright 2023 Pants project contributors (see CONTRIBUTORS.md). +# Licensed under the Apache License, Version 2.0 (see LICENSE). + +from __future__ import annotations + +from textwrap import dedent +from typing import Sequence + +import pytest + +from pants.core.goals.lint import LintResult, Partitions +from pants.core.target_types import FileTarget +from pants.core.util_rules import source_files +from pants.core.util_rules.source_files import SourceFiles, SourceFilesRequest +from pants.engine.addresses import Address +from pants.engine.internals.native_engine import EMPTY_DIGEST +from pants.engine.target import Target +from pants.testutil.python_interpreter_selection import all_major_minor_python_versions +from pants.testutil.rule_runner import QueryRule, RuleRunner + +from .dependency_inference import rules as dependency_inference_rules +from .rules import PartitionMetadata, SemgrepRequest +from .rules import rules as semgrep_rules +from .subsystem import Semgrep, SemgrepFieldSet +from .subsystem import rules as semgrep_subsystem_rules +from .target_types import SemgrepRuleSource, SemgrepRuleSourcesGeneratorTarget + +DIR = "src" + +GOOD_FILE = "good_pattern" +BAD_FILE = "bad_pattern" +RULES = dedent( + """\ + rules: + - id: find-bad-pattern + patterns: + - pattern: bad_pattern + message: >- + bad pattern found! + languages: [generic] + severity: ERROR + """ +) + + +@pytest.fixture +def rule_runner() -> RuleRunner: + return RuleRunner( + rules=[ + *semgrep_rules(), + *semgrep_subsystem_rules(), + *dependency_inference_rules(), + *source_files.rules(), + QueryRule(Partitions, (SemgrepRequest.PartitionRequest,)), + QueryRule(LintResult, (SemgrepRequest.Batch,)), + QueryRule(SourceFiles, (SourceFilesRequest,)), + ], + target_types=[SemgrepRuleSource, SemgrepRuleSourcesGeneratorTarget, FileTarget], + ) + + +def run_semgrep( + rule_runner: RuleRunner, + targets: list[Target], + *, + extra_args: Sequence[str] = (), +) -> tuple[LintResult, ...]: + rule_runner.set_options(["--backend-packages=pants.backend.tools.semgrep", *extra_args]) + partitions = rule_runner.request( + Partitions[SemgrepFieldSet, PartitionMetadata], + [SemgrepRequest.PartitionRequest(tuple(SemgrepFieldSet.create(tgt) for tgt in targets))], + ) + + return tuple( + rule_runner.request( + LintResult, [SemgrepRequest.Batch("", partition.elements, partition.metadata)] + ) + for partition in partitions + ) + + +def assert_success( + rule_runner: RuleRunner, target: Target, *, extra_args: Sequence[str] = () +) -> None: + result = run_semgrep(rule_runner, [target], extra_args=extra_args) + + assert len(result) == 1 + assert "FIXME FIXME" in result[0].stdout + assert result[0].exit_code == 0 + assert result[0].report == EMPTY_DIGEST + + +@pytest.mark.parametrize( + "major_minor_interpreter", + all_major_minor_python_versions(Semgrep.default_interpreter_constraints), +) +def test_passing(rule_runner: RuleRunner, major_minor_interpreter: str) -> None: + rule_runner.write_files( + { + f"{DIR}/file.txt": GOOD_FILE, + f"{DIR}/.semgrep.yml": RULES, + f"{DIR}/BUILD": dedent( + """\ + file(name="f", source="file.txt") + semgrep_rule_sources(name="s") + """ + ), + } + ) + tgt = rule_runner.get_target(Address(DIR, target_name="f")) + assert_success( + rule_runner, + tgt, + extra_args=[f"--python-interpreter-constraints=['=={major_minor_interpreter}.*']"], + ) From a405f7bbce053cb7380ebfcaabd23bf6aa7ec226 Mon Sep 17 00:00:00 2001 From: Huon Wilson Date: Fri, 14 Apr 2023 21:26:54 +1000 Subject: [PATCH 24/49] minor clean-up --- src/python/pants/backend/tools/semgrep/rules.py | 3 ++- src/python/pants/backend/tools/semgrep/subsystem.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/python/pants/backend/tools/semgrep/rules.py b/src/python/pants/backend/tools/semgrep/rules.py index 9c761426c56..cd1977549a4 100644 --- a/src/python/pants/backend/tools/semgrep/rules.py +++ b/src/python/pants/backend/tools/semgrep/rules.py @@ -7,6 +7,7 @@ from dataclasses import dataclass from typing import Iterable +from pants.backend.python.util_rules import pex from pants.backend.python.util_rules.pex import PexRequest, VenvPex, VenvPexProcess from pants.core.goals.lint import LintResult, LintTargetsRequest from pants.core.util_rules.partitions import Partition, Partitions @@ -177,4 +178,4 @@ async def lint( def rules() -> Iterable[Rule | UnionRule]: - return [*collect_rules(), *SemgrepRequest.rules()] + return [*collect_rules(), *SemgrepRequest.rules(), *pex.rules()] diff --git a/src/python/pants/backend/tools/semgrep/subsystem.py b/src/python/pants/backend/tools/semgrep/subsystem.py index 26097d260da..6eb816647f9 100644 --- a/src/python/pants/backend/tools/semgrep/subsystem.py +++ b/src/python/pants/backend/tools/semgrep/subsystem.py @@ -16,7 +16,7 @@ from pants.engine.rules import Rule, collect_rules, rule from pants.engine.target import Dependencies, FieldSet, SingleSourceField, Target from pants.engine.unions import UnionRule -from pants.option.option_types import ArgsListOption, BoolOption, SkipOption, StrListOption +from pants.option.option_types import ArgsListOption, BoolOption, SkipOption from pants.util.docutil import git_url From 1fad45ee0469737cda80eda9829b04c6c354c08c Mon Sep 17 00:00:00 2001 From: Huon Wilson Date: Wed, 19 Apr 2023 21:26:31 +1000 Subject: [PATCH 25/49] basic test passing --- .../pants/backend/tools/semgrep/rules.py | 11 +++-- .../tools/semgrep/rules_integration_test.py | 41 +++++++++++++++++-- 2 files changed, 46 insertions(+), 6 deletions(-) diff --git a/src/python/pants/backend/tools/semgrep/rules.py b/src/python/pants/backend/tools/semgrep/rules.py index cd1977549a4..33aa256979f 100644 --- a/src/python/pants/backend/tools/semgrep/rules.py +++ b/src/python/pants/backend/tools/semgrep/rules.py @@ -153,9 +153,14 @@ async def lint( "{pants_concurrency}", "--error", *semgrep.args, - # we don't pass the target files, because semgrep does its own file traversal, and - # letting it do so is the only way for .semgrepignore files to be obeyed - # https://github.com/returntocorp/semgrep/issues/4978 + # we don't pass the target files directly because that overrides .semgrepignore + # (https://github.com/returntocorp/semgrep/issues/4978), so instead we just tell its + # traversal to include all the source files in this partition. Unfortunately this + # include is implicitly unrooted (i.e. as if it was **/path/to/file), and so may + # pick up other files if the names match. The highest risk of this is within the + # semgrep PEX. + *(f"--include={f}" for f in input_files.files), + f"--exclude={semgrep_pex.pex_filename}", ), extra_env={ "SEMGREP_FORCE_COLOR": "true", diff --git a/src/python/pants/backend/tools/semgrep/rules_integration_test.py b/src/python/pants/backend/tools/semgrep/rules_integration_test.py index 2af8736d333..7792e07ce82 100644 --- a/src/python/pants/backend/tools/semgrep/rules_integration_test.py +++ b/src/python/pants/backend/tools/semgrep/rules_integration_test.py @@ -27,7 +27,10 @@ DIR = "src" -GOOD_FILE = "good_pattern" +# https://semgrep.dev/docs/cli-reference/#exit-codes +SEMGREP_ERROR_FAILURE_RETURN_CODE = 1 + +GOOD_FILE = "nothing_bad" BAD_FILE = "bad_pattern" RULES = dedent( """\ @@ -39,6 +42,10 @@ bad pattern found! languages: [generic] severity: ERROR + paths: + # 'generic' means this finds itself + exclude: + - '*.yml' """ ) @@ -65,7 +72,10 @@ def run_semgrep( *, extra_args: Sequence[str] = (), ) -> tuple[LintResult, ...]: - rule_runner.set_options(["--backend-packages=pants.backend.tools.semgrep", *extra_args]) + rule_runner.set_options( + ["--backend-packages=pants.backend.tools.semgrep", *extra_args], + env_inherit={"PATH", "PYENV_ROOT", "HOME"}, + ) partitions = rule_runner.request( Partitions[SemgrepFieldSet, PartitionMetadata], [SemgrepRequest.PartitionRequest(tuple(SemgrepFieldSet.create(tgt) for tgt in targets))], @@ -85,7 +95,7 @@ def assert_success( result = run_semgrep(rule_runner, [target], extra_args=extra_args) assert len(result) == 1 - assert "FIXME FIXME" in result[0].stdout + assert "Ran 1 rule on 1 file: 0 findings" in result[0].stderr assert result[0].exit_code == 0 assert result[0].report == EMPTY_DIGEST @@ -113,3 +123,28 @@ def test_passing(rule_runner: RuleRunner, major_minor_interpreter: str) -> None: tgt, extra_args=[f"--python-interpreter-constraints=['=={major_minor_interpreter}.*']"], ) + + +def test_failing(rule_runner: RuleRunner) -> None: + rule_runner.write_files( + { + f"{DIR}/file.txt": BAD_FILE, + f"{DIR}/.semgrep.yml": RULES, + f"{DIR}/BUILD": dedent( + """\ + file(name="f", source="file.txt") + semgrep_rule_sources(name="s") + """ + ), + } + ) + tgt = rule_runner.get_target(Address(DIR, target_name="f")) + + result = run_semgrep( + rule_runner, + [tgt], + ) + assert len(result) == 1 + assert "Ran 1 rule on 1 file: 1 finding" in result[0].stderr + assert result[0].exit_code == SEMGREP_ERROR_FAILURE_RETURN_CODE + assert result[0].report == EMPTY_DIGEST From f71db2539faf696c7ec3fac72b2ba4723c4d939b Mon Sep 17 00:00:00 2001 From: Huon Wilson Date: Fri, 21 Apr 2023 08:53:45 +1000 Subject: [PATCH 26/49] a bunch more tests, xfail --force test --- .../tools/semgrep/rules_integration_test.py | 275 ++++++++++++++++-- 1 file changed, 250 insertions(+), 25 deletions(-) diff --git a/src/python/pants/backend/tools/semgrep/rules_integration_test.py b/src/python/pants/backend/tools/semgrep/rules_integration_test.py index 7792e07ce82..209e029dfc0 100644 --- a/src/python/pants/backend/tools/semgrep/rules_integration_test.py +++ b/src/python/pants/backend/tools/semgrep/rules_integration_test.py @@ -31,7 +31,7 @@ SEMGREP_ERROR_FAILURE_RETURN_CODE = 1 GOOD_FILE = "nothing_bad" -BAD_FILE = "bad_pattern" +BAD_FILE = "bad_pattern\nalso_bad" RULES = dedent( """\ rules: @@ -48,6 +48,40 @@ - '*.yml' """ ) +RULES2 = dedent( + """\ + rules: + - id: find-another-bad-pattern + patterns: + - pattern: also_bad + message: >- + second bad found! + languages: [generic] + severity: ERROR + paths: + # 'generic' means this finds itself + exclude: + - '*.yml' + """ +) + +SINGLE_FILE_BUILD = dedent( + """\ + file(name="f", source="file.txt") + semgrep_rule_sources(name="s") + """ +) + +BAD_FILE_LAYOUT = { + f"{DIR}/file.txt": BAD_FILE, + f"{DIR}/.semgrep.yml": RULES, + f"{DIR}/BUILD": SINGLE_FILE_BUILD, +} +GOOD_FILE_LAYOUT = { + f"{DIR}/file.txt": GOOD_FILE, + f"{DIR}/.semgrep.yml": RULES, + f"{DIR}/BUILD": SINGLE_FILE_BUILD, +} @pytest.fixture @@ -92,12 +126,14 @@ def run_semgrep( def assert_success( rule_runner: RuleRunner, target: Target, *, extra_args: Sequence[str] = () ) -> None: - result = run_semgrep(rule_runner, [target], extra_args=extra_args) + results = run_semgrep(rule_runner, [target], extra_args=extra_args) - assert len(result) == 1 - assert "Ran 1 rule on 1 file: 0 findings" in result[0].stderr - assert result[0].exit_code == 0 - assert result[0].report == EMPTY_DIGEST + assert len(results) == 1 + result = results[0] + assert result.stdout == "" + assert "Ran 1 rule on 1 file: 0 findings" in result.stderr + assert result.exit_code == 0 + assert result.report == EMPTY_DIGEST @pytest.mark.parametrize( @@ -105,18 +141,7 @@ def assert_success( all_major_minor_python_versions(Semgrep.default_interpreter_constraints), ) def test_passing(rule_runner: RuleRunner, major_minor_interpreter: str) -> None: - rule_runner.write_files( - { - f"{DIR}/file.txt": GOOD_FILE, - f"{DIR}/.semgrep.yml": RULES, - f"{DIR}/BUILD": dedent( - """\ - file(name="f", source="file.txt") - semgrep_rule_sources(name="s") - """ - ), - } - ) + rule_runner.write_files(GOOD_FILE_LAYOUT) tgt = rule_runner.get_target(Address(DIR, target_name="f")) assert_success( rule_runner, @@ -126,25 +151,225 @@ def test_passing(rule_runner: RuleRunner, major_minor_interpreter: str) -> None: def test_failing(rule_runner: RuleRunner) -> None: + rule_runner.write_files(BAD_FILE_LAYOUT) + tgt = rule_runner.get_target(Address(DIR, target_name="f")) + + results = run_semgrep(rule_runner, [tgt]) + assert len(results) == 1 + result = results[0] + assert "find-bad-pattern" in result.stdout + assert "Ran 1 rule on 1 file: 1 finding" in result.stderr + assert result.exit_code == SEMGREP_ERROR_FAILURE_RETURN_CODE + assert result.report == EMPTY_DIGEST + + +def test_multiple_targets(rule_runner: RuleRunner) -> None: rule_runner.write_files( { - f"{DIR}/file.txt": BAD_FILE, + f"{DIR}/good.txt": GOOD_FILE, + f"{DIR}/bad.txt": BAD_FILE, f"{DIR}/.semgrep.yml": RULES, f"{DIR}/BUILD": dedent( """\ - file(name="f", source="file.txt") + file(name="g", source="good.txt") + file(name="b", source="bad.txt") semgrep_rule_sources(name="s") """ ), } ) + + tgts = [rule_runner.get_target(Address(DIR, target_name=name)) for name in ["g", "b"]] + + results = run_semgrep( + rule_runner, + tgts, + ) + assert len(results) == 1 + result = results[0] + assert "find-bad-pattern" in result.stdout + assert "Ran 1 rule on 2 files: 1 finding" in result.stderr + assert result.exit_code == SEMGREP_ERROR_FAILURE_RETURN_CODE + assert result.report == EMPTY_DIGEST + + +@pytest.mark.parametrize( + "files", + [ + pytest.param( + { + **BAD_FILE_LAYOUT, + ".semgrep.yml": RULES2, + "BUILD": """semgrep_rule_sources(name="s")""", + }, + id="via nesting", + ), + pytest.param( + { + f"{DIR}/bad.txt": BAD_FILE, + f"{DIR}/BUILD": """file(name="f", source="bad.txt")""", + ".semgrep/one.yml": RULES, + ".semgrep/two.yml": RULES2, + "BUILD": """semgrep_rule_sources(name="s")""", + }, + id="via .semgrep directory", + ), + ], +) +def test_multiple_configs(rule_runner: RuleRunner, files: dict[str, str]) -> None: + rule_runner.write_files(files) + + tgt = rule_runner.get_target(Address(DIR, target_name="f")) + results = run_semgrep(rule_runner, [tgt]) + + assert len(results) == 1 + result = results[0] + assert "find-bad-pattern" in result.stdout + assert "find-another-bad-pattern" in result.stdout + assert "Ran 2 rules on 1 file: 2 findings" in result.stderr + assert result.exit_code == SEMGREP_ERROR_FAILURE_RETURN_CODE + assert result.report == EMPTY_DIGEST + + +def test_semgrepignore(rule_runner: RuleRunner) -> None: + rule_runner.write_files({**BAD_FILE_LAYOUT, ".semgrepignore": "file.txt"}) + + tgt = rule_runner.get_target(Address(DIR, target_name="f")) + results = run_semgrep(rule_runner, [tgt]) + + assert len(results) == 1 + result = results[0] + assert result.stdout == "" + assert "Ran 1 rule on 0 files: 0 findings" in result.stderr + assert result.exit_code == 0 + assert result.report == EMPTY_DIGEST + + +def test_semgrepignore_nested_ignored(rule_runner: RuleRunner, caplog) -> None: + rule_runner.write_files({**BAD_FILE_LAYOUT, f"{DIR}/.semgrepignore": "file.txt"}) + tgt = rule_runner.get_target(Address(DIR, target_name="f")) + results = run_semgrep(rule_runner, [tgt]) - result = run_semgrep( + # a nested semgrep ignore file is completely unused... + assert len(results) == 1 + result = results[0] + assert "find-bad-pattern" in result.stdout + assert "Ran 1 rule on 1 file: 1 finding" in result.stderr + assert result.exit_code == SEMGREP_ERROR_FAILURE_RETURN_CODE + assert result.report == EMPTY_DIGEST + + # ...but there's a pants warning about it... + assert "Semgrep does not obey .semgrepignore outside the working directory" in caplog.text + assert f"{DIR}/.semgrepignore" in caplog.text + + caplog.clear() + + # ... that can be silenced + results = run_semgrep( rule_runner, [tgt], + extra_args=["--semgrep-acknowledge-nested-semgrepignore-files-are-not-used"], + ) + assert not caplog.records + + +def test_partition_by_config(rule_runner: RuleRunner) -> None: + file_dirs = [] + + def file___(dir: str) -> dict[str, str]: + file_dirs.append(dir) + return { + f"{dir}/file.txt": GOOD_FILE, + f"{dir}/BUILD": """file(name="f", source="file.txt")""", + } + + def semgrep(dir: str) -> dict[str, str]: + return {f"{dir}/.semgrep.yml": RULES, f"{dir}/BUILD": """semgrep_rule_sources(name="s")"""} + + rule_runner.write_files( + { + # 'y'/'n' indicates whether that level has semgrep config + **semgrep("y"), + **file___("y/dir1"), + **file___("y/dir2"), + **file___("y/n/dir1"), + **file___("y/n/dir2"), + **semgrep("y/y"), + **file___("y/y/dir1"), + **file___("y/y/dir2"), + **file___("n"), + } + ) + + field_sets = tuple( + SemgrepFieldSet.create(rule_runner.get_target(Address(dir, target_name="f"))) + for dir in file_dirs + ) + + partitions = rule_runner.request( + Partitions[SemgrepFieldSet, PartitionMetadata], + [SemgrepRequest.PartitionRequest(field_sets)], ) - assert len(result) == 1 - assert "Ran 1 rule on 1 file: 1 finding" in result[0].stderr - assert result[0].exit_code == SEMGREP_ERROR_FAILURE_RETURN_CODE - assert result[0].report == EMPTY_DIGEST + + sorted_partitions = sorted( + ( + sorted(field_set.address.spec for field_set in partition.elements), + sorted(f.address.filename for f in partition.metadata.config_files), + ) + for partition in partitions + ) + + assert sorted_partitions == [ + ( + ["y/dir1:f", "y/dir2:f", "y/n/dir1:f", "y/n/dir2:f"], + ["y/.semgrep.yml"], + ), + ( + ["y/y/dir1:f", "y/y/dir2:f"], + ["y/.semgrep.yml", "y/y/.semgrep.yml"], + ), + # n: doesn't appear in any partition + ] + + +def test_skip(rule_runner: RuleRunner) -> None: + rule_runner.write_files(BAD_FILE_LAYOUT) + tgt = rule_runner.get_target(Address(DIR, target_name="f")) + + results = run_semgrep(rule_runner, [tgt], extra_args=["--semgrep-skip"]) + assert not results + + +@pytest.mark.xfail +def test_force(rule_runner: RuleRunner) -> None: + rule_runner.write_files(GOOD_FILE_LAYOUT) + tgt = rule_runner.get_target(Address(DIR, target_name="f")) + + # Should not receive a memoized result if force=True. + results1 = run_semgrep(rule_runner, [tgt], extra_args=["--semgrep-force"]) + results2 = run_semgrep(rule_runner, [tgt], extra_args=["--semgrep-force"]) + + assert len(results1) == len(results2) == 1 + assert results1[0].exit_code == results2[0].exit_code == 0 + assert results1[0] is not results2[0] + + # But should if force=False. + results1 = run_semgrep(rule_runner, [tgt]) + results2 = run_semgrep(rule_runner, [tgt]) + assert len(results1) == len(results2) == 1 + assert results1[0].exit_code == results2[0].exit_code == 0 + assert results1[0] is results2[0] + + +def test_extra_args(rule_runner: RuleRunner) -> None: + rule_runner.write_files(BAD_FILE_LAYOUT) + tgt = rule_runner.get_target(Address(DIR, target_name="f")) + + results = run_semgrep(rule_runner, [tgt], extra_args=["--semgrep-args=--quiet"]) + assert len(results) == 1 + result = results[0] + assert "find-bad-pattern" in result.stdout + assert result.stderr == "" + assert result.exit_code == SEMGREP_ERROR_FAILURE_RETURN_CODE + assert result.report == EMPTY_DIGEST From 42f96833d5518a47023a33b9644eb539c335253f Mon Sep 17 00:00:00 2001 From: Huon Wilson Date: Fri, 21 Apr 2023 17:48:35 +1000 Subject: [PATCH 27/49] Note --semgrep-force test behaviour --- src/python/pants/backend/tools/semgrep/rules.py | 6 +++--- .../pants/backend/tools/semgrep/rules_integration_test.py | 6 +++++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/python/pants/backend/tools/semgrep/rules.py b/src/python/pants/backend/tools/semgrep/rules.py index 33aa256979f..7c20b5afcdb 100644 --- a/src/python/pants/backend/tools/semgrep/rules.py +++ b/src/python/pants/backend/tools/semgrep/rules.py @@ -139,6 +139,8 @@ async def lint( ), ) + cache_scope = ProcessCacheScope.PER_SESSION if semgrep.force else ProcessCacheScope.SUCCESSFUL + # TODO: https://github.com/pantsbuild/pants/issues/18430 support running this with --autofix # under the fix goal... but not all rules have fixes, so we need to be running with # --error/checking exit codes, which FixResult doesn't currently support. @@ -173,9 +175,7 @@ async def lint( concurrency_available=len(input_files.files), description=f"Run Semgrep on {pluralize(len(input_files.files), 'file')}.", level=LogLevel.DEBUG, - cache_scope=ProcessCacheScope.PER_SESSION - if semgrep.force - else ProcessCacheScope.SUCCESSFUL, + cache_scope=cache_scope, ), ) diff --git a/src/python/pants/backend/tools/semgrep/rules_integration_test.py b/src/python/pants/backend/tools/semgrep/rules_integration_test.py index 209e029dfc0..87d28f503ff 100644 --- a/src/python/pants/backend/tools/semgrep/rules_integration_test.py +++ b/src/python/pants/backend/tools/semgrep/rules_integration_test.py @@ -341,7 +341,11 @@ def test_skip(rule_runner: RuleRunner) -> None: assert not results -@pytest.mark.xfail +@pytest.mark.xfail( + reason=""" TODO: --semgrep-force does rerun the underlying process, but the LintResult's + contents are the same (same stdout etc.), these are deduped, and thus we cannot detect the + rerun""" +) def test_force(rule_runner: RuleRunner) -> None: rule_runner.write_files(GOOD_FILE_LAYOUT) tgt = rule_runner.get_target(Address(DIR, target_name="f")) From ec9c1fd593a4d5820e861cf656e4f9234f420a14 Mon Sep 17 00:00:00 2001 From: Huon Wilson Date: Fri, 21 Apr 2023 18:04:06 +1000 Subject: [PATCH 28/49] Minor fixes --- src/python/pants/backend/tools/semgrep/rules.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/python/pants/backend/tools/semgrep/rules.py b/src/python/pants/backend/tools/semgrep/rules.py index 7c20b5afcdb..da46b638842 100644 --- a/src/python/pants/backend/tools/semgrep/rules.py +++ b/src/python/pants/backend/tools/semgrep/rules.py @@ -39,7 +39,7 @@ class PartitionMetadata: @property def description(self) -> str: - return ", ".join(sorted(field.value for field in self.config_files)) + return ", ".join(sorted(field.file_path for field in self.config_files)) _IGNORE_FILE_NAME = ".semgrepignore" @@ -151,8 +151,7 @@ async def lint( argv=( "scan", *(f"--config={f}" for f in config_files.snapshot.files), - "-j", - "{pants_concurrency}", + "--jobs={pants_concurrency}", "--error", *semgrep.args, # we don't pass the target files directly because that overrides .semgrepignore From ec79db07204d19f9da6537b499429abab3929fa1 Mon Sep 17 00:00:00 2001 From: Huon Wilson Date: Fri, 21 Apr 2023 18:04:18 +1000 Subject: [PATCH 29/49] Test semgrep PEX exclusion --- .../tools/semgrep/rules_integration_test.py | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/python/pants/backend/tools/semgrep/rules_integration_test.py b/src/python/pants/backend/tools/semgrep/rules_integration_test.py index 87d28f503ff..d4a5f6a2295 100644 --- a/src/python/pants/backend/tools/semgrep/rules_integration_test.py +++ b/src/python/pants/backend/tools/semgrep/rules_integration_test.py @@ -377,3 +377,31 @@ def test_extra_args(rule_runner: RuleRunner) -> None: assert result.stderr == "" assert result.exit_code == SEMGREP_ERROR_FAILURE_RETURN_CODE assert result.report == EMPTY_DIGEST + + +def test_semgrep_pex_contents_is_ignored(rule_runner: RuleRunner) -> None: + rule_runner.write_files( + { + # Validate that this doesn't traverse into the PEX created for semgrep itself: a + # top-level __main__.py will translate into --include=__main__.py (aka **/__main__.py) + # which will, naively, find the __main__.py in the PEX. + "__main__.py": "", + ".semgrep.yml": RULES, + "BUILD": dedent( + """\ + file(name="f", source="__main__.py") + semgrep_rule_sources(name="s") + """ + ) + } + ) + + tgt = rule_runner.get_target(Address("", target_name="f")) + results = run_semgrep(rule_runner, [tgt]) + + assert len(results) == 1 + result = results[0] + assert result.stdout == "" + # Without the --exclude, this would run on 2 files. + assert "Ran 1 rule on 1 file: 0 findings" in result.stderr + assert result.exit_code == 0 From d7ff26ed76c2696879126674dda9a43637239568 Mon Sep 17 00:00:00 2001 From: Huon Wilson Date: Fri, 21 Apr 2023 18:27:08 +1000 Subject: [PATCH 30/49] default to --quiet --- .../tools/semgrep/rules_integration_test.py | 21 ++++++++++++++++++- .../pants/backend/tools/semgrep/subsystem.py | 6 +++++- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/python/pants/backend/tools/semgrep/rules_integration_test.py b/src/python/pants/backend/tools/semgrep/rules_integration_test.py index d4a5f6a2295..1fbc5d9578a 100644 --- a/src/python/pants/backend/tools/semgrep/rules_integration_test.py +++ b/src/python/pants/backend/tools/semgrep/rules_integration_test.py @@ -105,7 +105,13 @@ def run_semgrep( targets: list[Target], *, extra_args: Sequence[str] = (), + use_default_semgrep_args: bool = False, ) -> tuple[LintResult, ...]: + if not use_default_semgrep_args: + # clear out the default --quiet so that we can check the 'Ran ... rules on ... files: + # ... findings' output + extra_args = ("--semgrep-args=[]", *extra_args) + rule_runner.set_options( ["--backend-packages=pants.backend.tools.semgrep", *extra_args], env_inherit={"PATH", "PYENV_ROOT", "HOME"}, @@ -366,6 +372,19 @@ def test_force(rule_runner: RuleRunner) -> None: assert results1[0] is results2[0] +def test_default_args(rule_runner: RuleRunner) -> None: + rule_runner.write_files(BAD_FILE_LAYOUT) + tgt = rule_runner.get_target(Address(DIR, target_name="f")) + + results = run_semgrep(rule_runner, [tgt], use_default_semgrep_args=True) + assert len(results) == 1 + result = results[0] + assert "find-bad-pattern" in result.stdout + assert result.stderr == "" + assert result.exit_code == SEMGREP_ERROR_FAILURE_RETURN_CODE + assert result.report == EMPTY_DIGEST + + def test_extra_args(rule_runner: RuleRunner) -> None: rule_runner.write_files(BAD_FILE_LAYOUT) tgt = rule_runner.get_target(Address(DIR, target_name="f")) @@ -392,7 +411,7 @@ def test_semgrep_pex_contents_is_ignored(rule_runner: RuleRunner) -> None: file(name="f", source="__main__.py") semgrep_rule_sources(name="s") """ - ) + ), } ) diff --git a/src/python/pants/backend/tools/semgrep/subsystem.py b/src/python/pants/backend/tools/semgrep/subsystem.py index 6eb816647f9..f11418eab7e 100644 --- a/src/python/pants/backend/tools/semgrep/subsystem.py +++ b/src/python/pants/backend/tools/semgrep/subsystem.py @@ -50,7 +50,11 @@ class Semgrep(PythonToolBase): export = ExportToolOption() - args = ArgsListOption(example="--verbose") + args = ArgsListOption( + example="--verbose", + default=["--quiet"], + extra_help="This includes --quiet by default to reduce the volume of output.", + ) skip = SkipOption("lint") From b390be25577c93a7f4286792b6c4800b1a230372 Mon Sep 17 00:00:00 2001 From: Huon Wilson Date: Fri, 21 Apr 2023 18:46:08 +1000 Subject: [PATCH 31/49] replace explicit dependency inference with implicit --- .../experimental/tools/semgrep/register.py | 2 - .../tools/semgrep/dependency_inference.py | 78 ------------------- .../semgrep/dependency_inference_test.py | 8 -- .../pants/backend/tools/semgrep/rules.py | 50 +++++++++++- .../tools/semgrep/rules_integration_test.py | 2 - 5 files changed, 47 insertions(+), 93 deletions(-) delete mode 100644 src/python/pants/backend/tools/semgrep/dependency_inference.py delete mode 100644 src/python/pants/backend/tools/semgrep/dependency_inference_test.py diff --git a/src/python/pants/backend/experimental/tools/semgrep/register.py b/src/python/pants/backend/experimental/tools/semgrep/register.py index 05bf1ae7599..01bb51096ff 100644 --- a/src/python/pants/backend/experimental/tools/semgrep/register.py +++ b/src/python/pants/backend/experimental/tools/semgrep/register.py @@ -12,7 +12,6 @@ from typing import Iterable from pants.backend.python.goals import lockfile as python_lockfile -from pants.backend.tools.semgrep import dependency_inference from pants.backend.tools.semgrep import rules as semgrep_rules from pants.backend.tools.semgrep import subsystem as subsystem from pants.backend.tools.semgrep import tailor @@ -33,6 +32,5 @@ def rules() -> Iterable[Rule | UnionRule]: *semgrep_rules.rules(), *subsystem.rules(), *python_lockfile.rules(), - *dependency_inference.rules(), *tailor.rules(), ) diff --git a/src/python/pants/backend/tools/semgrep/dependency_inference.py b/src/python/pants/backend/tools/semgrep/dependency_inference.py deleted file mode 100644 index 448f08d909d..00000000000 --- a/src/python/pants/backend/tools/semgrep/dependency_inference.py +++ /dev/null @@ -1,78 +0,0 @@ -# Copyright 2023 Pants project contributors (see CONTRIBUTORS.md). -# Licensed under the Apache License, Version 2.0 (see LICENSE). -from __future__ import annotations - -import os -from collections import defaultdict -from dataclasses import dataclass - -from pants.backend.tools.semgrep.target_types import SemgrepRuleSourceField -from pants.build_graph.address import Address -from pants.engine.rules import collect_rules, rule -from pants.engine.target import ( - AllTargets, - Dependencies, - FieldSet, - InferDependenciesRequest, - InferredDependencies, - SingleSourceField, - Target, -) -from pants.engine.unions import UnionRule - -# class SemgrepDependenciesField(SpecialCasedDependencies): -# alias = "semgrep_dependencies" -# default = () -# help = "Which semgrep rules to use for this file" - - -class SemgrepDependencyInferenceFieldSet(FieldSet): - required_fields = (SingleSourceField, Dependencies) - - source = SingleSourceField - dependencies = Dependencies - - -@dataclass(frozen=True) -class InferSemgrepDependenciesRequest(InferDependenciesRequest): - infer_from = SemgrepDependencyInferenceFieldSet - - -@dataclass -class AllSemgrepConfigs: - targets: dict[str, list[Target]] - - -@rule -async def find_all_semgrep_configs(all_targets: AllTargets) -> AllSemgrepConfigs: - targets = defaultdict(list) - for tgt in all_targets: - if tgt.has_field(SemgrepRuleSourceField): - targets[tgt.address.spec_path].append(tgt) - return AllSemgrepConfigs(targets) - - -@rule -async def infer_semgrep_dependencies( - request: InferSemgrepDependenciesRequest, all_semgrep: AllSemgrepConfigs -) -> InferredDependencies: - spec = request.field_set.address.spec_path - found: list[Address] = [] - - while True: - found.extend(tgt.address for tgt in all_semgrep.targets.get(spec, [])) - - if not spec: - break - - spec = os.path.dirname(spec) - - return InferredDependencies(include=found) - - -def rules(): - return [ - *collect_rules(), - UnionRule(InferDependenciesRequest, InferSemgrepDependenciesRequest), - # Target.register_plugin_field(SemgrepDependenciesField), # - ] diff --git a/src/python/pants/backend/tools/semgrep/dependency_inference_test.py b/src/python/pants/backend/tools/semgrep/dependency_inference_test.py deleted file mode 100644 index 0d99c846287..00000000000 --- a/src/python/pants/backend/tools/semgrep/dependency_inference_test.py +++ /dev/null @@ -1,8 +0,0 @@ -# Copyright 2023 Pants project contributors (see CONTRIBUTORS.md). -# Licensed under the Apache License, Version 2.0 (see LICENSE). - -from __future__ import annotations - - -def text_fixme() -> None: - raise NotImplementedError() diff --git a/src/python/pants/backend/tools/semgrep/rules.py b/src/python/pants/backend/tools/semgrep/rules.py index da46b638842..a4d6f92f0c8 100644 --- a/src/python/pants/backend/tools/semgrep/rules.py +++ b/src/python/pants/backend/tools/semgrep/rules.py @@ -3,6 +3,7 @@ from __future__ import annotations import logging +import os from collections import defaultdict from dataclasses import dataclass from typing import Iterable @@ -15,7 +16,7 @@ from pants.engine.fs import CreateDigest, Digest, FileContent, MergeDigests, PathGlobs, Snapshot from pants.engine.process import FallibleProcessResult, ProcessCacheScope from pants.engine.rules import Get, MultiGet, Rule, collect_rules, rule -from pants.engine.target import DependenciesRequest, Targets +from pants.engine.target import AllTargets, Target, Targets from pants.engine.unions import UnionRule from pants.option.global_options import GlobalOptions from pants.util.logging import LogLevel @@ -68,6 +69,50 @@ class SemgrepIgnoreFiles: snapshot: Snapshot +@dataclass +class AllSemgrepConfigs: + targets: dict[str, list[Target]] + + +@rule +async def find_all_semgrep_configs(all_targets: AllTargets) -> AllSemgrepConfigs: + targets = defaultdict(list) + for tgt in all_targets: + if tgt.has_field(SemgrepRuleSourceField): + targets[tgt.address.spec_path].append(tgt) + return AllSemgrepConfigs(targets) + + +@dataclass(frozen=True) +class InferSemgrepDependenciesRequest: + field_set: SemgrepFieldSet + + +@rule +async def infer_semgrep_dependencies( + request: InferSemgrepDependenciesRequest, all_semgrep: AllSemgrepConfigs +) -> Targets: + # TODO: introspect the semgrep rules and determine which (if any) apply to the files, e.g. a # + # Python file shouldn't depend on a .semgrep.yml that doesn't have any 'python' or 'generic' # + # rules, and similarly if there's path inclusions/exclusions. + # TODO: this would be better as actual dependency inference (e.g. allows inspection, manual + # addition/exclusion), but that can only infer 'full' dependencies and it is wrong (e.g. JVM + # things break) for real code files to depend on this sort of non-code linter config; requires + # dependency scopes or similar (https://github.com/pantsbuild/pants/issues/12794) + spec = request.field_set.address.spec_path + found: list[Target] = [] + + while True: + found.extend(all_semgrep.targets.get(spec, [])) + + if not spec: + break + + spec = os.path.dirname(spec) + + return Targets(found) + + @rule async def all_semgrep_ignore_files() -> SemgrepIgnoreFiles: snapshot = await Get(Snapshot, PathGlobs([f"**/{_IGNORE_FILE_NAME}"])) @@ -86,8 +131,7 @@ async def partition( warn_about_ignore_files_if_required(ignore_files.snapshot, semgrep) dependencies = await MultiGet( - Get(Targets, DependenciesRequest(field_set.dependencies)) - for field_set in request.field_sets + Get(Targets, InferSemgrepDependenciesRequest(field_set)) for field_set in request.field_sets ) # partition by the sets of configs that apply to each input diff --git a/src/python/pants/backend/tools/semgrep/rules_integration_test.py b/src/python/pants/backend/tools/semgrep/rules_integration_test.py index 1fbc5d9578a..7a077d39e63 100644 --- a/src/python/pants/backend/tools/semgrep/rules_integration_test.py +++ b/src/python/pants/backend/tools/semgrep/rules_integration_test.py @@ -18,7 +18,6 @@ from pants.testutil.python_interpreter_selection import all_major_minor_python_versions from pants.testutil.rule_runner import QueryRule, RuleRunner -from .dependency_inference import rules as dependency_inference_rules from .rules import PartitionMetadata, SemgrepRequest from .rules import rules as semgrep_rules from .subsystem import Semgrep, SemgrepFieldSet @@ -90,7 +89,6 @@ def rule_runner() -> RuleRunner: rules=[ *semgrep_rules(), *semgrep_subsystem_rules(), - *dependency_inference_rules(), *source_files.rules(), QueryRule(Partitions, (SemgrepRequest.PartitionRequest,)), QueryRule(LintResult, (SemgrepRequest.Batch,)), From a208b90bc921b1d4474f10a0ad6752588fa6c28d Mon Sep 17 00:00:00 2001 From: Huon Wilson Date: Sat, 22 Apr 2023 10:35:51 +1000 Subject: [PATCH 32/49] New lockfile, update version --- .../bin/generate_builtin_lockfiles.py | 2 + .../pants/backend/tools/semgrep/semgrep.lock | 382 +++++++++--------- .../pants/backend/tools/semgrep/subsystem.py | 7 +- 3 files changed, 194 insertions(+), 197 deletions(-) diff --git a/build-support/bin/generate_builtin_lockfiles.py b/build-support/bin/generate_builtin_lockfiles.py index b92e9c46f94..8ee3bf2ab42 100644 --- a/build-support/bin/generate_builtin_lockfiles.py +++ b/build-support/bin/generate_builtin_lockfiles.py @@ -50,6 +50,7 @@ from pants.backend.scala.lint.scalafmt.subsystem import ScalafmtSubsystem from pants.backend.scala.subsystems.scalatest import Scalatest from pants.backend.terraform.dependency_inference import TerraformHcl2Parser +from pants.backend.tools.semgrep.subsystem import Semgrep from pants.backend.tools.yamllint.subsystem import Yamllint from pants.base.build_environment import get_buildroot from pants.jvm.resolve.jvm_tool import JvmToolBase @@ -121,6 +122,7 @@ class JvmTool(Tool[JvmToolBase]): PythonTool(Pylint, "pants.backend.python.lint.pylint"), PythonTool(PythonProtobufMypyPlugin, "pants.backend.codegen.protobuf.python"), PythonTool(PyOxidizer, "pants.backend.experimental.python.packaging.pyoxidizer"), + PythonTool(Semgrep, "pants.backend.experimental.tools.semgrep"), PythonTool(Setuptools, "pants.backend.python"), PythonTool(SetuptoolsSCM, "pants.backend.python"), PythonTool(TerraformHcl2Parser, "pants.backend.experimental.terraform"), diff --git a/src/python/pants/backend/tools/semgrep/semgrep.lock b/src/python/pants/backend/tools/semgrep/semgrep.lock index 628c3523b0e..b1434ff52af 100644 --- a/src/python/pants/backend/tools/semgrep/semgrep.lock +++ b/src/python/pants/backend/tools/semgrep/semgrep.lock @@ -1,6 +1,6 @@ // This lockfile was autogenerated by Pants. To regenerate, run: // -// build-support/bin/generate_all_lockfiles.sh +// ./pants run build-support/bin/generate_builtin_lockfiles.py // // --- BEGIN PANTS LOCKFILE METADATA: DO NOT EDIT OR REMOVE --- // { @@ -9,7 +9,7 @@ // "CPython<4,>=3.7" // ], // "generated_with_requirements": [ -// "semgrep==1.14.0" +// "semgrep<2,>=1.19.0" // ], // "manylinux": "manylinux2014", // "requirement_constraints": [], @@ -31,13 +31,13 @@ "artifacts": [ { "algorithm": "sha256", - "hash": "29e95c7f6778868dbd49170f98f8818f78f3dc5e0e37c0b1f474e3561b240836", - "url": "https://files.pythonhosted.org/packages/fb/6e/6f83bf616d2becdf333a1640f1d463fef3150e2e926b7010cb0f81c95e88/attrs-22.2.0-py3-none-any.whl" + "hash": "1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04", + "url": "https://files.pythonhosted.org/packages/f0/eb/fcb708c7bf5056045e9e98f62b93bd7467eb718b0202e7698eb11d66416c/attrs-23.1.0-py3-none-any.whl" }, { "algorithm": "sha256", - "hash": "c9227bfc2f01993c03f68db37d1d15c9690188323c067c641f1a35ca58185f99", - "url": "https://files.pythonhosted.org/packages/21/31/3f468da74c7de4fcf9b25591e682856389b3400b4b62f201e65f15ea3e07/attrs-22.2.0.tar.gz" + "hash": "6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015", + "url": "https://files.pythonhosted.org/packages/97/90/81f95d5f705be17872843536b1868f351805acf6971251ff07c1b8334dbb/attrs-23.1.0.tar.gz" } ], "project_name": "attrs", @@ -46,32 +46,26 @@ "attrs[tests-no-zope]; extra == \"tests\"", "attrs[tests]; extra == \"cov\"", "cloudpickle; platform_python_implementation == \"CPython\" and extra == \"tests-no-zope\"", - "cloudpickle; platform_python_implementation == \"CPython\" and extra == \"tests_no_zope\"", - "coverage-enable-subprocess; extra == \"cov\"", "coverage[toml]>=5.3; extra == \"cov\"", "furo; extra == \"docs\"", "hypothesis; extra == \"tests-no-zope\"", - "hypothesis; extra == \"tests_no_zope\"", - "mypy<0.990,>=0.971; platform_python_implementation == \"CPython\" and extra == \"tests-no-zope\"", - "mypy<0.990,>=0.971; platform_python_implementation == \"CPython\" and extra == \"tests_no_zope\"", + "importlib-metadata; python_version < \"3.8\"", + "mypy>=1.1.1; platform_python_implementation == \"CPython\" and extra == \"tests-no-zope\"", "myst-parser; extra == \"docs\"", + "pre-commit; extra == \"dev\"", "pympler; extra == \"tests-no-zope\"", - "pympler; extra == \"tests_no_zope\"", - "pytest-mypy-plugins; (platform_python_implementation == \"CPython\" and python_version < \"3.11\") and extra == \"tests-no-zope\"", - "pytest-mypy-plugins; (platform_python_implementation == \"CPython\" and python_version < \"3.11\") and extra == \"tests_no_zope\"", + "pytest-mypy-plugins; platform_python_implementation == \"CPython\" and python_version < \"3.11\" and extra == \"tests-no-zope\"", "pytest-xdist[psutil]; extra == \"tests-no-zope\"", - "pytest-xdist[psutil]; extra == \"tests_no_zope\"", "pytest>=4.3.0; extra == \"tests-no-zope\"", - "pytest>=4.3.0; extra == \"tests_no_zope\"", "sphinx-notfound-page; extra == \"docs\"", "sphinx; extra == \"docs\"", "sphinxcontrib-towncrier; extra == \"docs\"", "towncrier; extra == \"docs\"", - "zope.interface; extra == \"docs\"", - "zope.interface; extra == \"tests\"" + "zope-interface; extra == \"docs\"", + "zope-interface; extra == \"tests\"" ], - "requires_python": ">=3.6", - "version": "22.2.0" + "requires_python": ">=3.7", + "version": "23.1.0" }, { "artifacts": [ @@ -131,334 +125,334 @@ "artifacts": [ { "algorithm": "sha256", - "hash": "7e189e2e1d3ed2f4aebabd2d5b0f931e883676e51c7624826e0a4e5fe8a0bf24", - "url": "https://files.pythonhosted.org/packages/68/2b/02e9d6a98ddb73fa238d559a9edcc30b247b8dc4ee848b6184c936e99dc0/charset_normalizer-3.0.1-py3-none-any.whl" + "hash": "3d9098b479e78c85080c98e1e35ff40b4a31d8953102bb0fd7d1b6f8a2111a3d", + "url": "https://files.pythonhosted.org/packages/ef/81/14b3b8f01ddaddad6cdec97f2f599aa2fa466bd5ee9af99b08b7713ccd29/charset_normalizer-3.1.0-py3-none-any.whl" }, { "algorithm": "sha256", - "hash": "9d9153257a3f70d5f69edf2325357251ed20f772b12e593f3b3377b5f78e7ef8", - "url": "https://files.pythonhosted.org/packages/00/35/830c29e5dab61932224c7a6c89427090164a3e425cf03486ce7a3ce60623/charset_normalizer-3.0.1-cp37-cp37m-musllinux_1_1_i686.whl" + "hash": "3a5fc78f9e3f501a1614a98f7c54d3969f3ad9bba8ba3d9b438c3bc5d047dd28", + "url": "https://files.pythonhosted.org/packages/00/47/f14533da238134f5067fb1d951eb03d5c4be895d6afb11c7ebd07d111acb/charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl" }, { "algorithm": "sha256", - "hash": "72966d1b297c741541ca8cf1223ff262a6febe52481af742036a0b296e35fa5a", - "url": "https://files.pythonhosted.org/packages/01/ff/9ee4a44e8c32fe96dfc12daa42f29294608a55eadc88f327939327fb20fb/charset_normalizer-3.0.1-cp311-cp311-musllinux_1_1_aarch64.whl" + "hash": "3573d376454d956553c356df45bb824262c397c6e26ce43e8203c4c540ee0acb", + "url": "https://files.pythonhosted.org/packages/01/c7/0407de35b70525dba2a58a2724a525cf882ee76c3d2171d834463c5d2881/charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl" }, { "algorithm": "sha256", - "hash": "87701167f2a5c930b403e9756fab1d31d4d4da52856143b609e30a1ce7160f3c", - "url": "https://files.pythonhosted.org/packages/02/49/78b4c1bc8b1b0e0fc66fb31ce30d8302f10a1412ba75de72c57532f0beb0/charset_normalizer-3.0.1-cp311-cp311-macosx_11_0_arm64.whl" + "hash": "6734e606355834f13445b6adc38b53c0fd45f1a56a9ba06c2058f86893ae8017", + "url": "https://files.pythonhosted.org/packages/0a/67/8d3d162ec6641911879651cdef670c3c6136782b711d7f8e82e2fffe06e0/charset_normalizer-3.1.0-cp311-cp311-macosx_10_9_x86_64.whl" }, { "algorithm": "sha256", - "hash": "39049da0ffb96c8cbb65cbf5c5f3ca3168990adf3551bd1dee10c48fce8ae820", - "url": "https://files.pythonhosted.org/packages/03/5e/e81488c74e86eef85cf085417ed945da2dcca87ed22d76202680c16bd3c3/charset_normalizer-3.0.1-cp38-cp38-musllinux_1_1_x86_64.whl" + "hash": "3dc5b6a8ecfdc5748a7e429782598e4f17ef378e3e272eeb1340ea57c9109f41", + "url": "https://files.pythonhosted.org/packages/12/12/c5c39f5a149cd6788d2e40cea5618bae37380e2754fcdf53dc9e01bdd33a/charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl" }, { "algorithm": "sha256", - "hash": "024e606be3ed92216e2b6952ed859d86b4cfa52cd5bc5f050e7dc28f9b43ec42", - "url": "https://files.pythonhosted.org/packages/0e/d3/c5fa421dc69bb77c581ed561f1ec6656109c97731ad1128aa93d8bad3053/charset_normalizer-3.0.1-cp38-cp38-macosx_10_9_universal2.whl" + "hash": "38e812a197bf8e71a59fe55b757a84c1f946d0ac114acafaafaf21667a7e169e", + "url": "https://files.pythonhosted.org/packages/12/68/4812f9b05ac0a2b7619ac3dd7d7e3fc52c12006b84617021c615fc2fcf42/charset_normalizer-3.1.0-cp39-cp39-macosx_10_9_universal2.whl" }, { "algorithm": "sha256", - "hash": "503e65837c71b875ecdd733877d852adbc465bd82c768a067badd953bf1bc5a3", - "url": "https://files.pythonhosted.org/packages/0f/45/f462f534dd2853ebbc186ed859661db454665b1dc9ae6c690d982153cda9/charset_normalizer-3.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl" + "hash": "f645caaf0008bacf349875a974220f1f1da349c5dbe7c4ec93048cdc785a3326", + "url": "https://files.pythonhosted.org/packages/13/b7/21729a6d512246aa0bb872b90aea0d9fcd1b293762cdb1d1d33c01140074/charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_aarch64.whl" }, { "algorithm": "sha256", - "hash": "0c0a590235ccd933d9892c627dec5bc7511ce6ad6c1011fdf5b11363022746c1", - "url": "https://files.pythonhosted.org/packages/12/e5/aa09a1c39c3e444dd223d63e2c816c18ed78d035cff954143b2a539bdc9e/charset_normalizer-3.0.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl" + "hash": "b82fab78e0b1329e183a65260581de4375f619167478dddab510c6c6fb04d9b6", + "url": "https://files.pythonhosted.org/packages/16/58/19fd2f62e6ff44ba0db0cd44b584790555e2cde09293149f4409d654811b/charset_normalizer-3.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl" }, { "algorithm": "sha256", - "hash": "3e45867f1f2ab0711d60c6c71746ac53537f1684baa699f4f668d4c6f6ce8e14", - "url": "https://files.pythonhosted.org/packages/16/bd/671f11f920dfb46de848e9176d84ddb25b3bbdffac6751cbbf691c0b5b17/charset_normalizer-3.0.1-cp37-cp37m-macosx_10_9_x86_64.whl" + "hash": "0ca564606d2caafb0abe6d1b5311c2649e8071eb241b2d64e75a0d0065107e62", + "url": "https://files.pythonhosted.org/packages/18/36/7ae10a3dd7f9117b61180671f8d1e4802080cca88ad40aaabd3dad8bab0e/charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl" }, { "algorithm": "sha256", - "hash": "8eade758719add78ec36dc13201483f8e9b5d940329285edcd5f70c0a9edbd7f", - "url": "https://files.pythonhosted.org/packages/17/67/4b25c0358a2e812312b551e734d58855d58f47d0e0e9d1573930003910cb/charset_normalizer-3.0.1-cp39-cp39-macosx_10_9_universal2.whl" + "hash": "10c93628d7497c81686e8e5e557aafa78f230cd9e77dd0c40032ef90c18f2230", + "url": "https://files.pythonhosted.org/packages/1c/9b/de2adc43345623da8e7c958719528a42b6d87d2601017ce1187d43b8a2d7/charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_i686.whl" }, { "algorithm": "sha256", - "hash": "81d6741ab457d14fdedc215516665050f3822d3e56508921cc7239f8c8e66a58", - "url": "https://files.pythonhosted.org/packages/17/da/fdf8ffc33716c82cae06008159a55a581fa515e8dd02e3395dcad42ff83d/charset_normalizer-3.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl" + "hash": "d16fd5252f883eb074ca55cb622bc0bee49b979ae4e8639fff6ca3ff44f9f854", + "url": "https://files.pythonhosted.org/packages/1f/be/c6c76cf8fcf6918922223203c83ba8192eff1c6a709e8cfec7f5ca3e7d2d/charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl" }, { "algorithm": "sha256", - "hash": "e696f0dd336161fca9adbb846875d40752e6eba585843c768935ba5c9960722b", - "url": "https://files.pythonhosted.org/packages/20/a2/16b2cbf5f73bdd10624b94647b85c008ba25059792a5c7b4fdb8358bceeb/charset_normalizer-3.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl" + "hash": "11d117e6c63e8f495412d37e7dc2e2fff09c34b2d09dbe2bee3c6229577818be", + "url": "https://files.pythonhosted.org/packages/21/16/1b0d8fdcb81bbf180976af4f867ce0f2244d303ab10d452fde361dec3b5c/charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_i686.whl" }, { "algorithm": "sha256", - "hash": "0f438ae3532723fb6ead77e7c604be7c8374094ef4ee2c5e03a3a17f1fca256c", - "url": "https://files.pythonhosted.org/packages/25/19/298089cef2eb82fd3810d982aa239d4226594f99e1fe78494cb9b47b03c9/charset_normalizer-3.0.1-cp39-cp39-musllinux_1_1_s390x.whl" + "hash": "b06f0d3bf045158d2fb8837c5785fe9ff9b8c93358be64461a1089f5da983137", + "url": "https://files.pythonhosted.org/packages/23/13/cf5d7bb5bc95f120df64d6c470581189df51d7f011560b2a06a395b7a120/charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_ppc64le.whl" }, { "algorithm": "sha256", - "hash": "911d8a40b2bef5b8bbae2e36a0b103f142ac53557ab421dc16ac4aafee6f53dc", - "url": "https://files.pythonhosted.org/packages/25/b5/f477e419b06e49f3bae446cbdc1fd71d2599be8b12b4d45c641c5a4495b1/charset_normalizer-3.0.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl" + "hash": "1c60b9c202d00052183c9be85e5eaf18a4ada0a47d188a83c8f5c5b23252f649", + "url": "https://files.pythonhosted.org/packages/2c/2f/ec805104098085728b7cb610deede7195c6fa59f51942422f02cc427b6f6/charset_normalizer-3.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl" }, { "algorithm": "sha256", - "hash": "44ba614de5361b3e5278e1241fda3dc1838deed864b50a10d7ce92983797fa76", - "url": "https://files.pythonhosted.org/packages/2d/02/0f875eb6a1cf347bd3a6098f458f79796aafa3b51090fd7b2784736dc67d/charset_normalizer-3.0.1-cp310-cp310-musllinux_1_1_ppc64le.whl" + "hash": "80d1543d58bd3d6c271b66abf454d437a438dff01c3e62fdbcd68f2a11310d4b", + "url": "https://files.pythonhosted.org/packages/31/8b/81c3515a69d06b501fcce69506af57a7a19bd9f42cabd1a667b1b40f2c55/charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_ppc64le.whl" }, { "algorithm": "sha256", - "hash": "ab5de034a886f616a5668aa5d098af2b5385ed70142090e2a31bcbd0af0fdb3d", - "url": "https://files.pythonhosted.org/packages/31/06/f6330ee70c041a032ee1a5d32785d69748cfa41f64b6d327cc08cae51de9/charset_normalizer-3.0.1-cp39-cp39-musllinux_1_1_aarch64.whl" + "hash": "ea9f9c6034ea2d93d9147818f17c2a0860d41b71c38b9ce4d55f21b6f9165a11", + "url": "https://files.pythonhosted.org/packages/33/10/c87ba15f779f8251ae55fa147631339cd91e7af51c3c133d2687c6e41800/charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_i686.whl" }, { "algorithm": "sha256", - "hash": "f6f45710b4459401609ebebdbcfb34515da4fc2aa886f95107f556ac69a9147e", - "url": "https://files.pythonhosted.org/packages/31/af/67b7653a35dbd56f6bb9ff54652a551eae8420d1d0545f0042c5bdb15fb0/charset_normalizer-3.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl" + "hash": "21fa558996782fc226b529fdd2ed7866c2c6ec91cee82735c98a197fae39f706", + "url": "https://files.pythonhosted.org/packages/33/97/9967fb2d364a9da38557e4af323abcd58cc05bdd8f77e9fd5ae4882772cc/charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl" }, { "algorithm": "sha256", - "hash": "0298eafff88c99982a4cf66ba2efa1128e4ddaca0b05eec4c456bbc7db691d8d", - "url": "https://files.pythonhosted.org/packages/37/00/ca188e0a2b3cd3184cdd2521b8765cf579327d128caa8aedc3dc7614020a/charset_normalizer-3.0.1-cp311-cp311-macosx_10_9_universal2.whl" + "hash": "b116502087ce8a6b7a5f1814568ccbd0e9f6cfd99948aa59b0e241dc57cf739f", + "url": "https://files.pythonhosted.org/packages/45/3d/fa2683f5604f99fba5098a7313e5d4846baaecbee754faf115907f21a85f/charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl" }, { "algorithm": "sha256", - "hash": "8b8af03d2e37866d023ad0ddea594edefc31e827fee64f8de5611a1dbc373174", - "url": "https://files.pythonhosted.org/packages/37/60/7a01f3a129d1af1f26ab2c56aae89a72dbf33fd46a467c1aa994ec62b90b/charset_normalizer-3.0.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl" + "hash": "ac3775e3311661d4adace3697a52ac0bab17edd166087d493b52d4f4f553f9f0", + "url": "https://files.pythonhosted.org/packages/4e/11/f7077d78b18aca8ea3186a706c0221aa2bc34c442a3d3bdf3ad401a29052/charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_aarch64.whl" }, { "algorithm": "sha256", - "hash": "31a9ddf4718d10ae04d9b18801bd776693487cbb57d74cc3458a7673f6f34639", - "url": "https://files.pythonhosted.org/packages/55/2b/35619e03725bfa4af4a902e1996c9ee8052d6bce005ff79c9bd988820cb4/charset_normalizer-3.0.1-cp310-cp310-musllinux_1_1_i686.whl" + "hash": "74db0052d985cf37fa111828d0dd230776ac99c740e1a758ad99094be4f1803d", + "url": "https://files.pythonhosted.org/packages/4f/18/92866f050f7114ba38aba4f4a69f83cc2a25dc2e5a8af4b44fd1bfd6d528/charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_aarch64.whl" }, { "algorithm": "sha256", - "hash": "9cf4e8ad252f7c38dd1f676b46514f92dc0ebeb0db5552f5f403509705e24753", - "url": "https://files.pythonhosted.org/packages/56/5d/275fb120957dfe5a2262d04f28bc742fd4bcc2bd270d19bb8757e09737ef/charset_normalizer-3.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl" + "hash": "6f4f4668e1831850ebcc2fd0b1cd11721947b6dc7c00bf1c6bd3c929ae14f2c7", + "url": "https://files.pythonhosted.org/packages/4f/7c/af43743567a7da2a069b4f9fa31874c3c02b963cd1fb84fe1e7568a567e6/charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_ppc64le.whl" }, { "algorithm": "sha256", - "hash": "11b53acf2411c3b09e6af37e4b9005cba376c872503c8f28218c7243582df45d", - "url": "https://files.pythonhosted.org/packages/5a/d8/9e76846e70e729de85ecc6af21edc584a2adfef202dc5f5ae00a02622e3d/charset_normalizer-3.0.1-cp37-cp37m-musllinux_1_1_x86_64.whl" + "hash": "e0ac8959c929593fee38da1c2b64ee9778733cdf03c482c9ff1d508b6b593b2b", + "url": "https://files.pythonhosted.org/packages/4f/a2/9031ba4a008e11a21d7b7aa41751290d2f2035a2f14ecb6e589771a17c47/charset_normalizer-3.1.0-cp310-cp310-macosx_10_9_universal2.whl" }, { "algorithm": "sha256", - "hash": "109487860ef6a328f3eec66f2bf78b0b72400280d8f8ea05f69c51644ba6521a", - "url": "https://files.pythonhosted.org/packages/5b/e7/5527effca09d873e07e128d3daac7c531203b5105cb4e2956c2b7a8cc41c/charset_normalizer-3.0.1-cp38-cp38-musllinux_1_1_aarch64.whl" + "hash": "cb7b2ab0188829593b9de646545175547a70d9a6e2b63bf2cd87a0a391599324", + "url": "https://files.pythonhosted.org/packages/56/24/5f2dedcf3d0673931b6200c410832ae44b376848bc899dbf1fa6c91c4ebe/charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_x86_64.whl" }, { "algorithm": "sha256", - "hash": "8499ca8f4502af841f68135133d8258f7b32a53a1d594aa98cc52013fff55678", - "url": "https://files.pythonhosted.org/packages/6a/ab/3a00ecbddabe25132c20c1bd45e6f90c537b5f7a0b5bcaba094c4922928c/charset_normalizer-3.0.1-cp39-cp39-macosx_10_9_x86_64.whl" + "hash": "cf6511efa4801b9b38dc5546d7547d5b5c6ef4b081c60b23e4d941d0eba9cbeb", + "url": "https://files.pythonhosted.org/packages/5d/2b/4d8c80400c04ae3c8dbc847de092e282b5c7b17f8f9505d68bb3e5815c71/charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_ppc64le.whl" }, { "algorithm": "sha256", - "hash": "772b87914ff1152b92a197ef4ea40efe27a378606c39446ded52c8f80f79702e", - "url": "https://files.pythonhosted.org/packages/6e/d7/1d4035fcbf7d0f2e89588a142628355d8d1cd652a227acefb9ec85908cd4/charset_normalizer-3.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl" + "hash": "628c985afb2c7d27a4800bfb609e03985aaecb42f955049957814e0491d4006d", + "url": "https://files.pythonhosted.org/packages/61/e3/ad9ae58b28482d1069eba1edec2be87701f5dd6fd6024a665020d66677a0/charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl" }, { "algorithm": "sha256", - "hash": "16a8663d6e281208d78806dbe14ee9903715361cf81f6d4309944e4d1e59ac5b", - "url": "https://files.pythonhosted.org/packages/71/67/79be03bf7ab4198d994c2e8da869ca354487bfa25656b95cf289cf6338a2/charset_normalizer-3.0.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl" + "hash": "e89df2958e5159b811af9ff0f92614dabf4ff617c03a4c1c6ff53bf1c399e0e1", + "url": "https://files.pythonhosted.org/packages/67/30/dbab1fe5ab2ce5d3d517ad9936170d896e9687f3860a092519f1fe359812/charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl" }, { "algorithm": "sha256", - "hash": "4a8fcf28c05c1f6d7e177a9a46a1c52798bfe2ad80681d275b10dcf317deaf0b", - "url": "https://files.pythonhosted.org/packages/80/54/183163f9910936e57a60ee618f4f5cc91c2f8333ee2d4ebc6c50f6c8684d/charset_normalizer-3.0.1-cp311-cp311-musllinux_1_1_s390x.whl" + "hash": "d7fc3fca01da18fbabe4625d64bb612b533533ed10045a2ac3dd194bfa656b60", + "url": "https://files.pythonhosted.org/packages/67/df/660e9665ace7ad711e275194a86cb757fb4d4e513fae5ff3d39573db4984/charset_normalizer-3.1.0-cp310-cp310-macosx_10_9_x86_64.whl" }, { "algorithm": "sha256", - "hash": "761e8904c07ad053d285670f36dd94e1b6ab7f16ce62b9805c475b7aa1cffde6", - "url": "https://files.pythonhosted.org/packages/82/49/ab81421d5aa25bc8535896a017c93204cb4051f2a4e72b1ad8f3b594e072/charset_normalizer-3.0.1-cp311-cp311-musllinux_1_1_x86_64.whl" + "hash": "73dc03a6a7e30b7edc5b01b601e53e7fc924b04e1835e8e407c12c037e81adbd", + "url": "https://files.pythonhosted.org/packages/68/77/af702eba147ba963b27eb00832cef6b8c4cb9fcf7404a476993876434b93/charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_s390x.whl" }, { "algorithm": "sha256", - "hash": "8ac7b6a045b814cf0c47f3623d21ebd88b3e8cf216a14790b455ea7ff0135d18", - "url": "https://files.pythonhosted.org/packages/84/ff/78a4942ef1ea4d1c464cc9a132122b36c5390c5cf6301ed0f9e3e6e24bd9/charset_normalizer-3.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl" + "hash": "1435ae15108b1cb6fffbcea2af3d468683b7afed0169ad718451f8db5d1aff6f", + "url": "https://files.pythonhosted.org/packages/69/22/66351781e668158feef71c5e3b059a79ecc9efc3ef84a45888b0f3a933d5/charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl" }, { "algorithm": "sha256", - "hash": "5995f0164fa7df59db4746112fec3f49c461dd6b31b841873443bdb077c13cfc", - "url": "https://files.pythonhosted.org/packages/86/eb/31c9025b4ed7eddd930c5f2ac269efb953de33140608c7539675d74a2081/charset_normalizer-3.0.1-cp311-cp311-musllinux_1_1_ppc64le.whl" + "hash": "20064ead0717cf9a73a6d1e779b23d149b53daf971169289ed2ed43a71e8d3b0", + "url": "https://files.pythonhosted.org/packages/6d/59/59a3f4d8a59ee270da77f9e954a0e284c9d6884d39ec69d696d9aa5ff2f2/charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl" }, { "algorithm": "sha256", - "hash": "f9d0c5c045a3ca9bedfc35dca8526798eb91a07aa7a2c0fee134c6c6f321cbd7", - "url": "https://files.pythonhosted.org/packages/89/87/c237a299a658b35d19fd531eeb8247480627fc2fb4b7a471334b48850f45/charset_normalizer-3.0.1-cp311-cp311-musllinux_1_1_i686.whl" + "hash": "c84132a54c750fda57729d1e2599bb598f5fa0344085dbde5003ba429a4798c0", + "url": "https://files.pythonhosted.org/packages/72/90/667a6bc6abe42fc10adf4cd2c1e1c399d78e653dbac4c8018350843d4ab7/charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl" }, { "algorithm": "sha256", - "hash": "a8d0fc946c784ff7f7c3742310cc8a57c5c6dc31631269876a88b809dbeff3d3", - "url": "https://files.pythonhosted.org/packages/90/59/941e2e5ae6828a688c6437ad16e026eb3606d0cfdd13ea5c9090980f3ffd/charset_normalizer-3.0.1-cp311-cp311-macosx_10_9_x86_64.whl" + "hash": "de5695a6f1d8340b12a5d6d4484290ee74d61e467c39ff03b39e30df62cf83a0", + "url": "https://files.pythonhosted.org/packages/74/5f/361202de730532028458b729781b8435f320e31a622c27f30e25eec80513/charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl" }, { "algorithm": "sha256", - "hash": "12db3b2c533c23ab812c2b25934f60383361f8a376ae272665f8e48b88e8e1c6", - "url": "https://files.pythonhosted.org/packages/92/00/b8dc8dd725297b05f1ab4929c9d7e879f31746131534221c5c8948bc7563/charset_normalizer-3.0.1-cp310-cp310-musllinux_1_1_s390x.whl" + "hash": "49919f8400b5e49e961f320c735388ee686a62327e773fa5b3ce6721f7e785ce", + "url": "https://files.pythonhosted.org/packages/74/f1/d0b8385b574f7e086fb6709e104b696707bd3742d54a6caf0cebbb7e975b/charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_s390x.whl" }, { "algorithm": "sha256", - "hash": "02a51034802cbf38db3f89c66fb5d2ec57e6fe7ef2f4a44d070a593c3688667b", - "url": "https://files.pythonhosted.org/packages/93/d1/569445a704414e150f198737c245ab96b40d28d5b68045a62c414a5157de/charset_normalizer-3.0.1-cp37-cp37m-musllinux_1_1_ppc64le.whl" + "hash": "d2686f91611f9e17f4548dbf050e75b079bbc2a82be565832bc8ea9047b61c8c", + "url": "https://files.pythonhosted.org/packages/82/b9/51b66a647be8685dee75b7807e0f750edf5c1e4f29bc562ad285c501e3c7/charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_x86_64.whl" }, { "algorithm": "sha256", - "hash": "ebea339af930f8ca5d7a699b921106c6e29c617fe9606fa7baa043c1cdae326f", - "url": "https://files.pythonhosted.org/packages/96/d7/1675d9089a1f4677df5eb29c3f8b064aa1e70c1251a0a8a127803158942d/charset-normalizer-3.0.1.tar.gz" + "hash": "dd5653e67b149503c68c4018bf07e42eeed6b4e956b24c00ccdf93ac79cdff84", + "url": "https://files.pythonhosted.org/packages/84/23/f60cda6c70ae922ad78368982f06e7fef258fba833212f26275fe4727dc4/charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_s390x.whl" }, { "algorithm": "sha256", - "hash": "c512accbd6ff0270939b9ac214b84fb5ada5f0409c44298361b2f5e13f9aed9e", - "url": "https://files.pythonhosted.org/packages/98/e4/d4685870fda1cc7c5e29899ec329500460418e54f4f5df76ee520e30689a/charset_normalizer-3.0.1-cp310-cp310-musllinux_1_1_x86_64.whl" + "hash": "f8303414c7b03f794347ad062c0516cee0e15f7a612abd0ce1e25caf6ceb47df", + "url": "https://files.pythonhosted.org/packages/85/e8/18d408d8fe29a56012c10d6b15960940b83f06620e9d7481581cdc6d9901/charset_normalizer-3.1.0-cp311-cp311-macosx_11_0_arm64.whl" }, { "algorithm": "sha256", - "hash": "3b590df687e3c5ee0deef9fc8c547d81986d9a1b56073d82de008744452d6541", - "url": "https://files.pythonhosted.org/packages/99/24/eb846dc9a797da58e6e5b3b5a71d3ff17264de3f424fb29aaa5d27173b55/charset_normalizer-3.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl" + "hash": "6f6c7a8a57e9405cad7485f4c9d3172ae486cfef1344b5ddd8e5239582d7355e", + "url": "https://files.pythonhosted.org/packages/94/70/23981e7bf098efbc4037e7c66d28a10e950d9296c08c6dea8ef290f9c79e/charset_normalizer-3.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl" }, { "algorithm": "sha256", - "hash": "4b0d02d7102dd0f997580b51edc4cebcf2ab6397a7edf89f1c73b586c614272c", - "url": "https://files.pythonhosted.org/packages/9c/42/c1ebc736c57459aab28bfb8aa28c6a047796f2ea46050a3b129b4920dbe4/charset_normalizer-3.0.1-cp38-cp38-macosx_10_9_x86_64.whl" + "hash": "c3af8e0f07399d3176b179f2e2634c3ce9c1301379a6b8c9c9aeecd481da494f", + "url": "https://files.pythonhosted.org/packages/9a/f1/ff81439aa09070fee64173e6ca6ce1342f2b1cca997bcaae89e443812684/charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl" }, { "algorithm": "sha256", - "hash": "2edb64ee7bf1ed524a1da60cdcd2e1f6e2b4f66ef7c077680739f1641f62f555", - "url": "https://files.pythonhosted.org/packages/9f/5a/9dc8932d1e5f8eeaa502e3c3fce91c86be20c04eb3ec202d2b7d74b567e5/charset_normalizer-3.0.1-cp310-cp310-musllinux_1_1_aarch64.whl" + "hash": "aaf53a6cebad0eae578f062c7d462155eada9c172bd8c4d250b8c1d8eb7f916a", + "url": "https://files.pythonhosted.org/packages/9e/62/a1e0a8f8830c92014602c8a88a1a20b8a68d636378077381f671e6e1cec9/charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl" }, { "algorithm": "sha256", - "hash": "a16418ecf1329f71df119e8a65f3aa68004a3f9383821edcb20f0702934d8087", - "url": "https://files.pythonhosted.org/packages/a2/93/0b1aa4dbc0ae2aa2e1b2e6d037ab8984dc09912d6b26d63ced14da07e3a7/charset_normalizer-3.0.1-cp37-cp37m-musllinux_1_1_aarch64.whl" + "hash": "0be65ccf618c1e7ac9b849c315cc2e8a8751d9cfdaa43027d4f6624bd587ab7e", + "url": "https://files.pythonhosted.org/packages/a2/6c/5167f08da5298f383036c33cb749ab5b3405fd07853edc8314c6882c01b8/charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_s390x.whl" }, { "algorithm": "sha256", - "hash": "358a7c4cb8ba9b46c453b1dd8d9e431452d5249072e4f56cfda3149f6ab1405e", - "url": "https://files.pythonhosted.org/packages/a2/a7/adc963ad8f8fddadd6be088e636972705ec9d1d92d1b45e6119eb02b7e9e/charset_normalizer-3.0.1-cp38-cp38-macosx_11_0_arm64.whl" + "hash": "1e8fcdd8f672a1c4fc8d0bd3a2b576b152d2a349782d1eb0f6b8e52e9954731d", + "url": "https://files.pythonhosted.org/packages/a4/03/355281b62c26712a50c6a9dd75339d8cdd58488fd7bf2556ba1320ebd315/charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_i686.whl" }, { "algorithm": "sha256", - "hash": "2e396d70bc4ef5325b72b593a72c8979999aa52fb8bcf03f701c1b03e1166918", - "url": "https://files.pythonhosted.org/packages/a3/09/a837b27b122e710dfad15b0b5df04cd0623c8d8d3382e4298f50798fb84a/charset_normalizer-3.0.1-cp37-cp37m-musllinux_1_1_s390x.whl" + "hash": "53d0a3fa5f8af98a1e261de6a3943ca631c526635eb5817a87a59d9a57ebf48f", + "url": "https://files.pythonhosted.org/packages/a9/83/138d2624fdbcb62b7e14715eb721d44347e41a1b4c16544661e940793f49/charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_x86_64.whl" }, { "algorithm": "sha256", - "hash": "62595ab75873d50d57323a91dd03e6966eb79c41fa834b7a1661ed043b2d404d", - "url": "https://files.pythonhosted.org/packages/a3/4b/f565c852163312a0991c30598f403fd06796a12e408d7839cc46ca8d7f4a/charset_normalizer-3.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl" + "hash": "8f25e17ab3039b05f762b0a55ae0b3632b2e073d9c8fc88e89aca31a6198e88f", + "url": "https://files.pythonhosted.org/packages/ac/7f/62d5dff4e9cb993e4b0d4ea78a74cc84d7d92120879529e0ce0965765936/charset_normalizer-3.1.0-cp39-cp39-macosx_11_0_arm64.whl" }, { "algorithm": "sha256", - "hash": "f97e83fa6c25693c7a35de154681fcc257c1c41b38beb0304b9c4d2d9e164479", - "url": "https://files.pythonhosted.org/packages/aa/a4/2d6255d4db5d4558a92458fd8dacddfdda2fb4ad9c0a87db6f6034aded34/charset_normalizer-3.0.1-cp38-cp38-musllinux_1_1_ppc64le.whl" + "hash": "3a06f32c9634a8705f4ca9946d667609f52cf130d5548881401f1eb2c39b1e2c", + "url": "https://files.pythonhosted.org/packages/ac/c5/990bc41a98b7fa2677c665737fdf278bb74ad4b199c56b6b564b3d4cbfc5/charset_normalizer-3.1.0-cp38-cp38-macosx_10_9_x86_64.whl" }, { "algorithm": "sha256", - "hash": "70990b9c51340e4044cfc394a81f614f3f90d41397104d226f21e66de668730d", - "url": "https://files.pythonhosted.org/packages/af/63/2c00ff4e657fb9bb76306ffbc7878fd52067e39716f5e8b0dd5582caf1fa/charset_normalizer-3.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl" + "hash": "6f5c2e7bc8a4bf7c426599765b1bd33217ec84023033672c1e9a8b35eaeaaaf8", + "url": "https://files.pythonhosted.org/packages/b0/55/d8ef4c8c7d2a8b3a16e7d9b03c59475c2ee96a0e0c90b14c99faaac0ee3b/charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_x86_64.whl" }, { "algorithm": "sha256", - "hash": "88600c72ef7587fe1708fd242b385b6ed4b8904976d5da0893e31df8b3480cb6", - "url": "https://files.pythonhosted.org/packages/b2/4c/9a4f30042bfee22d34d80daf75f51817cdd23180d718e0160aab235c4faf/charset_normalizer-3.0.1-cp310-cp310-macosx_10_9_universal2.whl" + "hash": "11d3bcb7be35e7b1bba2c23beedac81ee893ac9871d0ba79effc7fc01167db6c", + "url": "https://files.pythonhosted.org/packages/bb/dc/58fdef3ab85e8e7953a8b89ef1d2c06938b8ad88d9617f22967e1a90e6b8/charset_normalizer-3.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl" }, { "algorithm": "sha256", - "hash": "00d3ffdaafe92a5dc603cb9bd5111aaa36dfa187c8285c543be562e61b755f6b", - "url": "https://files.pythonhosted.org/packages/b5/1a/932d86fde86bb0d2992c74552c9a422883fe0890132bbc9a5e2211f03318/charset_normalizer-3.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl" + "hash": "891cf9b48776b5c61c700b55a598621fdb7b1e301a550365571e9624f270c203", + "url": "https://files.pythonhosted.org/packages/c2/35/dfb4032f5712747d3dcfdd19d0768f6d8f60910ae24ed066ecbf442be013/charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_aarch64.whl" }, { "algorithm": "sha256", - "hash": "14e76c0f23218b8f46c4d87018ca2e441535aed3632ca134b10239dfb6dadd6b", - "url": "https://files.pythonhosted.org/packages/c0/4d/6b82099e3f25a9ed87431e2f51156c14f3a9ce8fad73880a3856cd95f1d5/charset_normalizer-3.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl" + "hash": "04eefcee095f58eaabe6dc3cc2262f3bcd776d2c67005880894f447b3f2cb9c1", + "url": "https://files.pythonhosted.org/packages/c6/ab/43ea052756b2f2dcb6a131897811c0e2704b0288f090336217d3346cd682/charset_normalizer-3.1.0-cp310-cp310-macosx_11_0_arm64.whl" }, { "algorithm": "sha256", - "hash": "292d5e8ba896bbfd6334b096e34bffb56161c81408d6d036a7dfa6929cff8783", - "url": "https://files.pythonhosted.org/packages/c1/06/b7b1d3d186e0f288500b8a1161ede6b38a0abbf878c2033d667e815e6bd7/charset_normalizer-3.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl" + "hash": "bd7163182133c0c7701b25e604cf1611c0d87712e56e88e7ee5d72deab3e76b5", + "url": "https://files.pythonhosted.org/packages/c9/8c/a76dd9f2c8803eb147e1e715727f5c3ba0ef39adaadf66a7b3698c113180/charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_aarch64.whl" }, { "algorithm": "sha256", - "hash": "a60332922359f920193b1d4826953c507a877b523b2395ad7bc716ddd386d866", - "url": "https://files.pythonhosted.org/packages/c1/b2/d81606aebeb7e9a33dc877ff3a206c9946f5bb374c99d22d4a28825aa270/charset_normalizer-3.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl" + "hash": "75f2568b4189dda1c567339b48cba4ac7384accb9c2a7ed655cd86b04055c795", + "url": "https://files.pythonhosted.org/packages/cc/f6/21a66e524658bd1dd7b89ac9d1ee8f7823f2d9701a2fbc458ab9ede53c63/charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl" }, { "algorithm": "sha256", - "hash": "9cb3032517f1627cc012dbc80a8ec976ae76d93ea2b5feaa9d2a5b8882597579", - "url": "https://files.pythonhosted.org/packages/c4/d4/94f1ea460cce04483d2460efba6fd4d66e6f60ad6fc6075dba13e3501e48/charset_normalizer-3.0.1-cp39-cp39-musllinux_1_1_i686.whl" + "hash": "6baf0baf0d5d265fa7944feb9f7451cc316bfe30e8df1a61b1bb08577c554f31", + "url": "https://files.pythonhosted.org/packages/d5/92/86c0f0e66e897f6818c46dadef328a5b345d061688f9960fc6ca1fd03dbe/charset_normalizer-3.1.0-cp39-cp39-macosx_10_9_x86_64.whl" }, { "algorithm": "sha256", - "hash": "c22d3fe05ce11d3671297dc8973267daa0f938b93ec716e12e0f6dee81591dc1", - "url": "https://files.pythonhosted.org/packages/c8/a2/8f873138c99423de3b402daf8ccd7a538632c83d0c129444a6a18ef34e03/charset_normalizer-3.0.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl" + "hash": "e1b25e3ad6c909f398df8921780d6a3d120d8c09466720226fc621605b6f92b1", + "url": "https://files.pythonhosted.org/packages/d7/4c/37ad75674e8c6bc22ab01bef673d2d6e46ee44203498c9a26aa23959afe5/charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl" }, { "algorithm": "sha256", - "hash": "608862a7bf6957f2333fc54ab4399e405baad0163dc9f8d99cb236816db169d4", - "url": "https://files.pythonhosted.org/packages/c9/dd/80a5e8c080b7e1cc2b0ca35f0d6aeedafd7bbd06d25031ac20868b1366d6/charset_normalizer-3.0.1-cp39-cp39-musllinux_1_1_ppc64le.whl" + "hash": "7381c66e0561c5757ffe616af869b916c8b4e42b367ab29fedc98481d1e74e14", + "url": "https://files.pythonhosted.org/packages/d8/ca/a7ff600781bf1e5f702ba26bb82f2ba1d3a873a3f8ad73cc44c79dfaefa9/charset_normalizer-3.1.0-cp38-cp38-macosx_11_0_arm64.whl" }, { "algorithm": "sha256", - "hash": "ff6f3db31555657f3163b15a6b7c6938d08df7adbfc9dd13d9d19edad678f1e8", - "url": "https://files.pythonhosted.org/packages/d3/5b/4031145fcfb9ceaf49dad2fbf9a44e062eb2c08aff36f71d8aafbecf4567/charset_normalizer-3.0.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl" + "hash": "ac0aa6cd53ab9a31d397f8303f92c42f534693528fafbdb997c82bae6e477ad9", + "url": "https://files.pythonhosted.org/packages/dd/39/6276cf5a395ffd39b77dadf0e2fcbfca8dbfe48c56ada250c40086055143/charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl" }, { "algorithm": "sha256", - "hash": "79909e27e8e4fcc9db4addea88aa63f6423ebb171db091fb4373e3312cb6d603", - "url": "https://files.pythonhosted.org/packages/d9/7a/60d45c9453212b30eebbf8b5cddbdef330eebddfcf335bce7920c43fb72e/charset_normalizer-3.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl" + "hash": "9a3267620866c9d17b959a84dd0bd2d45719b817245e49371ead79ed4f710d19", + "url": "https://files.pythonhosted.org/packages/e1/7c/398600268fc98b7e007f5a716bd60903fff1ecff75e45f5700212df5cd76/charset_normalizer-3.1.0-cp311-cp311-macosx_10_9_universal2.whl" }, { "algorithm": "sha256", - "hash": "c2ac1b08635a8cd4e0cbeaf6f5e922085908d48eb05d44c5ae9eabab148512ca", - "url": "https://files.pythonhosted.org/packages/dc/ff/2c7655d83b1d6d6a0e132d50d54131fcb8da763b417ccc6c4a506aa0e08c/charset_normalizer-3.0.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl" + "hash": "0c95f12b74681e9ae127728f7e5409cbbef9cd914d5896ef238cc779b8152373", + "url": "https://files.pythonhosted.org/packages/e1/b4/53678b2a14e0496fc167fe9b9e726ad33d670cfd2011031aa5caeee6b784/charset_normalizer-3.1.0-cp37-cp37m-macosx_10_9_x86_64.whl" }, { "algorithm": "sha256", - "hash": "cadaeaba78750d58d3cc6ac4d1fd867da6fc73c88156b7a3212a3cd4819d679d", - "url": "https://files.pythonhosted.org/packages/df/2f/4806e155191f75e720aca98a969581c6b2676f0379dd315c34c388bbf8b5/charset_normalizer-3.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl" + "hash": "abc1185d79f47c0a7aaf7e2412a0eb2c03b724581139193d2d82b3ad8cbb00ac", + "url": "https://files.pythonhosted.org/packages/e5/aa/9d2d60d6a566423da96c15cd11cbb88a70f9aff9a4db096094ee19179cab/charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_s390x.whl" }, { "algorithm": "sha256", - "hash": "8c7fe7afa480e3e82eed58e0ca89f751cd14d767638e2550c77a92a9e749c317", - "url": "https://files.pythonhosted.org/packages/df/c5/dd3a17a615775d0ffc3e12b0e47833d8b7e0a4871431dad87a3f92382a19/charset_normalizer-3.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl" + "hash": "e633940f28c1e913615fd624fcdd72fdba807bf53ea6925d6a588e84e1151531", + "url": "https://files.pythonhosted.org/packages/ea/38/d31c7906c4be13060c1a5034087966774ef33ab57ff2eee76d71265173c3/charset_normalizer-3.1.0-cp38-cp38-macosx_10_9_universal2.whl" }, { "algorithm": "sha256", - "hash": "c75ffc45f25324e68ab238cb4b5c0a38cd1c3d7f1fb1f72b5541de469e2247db", - "url": "https://files.pythonhosted.org/packages/e1/7f/64b51f144fa9e74da63fa690d9563eae627f4df6cc6ae5185a781e1912e5/charset_normalizer-3.0.1-cp310-cp310-macosx_10_9_x86_64.whl" + "hash": "fca62a8301b605b954ad2e9c3666f9d97f63872aa4efcae5492baca2056b74ab", + "url": "https://files.pythonhosted.org/packages/f2/b7/e21e16c98575616f4ce09dc766dbccdac0ca119c176b184d46105e971a84/charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl" }, { "algorithm": "sha256", - "hash": "3ae1de54a77dc0d6d5fcf623290af4266412a7c4be0b1ff7444394f03f5c54e3", - "url": "https://files.pythonhosted.org/packages/e3/96/8cdbce165c96cce5f2c9c7748f7ed8e0cf0c5d03e213bbc90b7c3e918bf5/charset_normalizer-3.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl" + "hash": "5f008525e02908b20e04707a4f704cd286d94718f48bb33edddc7d7b584dddc1", + "url": "https://files.pythonhosted.org/packages/f2/d7/6ee92c11eda3f3c9cac1e059901092bfdf07388be7d2e60ac627527eee62/charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_i686.whl" }, { "algorithm": "sha256", - "hash": "37f8febc8ec50c14f3ec9637505f28e58d4f66752207ea177c1d67df25da5aed", - "url": "https://files.pythonhosted.org/packages/e8/80/141f6af05332cbb811ab469f64deb1e1d4cc9e8b0c003aa8a38d689ce84a/charset_normalizer-3.0.1-cp38-cp38-musllinux_1_1_i686.whl" + "hash": "22908891a380d50738e1f978667536f6c6b526a2064156203d418f4856d6e86a", + "url": "https://files.pythonhosted.org/packages/f4/0a/8c03913ed1eca9d831db0c28759edb6ce87af22bb55dbc005a52525a75b6/charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_x86_64.whl" }, { "algorithm": "sha256", - "hash": "db72b07027db150f468fbada4d85b3b2729a3db39178abf5c543b784c1254539", - "url": "https://files.pythonhosted.org/packages/f0/78/30d853a3073c866b47abede6d86b5532aa99ac67a95e86077d20be1ce481/charset_normalizer-3.0.1-cp310-cp310-macosx_11_0_arm64.whl" + "hash": "3747443b6a904001473370d7810aa19c3a180ccd52a7157aacc264a5ac79265e", + "url": "https://files.pythonhosted.org/packages/f6/0f/de1c4030fd669e6719277043e3b0f152a83c118dd1020cf85b51d443d04a/charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl" }, { "algorithm": "sha256", - "hash": "a152f5f33d64a6be73f1d30c9cc82dfc73cec6477ec268e7c6e4c7d23c2d2291", - "url": "https://files.pythonhosted.org/packages/f1/ff/9a1c65d8c44958f45ae40cd558ab63bd499a35198a2014e13c0887c07ed1/charset_normalizer-3.0.1-cp38-cp38-musllinux_1_1_s390x.whl" + "hash": "78cacd03e79d009d95635e7d6ff12c21eb89b894c354bd2b2ed0b4763373693b", + "url": "https://files.pythonhosted.org/packages/f8/ed/500609cb2457b002242b090c814549997424d72690ef3058cfdfca91f68b/charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl" }, { "algorithm": "sha256", - "hash": "3fc1c4a2ffd64890aebdb3f97e1278b0cc72579a08ca4de8cd2c04799a3a22be", - "url": "https://files.pythonhosted.org/packages/f5/84/cac681144a28114bd9e40d3cdbfd961c14ecc2b56f1baec2094afd6744c7/charset_normalizer-3.0.1-cp39-cp39-macosx_11_0_arm64.whl" + "hash": "04afa6387e2b282cf78ff3dbce20f0cc071c12dc8f685bd40960cc68644cfea6", + "url": "https://files.pythonhosted.org/packages/fa/8e/2e5c742c3082bce3eea2ddd5b331d08050cda458bc362d71c48e07a44719/charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_ppc64le.whl" }, { "algorithm": "sha256", - "hash": "356541bf4381fa35856dafa6a965916e54bed415ad8a24ee6de6e37deccf2786", - "url": "https://files.pythonhosted.org/packages/f5/ec/a9bed59079bd0267d34ada58a4048c96a59b3621e7f586ea85840d41831d/charset_normalizer-3.0.1-cp39-cp39-musllinux_1_1_x86_64.whl" + "hash": "34e0a2f9c370eb95597aae63bf85eb5e96826d81e3dcf88b8886012906f509b5", + "url": "https://files.pythonhosted.org/packages/ff/d7/8d757f8bd45be079d76309248845a04f09619a7b17d6dfc8c9ff6433cac2/charset-normalizer-3.1.0.tar.gz" } ], "project_name": "charset-normalizer", "requires_dists": [], - "requires_python": null, - "version": "3.0.1" + "requires_python": ">=3.7.0", + "version": "3.1.0" }, { "artifacts": [ @@ -610,13 +604,13 @@ "artifacts": [ { "algorithm": "sha256", - "hash": "7efb448ec9a5e313a57655d35aa54cd3e01b7e1fbcf72dce1bf06119420f5bad", - "url": "https://files.pythonhosted.org/packages/26/a7/9da7d5b23fc98ab3d424ac2c65613d63c1f401efb84ad50f2fa27b2caab4/importlib_metadata-6.0.0-py3-none-any.whl" + "hash": "03ba783c3a2c69d751b109fc0c94a62c51f581b3d6acf8ed1331b6d5729321ff", + "url": "https://files.pythonhosted.org/packages/50/a2/75da1ccf2cc043aeb866e18c9a196f0b42abfe5d421c657019c68ebec1e9/importlib_metadata-6.5.0-py3-none-any.whl" }, { "algorithm": "sha256", - "hash": "e354bedeb60efa6affdcc8ae121b73544a7aa74156d047311948f6d711cd378d", - "url": "https://files.pythonhosted.org/packages/90/07/6397ad02d31bddf1841c9ad3ec30a693a3ff208e09c2ef45c9a8a5f85156/importlib_metadata-6.0.0.tar.gz" + "hash": "7a8bdf1bc3a726297f5cfbc999e6e7ff6b4fa41b26bba4afc580448624460045", + "url": "https://files.pythonhosted.org/packages/9d/ce/dc7221b3044c7382d5abad27d2599e63b0e0ccabc49491244203ee1ded96/importlib_metadata-6.5.0.tar.gz" } ], "project_name": "importlib-metadata", @@ -645,7 +639,7 @@ "zipp>=0.5" ], "requires_python": ">=3.7", - "version": "6.0.0" + "version": "6.5.0" }, { "artifacts": [ @@ -788,32 +782,32 @@ "artifacts": [ { "algorithm": "sha256", - "hash": "714ac14496c3e68c99c29b00845f7a2b85f3bb6f1078fd9f72fd20f0570002b2", - "url": "https://files.pythonhosted.org/packages/ed/35/a31aed2993e398f6b09a790a181a7927eb14610ee8bbf02dc14d31677f1c/packaging-23.0-py3-none-any.whl" + "hash": "994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61", + "url": "https://files.pythonhosted.org/packages/ab/c3/57f0601a2d4fe15de7a553c00adbc901425661bf048f2a22dfc500caf121/packaging-23.1-py3-none-any.whl" }, { "algorithm": "sha256", - "hash": "b6ad297f8907de0fa2fe1ccbd26fdaf387f5f47c7275fedf8cce89f99446cf97", - "url": "https://files.pythonhosted.org/packages/47/d5/aca8ff6f49aa5565df1c826e7bf5e85a6df852ee063600c1efa5b932968c/packaging-23.0.tar.gz" + "hash": "a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f", + "url": "https://files.pythonhosted.org/packages/b9/6c/7c6658d258d7971c5eb0d9b69fa9265879ec9a9158031206d47800ae2213/packaging-23.1.tar.gz" } ], "project_name": "packaging", "requires_dists": [], "requires_python": ">=3.7", - "version": "23.0" + "version": "23.1" }, { "artifacts": [ { "algorithm": "sha256", - "hash": "1800c0a04962ee99d161c07f5a12fc49549caf5cfcda426a9103e34e37f854ba", - "url": "https://files.pythonhosted.org/packages/68/a9/3f23e0d8a05e9ec4f6180dc01ec0e73e207e526161ee74647eb1488b613d/peewee-3.16.0.tar.gz" + "hash": "10769981198c7311f84a0ca8db892fa213303a8eb1305deb795a71e7bd606a91", + "url": "https://files.pythonhosted.org/packages/a9/50/1dd5ea74c559df4afb8391f8d05f0fec685dbe8effba13bb9072901eb288/peewee-3.16.2.tar.gz" } ], "project_name": "peewee", "requires_dists": [], "requires_python": null, - "version": "3.16.0" + "version": "3.16.2" }, { "artifacts": [ @@ -837,21 +831,21 @@ "artifacts": [ { "algorithm": "sha256", - "hash": "fa7bd7bd2771287c0de303af8bfdfc731f51bd2c6a47ab69d117138893b82717", - "url": "https://files.pythonhosted.org/packages/0b/42/d9d95cc461f098f204cd20c85642ae40fbff81f74c300341b8d0e0df14e0/Pygments-2.14.0-py3-none-any.whl" + "hash": "db2db3deb4b4179f399a09054b023b6a586b76499d36965813c71aa8ed7b5fd1", + "url": "https://files.pythonhosted.org/packages/34/a7/37c8d68532ba71549db4212cb036dbd6161b40e463aba336770e80c72f84/Pygments-2.15.1-py3-none-any.whl" }, { "algorithm": "sha256", - "hash": "b3ed06a9e8ac9a9aae5a6f5dbe78a8a58655d17b43b93c078f094ddc476ae297", - "url": "https://files.pythonhosted.org/packages/da/6a/c427c06913204e24de28de5300d3f0e809933f376e0b7df95194b2bb3f71/Pygments-2.14.0.tar.gz" + "hash": "8ace4d3c1dd481894b2005f560ead0f9f19ee64fe983366be1a21e171d12775c", + "url": "https://files.pythonhosted.org/packages/89/6b/2114e54b290824197006e41be3f9bbe1a26e9c39d1f5fa20a6d62945a0b3/Pygments-2.15.1.tar.gz" } ], "project_name": "pygments", "requires_dists": [ "importlib-metadata; python_version < \"3.8\" and extra == \"plugins\"" ], - "requires_python": ">=3.6", - "version": "2.14.0" + "requires_python": ">=3.7", + "version": "2.15.1" }, { "artifacts": [ @@ -1001,24 +995,24 @@ "artifacts": [ { "algorithm": "sha256", - "hash": "8aa57747f3fc3e977684f0176a88e789be314a99f99b43b75d1e9cb5dc6db9e9", - "url": "https://files.pythonhosted.org/packages/a8/c6/14b77fe7a5fab66ffbeffd6706f598d00a52702846bce0e2339bcf9dd20c/rich-13.3.1-py3-none-any.whl" + "hash": "22b74cae0278fd5086ff44144d3813be1cedc9115bdfabbfefd86400cb88b20a", + "url": "https://files.pythonhosted.org/packages/9d/1a/28117ae737aec7c004ed5067034a8949adab43730420b50312821f466c3f/rich-13.3.4-py3-none-any.whl" }, { "algorithm": "sha256", - "hash": "125d96d20c92b946b983d0d392b84ff945461e5a06d3867e9f9e575f8697b67f", - "url": "https://files.pythonhosted.org/packages/68/31/b8934896818c885001aeb7df388ba0523ea3ec88ad31805983d9b0480a50/rich-13.3.1.tar.gz" + "hash": "b5d573e13605423ec80bdd0cd5f8541f7844a0e71a13f74cf454ccb2f490708b", + "url": "https://files.pythonhosted.org/packages/31/3b/2360352760b436f822258396e66ffb6d42585518a9cde2f93f142e64c5eb/rich-13.3.4.tar.gz" } ], "project_name": "rich", "requires_dists": [ "ipywidgets<9,>=7.5.1; extra == \"jupyter\"", - "markdown-it-py<3.0.0,>=2.1.0", - "pygments<3.0.0,>=2.14.0", + "markdown-it-py<3.0.0,>=2.2.0", + "pygments<3.0.0,>=2.13.0", "typing-extensions<5.0,>=4.0.0; python_version < \"3.9\"" ], "requires_python": ">=3.7.0", - "version": "13.3.1" + "version": "13.3.4" }, { "artifacts": [ @@ -1154,23 +1148,23 @@ "artifacts": [ { "algorithm": "sha256", - "hash": "349adfa431f2f4358f786536f43210d974e6563b9d9794c6398f742314f86700", - "url": "https://files.pythonhosted.org/packages/75/a7/242c85f80ab18c74182c55a2f94d7ba893f2b35a799749a039936580917c/semgrep-1.14.0-cp37.cp38.cp39.py37.py38.py39-none-macosx_11_0_arm64.whl" + "hash": "89cfde0b8bb5a3b0624d9710ec7ac73e74598bf227c3c61fd1fa489dd29e33eb", + "url": "https://files.pythonhosted.org/packages/a5/59/4ffda356d7a85b84746c44e81fdcff8bffb155e5dda9933508c7f53d7b99/semgrep-1.19.0-cp37.cp38.cp39.py37.py38.py39-none-macosx_11_0_arm64.whl" }, { "algorithm": "sha256", - "hash": "db1214c64644c79bfc84ba41c2d692706ee129be97fa476f99369cbc8fb8f47d", - "url": "https://files.pythonhosted.org/packages/14/34/6ae356e002d48203e875fd71bd401339a7357c9a27667c579b963e1003b2/semgrep-1.14.0-cp37.cp38.cp39.py37.py38.py39-none-any.whl" + "hash": "723429f9548bd4c959b12c25b6f88fc48858c129d0a13f0fc7904be866f344de", + "url": "https://files.pythonhosted.org/packages/a5/0f/87759b6f16c2195f367b3a5543190d823449a66263edd2e713c2688765b1/semgrep-1.19.0-cp37.cp38.cp39.py37.py38.py39-none-any.whl" }, { "algorithm": "sha256", - "hash": "ab917d3c490cbe4345d61325980314df729b1d6b25b2508818b3865e6c56ae81", - "url": "https://files.pythonhosted.org/packages/87/61/34e2bdd32ef2b87262f267b62c2be20f8f1515af91ece737475a6875a6d8/semgrep-1.14.0-cp37.cp38.cp39.py37.py38.py39-none-macosx_10_14_x86_64.whl" + "hash": "e59784801146a8d799bc553da68e5e5acbf338a3cae1e8486a9b7aaa925cb266", + "url": "https://files.pythonhosted.org/packages/c4/32/3642576d14b367cd68c5842112a4ac2763b2de72568d3a78e38e94ae5391/semgrep-1.19.0-cp37.cp38.cp39.py37.py38.py39-none-macosx_10_14_x86_64.whl" }, { "algorithm": "sha256", - "hash": "a788b425d893381b7cd78e21d5e7776cc0cc95b25645b9b0721a87e7f2580f8d", - "url": "https://files.pythonhosted.org/packages/92/15/c812e25602475768ce8a02785d53725b77d8c53ab263a1b21940dfdaf080/semgrep-1.14.0.tar.gz" + "hash": "5d5e32ffb1bd963e0ede87f0b801305b8367ad660a73f8e0dd8e14f0c8d3e956", + "url": "https://files.pythonhosted.org/packages/ee/68/573843402f3025f153dd70e6af37e352d061fa22bef7f4903198a31057dd/semgrep-1.19.0.tar.gz" } ], "project_name": "semgrep", @@ -1196,7 +1190,7 @@ "wcmatch~=8.3" ], "requires_python": ">=3.7", - "version": "1.14.0" + "version": "1.19.0" }, { "artifacts": [ @@ -1506,13 +1500,13 @@ "artifacts": [ { "algorithm": "sha256", - "hash": "75edcdc2f7d85b137124a6c3c9fc3933cdeaa12ecb9a6a959f22797a0feca7e1", - "url": "https://files.pythonhosted.org/packages/fe/ca/466766e20b767ddb9b951202542310cba37ea5f2d792dae7589f1741af58/urllib3-1.26.14-py2.py3-none-any.whl" + "hash": "aa751d169e23c7479ce47a0cb0da579e3ede798f994f5816a74e4f4500dcea42", + "url": "https://files.pythonhosted.org/packages/7b/f5/890a0baca17a61c1f92f72b81d3c31523c99bec609e60c292ea55b387ae8/urllib3-1.26.15-py2.py3-none-any.whl" }, { "algorithm": "sha256", - "hash": "076907bf8fd355cde77728471316625a4d2f7e713c125f51953bb5b3eecf4f72", - "url": "https://files.pythonhosted.org/packages/c5/52/fe421fb7364aa738b3506a2d99e4f3a56e079c0a798e9f4fa5e14c60922f/urllib3-1.26.14.tar.gz" + "hash": "8a388717b9476f934a21484e8c8e61875ab60644d29b9b39e11e4b9dc1c6b305", + "url": "https://files.pythonhosted.org/packages/21/79/6372d8c0d0641b4072889f3ff84f279b738cd8595b64c8e0496d4e848122/urllib3-1.26.15.tar.gz" } ], "project_name": "urllib3", @@ -1529,7 +1523,7 @@ "urllib3-secure-extra; extra == \"secure\"" ], "requires_python": "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7", - "version": "1.26.14" + "version": "1.26.15" }, { "artifacts": [ @@ -1593,11 +1587,11 @@ } ], "path_mappings": {}, - "pex_version": "2.1.124", + "pex_version": "2.1.133", "pip_version": "23.0.1", "prefer_older_binary": false, "requirements": [ - "semgrep==1.14.0" + "semgrep<2,>=1.19.0" ], "requires_python": [ "<4,>=3.7" diff --git a/src/python/pants/backend/tools/semgrep/subsystem.py b/src/python/pants/backend/tools/semgrep/subsystem.py index f11418eab7e..3a56d4c3070 100644 --- a/src/python/pants/backend/tools/semgrep/subsystem.py +++ b/src/python/pants/backend/tools/semgrep/subsystem.py @@ -9,7 +9,7 @@ from pants.backend.python.goals import lockfile from pants.backend.python.goals.export import ExportPythonTool, ExportPythonToolSentinel from pants.backend.python.goals.lockfile import GeneratePythonLockfile -from pants.backend.python.subsystems.python_tool_base import ExportToolOption, PythonToolBase +from pants.backend.python.subsystems.python_tool_base import ExportToolOption, LockfileRules, PythonToolBase from pants.backend.python.target_types import ConsoleScript from pants.backend.python.util_rules.pex_requirements import GeneratePythonToolLockfileSentinel from pants.core.goals.generate_lockfiles import GenerateToolLockfileSentinel @@ -37,16 +37,17 @@ class Semgrep(PythonToolBase): options_scope = "semgrep" help = "Lightweight static analysis for many languages. Find bug variants with patterns that look like source code. (https://semgrep.dev/)" - default_version = "semgrep==1.14.0" + default_version = "semgrep>=1.19.0,<2" default_main = ConsoleScript("semgrep") + default_requirements = [default_version] register_interpreter_constraints = True - default_interpreter_constraints = ["CPython>=3.7,<4"] register_lockfile = True default_lockfile_resource = ("pants.backend.tools.semgrep", "semgrep.lock") default_lockfile_path = "src/python/pants/backend/tools/semgrep/semgrep.lock" default_lockfile_url = git_url(default_lockfile_path) + lockfile_rules_type = LockfileRules.SIMPLE export = ExportToolOption() From e2ca370613d2bff33ae6cfd604a5b6c1a27560af Mon Sep 17 00:00:00 2001 From: Huon Wilson Date: Sat, 22 Apr 2023 12:23:47 +1000 Subject: [PATCH 33/49] black, types, timeout --- src/python/pants/backend/tools/semgrep/BUILD | 7 ++++++- src/python/pants/backend/tools/semgrep/subsystem.py | 6 +++++- src/python/pants/backend/tools/semgrep/tailor_test.py | 2 ++ 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/python/pants/backend/tools/semgrep/BUILD b/src/python/pants/backend/tools/semgrep/BUILD index 1ae55aee4e5..aab302d28db 100644 --- a/src/python/pants/backend/tools/semgrep/BUILD +++ b/src/python/pants/backend/tools/semgrep/BUILD @@ -5,4 +5,9 @@ resource(name="lockfile", source="semgrep.lock") python_sources(dependencies=[":lockfile"]) -python_tests(name="tests") +python_tests( + name="tests", + overrides={ + "rules_integration_test.py": dict(timeout=240), + }, +) diff --git a/src/python/pants/backend/tools/semgrep/subsystem.py b/src/python/pants/backend/tools/semgrep/subsystem.py index 3a56d4c3070..06b66a66474 100644 --- a/src/python/pants/backend/tools/semgrep/subsystem.py +++ b/src/python/pants/backend/tools/semgrep/subsystem.py @@ -9,7 +9,11 @@ from pants.backend.python.goals import lockfile from pants.backend.python.goals.export import ExportPythonTool, ExportPythonToolSentinel from pants.backend.python.goals.lockfile import GeneratePythonLockfile -from pants.backend.python.subsystems.python_tool_base import ExportToolOption, LockfileRules, PythonToolBase +from pants.backend.python.subsystems.python_tool_base import ( + ExportToolOption, + LockfileRules, + PythonToolBase, +) from pants.backend.python.target_types import ConsoleScript from pants.backend.python.util_rules.pex_requirements import GeneratePythonToolLockfileSentinel from pants.core.goals.generate_lockfiles import GenerateToolLockfileSentinel diff --git a/src/python/pants/backend/tools/semgrep/tailor_test.py b/src/python/pants/backend/tools/semgrep/tailor_test.py index 9b8773efc2d..2f790cea458 100644 --- a/src/python/pants/backend/tools/semgrep/tailor_test.py +++ b/src/python/pants/backend/tools/semgrep/tailor_test.py @@ -1,6 +1,8 @@ # Copyright 2023 Pants project contributors (see CONTRIBUTORS.md). # Licensed under the Apache License, Version 2.0 (see LICENSE). +from __future__ import annotations + import pytest from pants.backend.tools.semgrep import tailor From a198538782f88d01e03c46626bf024d2ba554249 Mon Sep 17 00:00:00 2001 From: Huon Wilson Date: Sat, 22 Apr 2023 20:15:30 +1000 Subject: [PATCH 34/49] doc tweaks, years --- .../backend/experimental/tools/semgrep/register.py | 2 +- src/python/pants/backend/tools/semgrep/rules.py | 2 +- src/python/pants/backend/tools/semgrep/subsystem.py | 13 +++++++++++-- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/python/pants/backend/experimental/tools/semgrep/register.py b/src/python/pants/backend/experimental/tools/semgrep/register.py index 01bb51096ff..785cff53884 100644 --- a/src/python/pants/backend/experimental/tools/semgrep/register.py +++ b/src/python/pants/backend/experimental/tools/semgrep/register.py @@ -1,4 +1,4 @@ -# Copyright 2022 Pants project contributors (see CONTRIBUTORS.md). +# Copyright 2023 Pants project contributors (see CONTRIBUTORS.md). # Licensed under the Apache License, Version 2.0 (see LICENSE). """Lightweight static analysis for many languages. Find bug variants with patterns that look like diff --git a/src/python/pants/backend/tools/semgrep/rules.py b/src/python/pants/backend/tools/semgrep/rules.py index a4d6f92f0c8..c3d542426f6 100644 --- a/src/python/pants/backend/tools/semgrep/rules.py +++ b/src/python/pants/backend/tools/semgrep/rules.py @@ -1,4 +1,4 @@ -# Copyright 2022 Pants project contributors (see CONTRIBUTORS.md). +# Copyright 2023 Pants project contributors (see CONTRIBUTORS.md). # Licensed under the Apache License, Version 2.0 (see LICENSE). from __future__ import annotations diff --git a/src/python/pants/backend/tools/semgrep/subsystem.py b/src/python/pants/backend/tools/semgrep/subsystem.py index 06b66a66474..034a63c2345 100644 --- a/src/python/pants/backend/tools/semgrep/subsystem.py +++ b/src/python/pants/backend/tools/semgrep/subsystem.py @@ -1,4 +1,4 @@ -# Copyright 2022 Pants project contributors (see CONTRIBUTORS.md). +# Copyright 2023 Pants project contributors (see CONTRIBUTORS.md). # Licensed under the Apache License, Version 2.0 (see LICENSE). from __future__ import annotations @@ -22,6 +22,7 @@ from pants.engine.unions import UnionRule from pants.option.option_types import ArgsListOption, BoolOption, SkipOption from pants.util.docutil import git_url +from pants.util.strutil import softwrap @dataclass(frozen=True) @@ -76,7 +77,15 @@ class Semgrep(PythonToolBase): force = BoolOption( default=False, - help="If true, semgrep is always run, even if the input files haven't changed. This can be used to run cloud rulesets like `--semgrep-force --semgrep-args='--config p/python`.", + help=softwrap( + """\ + If true, semgrep is always run, even if the input files haven't changed. This can be + used to run cloud rulesets like `pants lint --semgrep-force + --semgrep-args='--config=p/python' ::`. Without `--semgrep-force`, using the cloud + rulesets may give inconsistent results on different machines, due to caching, because + the rulesets may change. + """ + ), advanced=True, ) From e2902acd594fd7f4532edfdecda61a0468fc2b84 Mon Sep 17 00:00:00 2001 From: Huon Wilson Date: Sat, 29 Apr 2023 12:11:16 +1000 Subject: [PATCH 35/49] Rename --- .../bin/generate_builtin_lockfiles.py | 4 ++-- .../experimental/tools/semgrep/register.py | 4 ++-- .../pants/backend/tools/semgrep/rules.py | 18 +++++++------- .../tools/semgrep/rules_integration_test.py | 24 +++++++++++-------- .../pants/backend/tools/semgrep/subsystem.py | 10 ++++---- .../pants/backend/tools/semgrep/tailor.py | 4 ++-- .../backend/tools/semgrep/target_types.py | 4 ++-- 7 files changed, 37 insertions(+), 31 deletions(-) diff --git a/build-support/bin/generate_builtin_lockfiles.py b/build-support/bin/generate_builtin_lockfiles.py index 8ee3bf2ab42..ac6f82f4247 100644 --- a/build-support/bin/generate_builtin_lockfiles.py +++ b/build-support/bin/generate_builtin_lockfiles.py @@ -50,7 +50,7 @@ from pants.backend.scala.lint.scalafmt.subsystem import ScalafmtSubsystem from pants.backend.scala.subsystems.scalatest import Scalatest from pants.backend.terraform.dependency_inference import TerraformHcl2Parser -from pants.backend.tools.semgrep.subsystem import Semgrep +from pants.backend.tools.semgrep.subsystem import SemgrepSubsystem from pants.backend.tools.yamllint.subsystem import Yamllint from pants.base.build_environment import get_buildroot from pants.jvm.resolve.jvm_tool import JvmToolBase @@ -122,7 +122,7 @@ class JvmTool(Tool[JvmToolBase]): PythonTool(Pylint, "pants.backend.python.lint.pylint"), PythonTool(PythonProtobufMypyPlugin, "pants.backend.codegen.protobuf.python"), PythonTool(PyOxidizer, "pants.backend.experimental.python.packaging.pyoxidizer"), - PythonTool(Semgrep, "pants.backend.experimental.tools.semgrep"), + PythonTool(SemgrepSubsystem, "pants.backend.experimental.tools.semgrep"), PythonTool(Setuptools, "pants.backend.python"), PythonTool(SetuptoolsSCM, "pants.backend.python"), PythonTool(TerraformHcl2Parser, "pants.backend.experimental.terraform"), diff --git a/src/python/pants/backend/experimental/tools/semgrep/register.py b/src/python/pants/backend/experimental/tools/semgrep/register.py index 785cff53884..87a75b0a022 100644 --- a/src/python/pants/backend/experimental/tools/semgrep/register.py +++ b/src/python/pants/backend/experimental/tools/semgrep/register.py @@ -16,15 +16,15 @@ from pants.backend.tools.semgrep import subsystem as subsystem from pants.backend.tools.semgrep import tailor from pants.backend.tools.semgrep.target_types import ( - SemgrepRuleSource, SemgrepRuleSourcesGeneratorTarget, + SemgrepRuleSourceTarget, ) from pants.engine.rules import Rule from pants.engine.unions import UnionRule def target_types(): - return [SemgrepRuleSource, SemgrepRuleSourcesGeneratorTarget] + return [SemgrepRuleSourceTarget, SemgrepRuleSourcesGeneratorTarget] def rules() -> Iterable[Rule | UnionRule]: diff --git a/src/python/pants/backend/tools/semgrep/rules.py b/src/python/pants/backend/tools/semgrep/rules.py index c3d542426f6..b8ad5f3bd91 100644 --- a/src/python/pants/backend/tools/semgrep/rules.py +++ b/src/python/pants/backend/tools/semgrep/rules.py @@ -22,15 +22,15 @@ from pants.util.logging import LogLevel from pants.util.strutil import pluralize, softwrap -from .subsystem import Semgrep, SemgrepFieldSet +from .subsystem import SemgrepFieldSet, SemgrepSubsystem from .target_types import SemgrepRuleSourceField logger = logging.getLogger(__name__) -class SemgrepRequest(LintTargetsRequest): +class SemgrepLintRequest(LintTargetsRequest): field_set_type = SemgrepFieldSet - tool_subsystem = Semgrep + tool_subsystem = SemgrepSubsystem @dataclass(frozen=True) @@ -46,7 +46,7 @@ def description(self) -> str: _IGNORE_FILE_NAME = ".semgrepignore" -def warn_about_ignore_files_if_required(ignore_files: Snapshot, semgrep: Semgrep) -> None: +def warn_about_ignore_files_if_required(ignore_files: Snapshot, semgrep: SemgrepSubsystem) -> None: non_root_files = sorted(name for name in ignore_files.files if name != _IGNORE_FILE_NAME) if non_root_files and not semgrep.acknowledge_nested_semgrepignore_files_are_not_used: # https://github.com/returntocorp/semgrep/issues/5669 @@ -121,8 +121,8 @@ async def all_semgrep_ignore_files() -> SemgrepIgnoreFiles: @rule async def partition( - request: SemgrepRequest.PartitionRequest[SemgrepFieldSet], - semgrep: Semgrep, + request: SemgrepLintRequest.PartitionRequest[SemgrepFieldSet], + semgrep: SemgrepSubsystem, ignore_files: SemgrepIgnoreFiles, ) -> Partitions: if semgrep.skip: @@ -160,8 +160,8 @@ async def partition( @rule(desc="Lint with Semgrep", level=LogLevel.DEBUG) async def lint( - request: SemgrepRequest.Batch[SemgrepFieldSet, PartitionMetadata], - semgrep: Semgrep, + request: SemgrepLintRequest.Batch[SemgrepFieldSet, PartitionMetadata], + semgrep: SemgrepSubsystem, global_options: GlobalOptions, ) -> LintResult: config_files, semgrep_pex, input_files, settings = await MultiGet( @@ -226,4 +226,4 @@ async def lint( def rules() -> Iterable[Rule | UnionRule]: - return [*collect_rules(), *SemgrepRequest.rules(), *pex.rules()] + return [*collect_rules(), *SemgrepLintRequest.rules(), *pex.rules()] diff --git a/src/python/pants/backend/tools/semgrep/rules_integration_test.py b/src/python/pants/backend/tools/semgrep/rules_integration_test.py index 7a077d39e63..da243308a88 100644 --- a/src/python/pants/backend/tools/semgrep/rules_integration_test.py +++ b/src/python/pants/backend/tools/semgrep/rules_integration_test.py @@ -18,11 +18,11 @@ from pants.testutil.python_interpreter_selection import all_major_minor_python_versions from pants.testutil.rule_runner import QueryRule, RuleRunner -from .rules import PartitionMetadata, SemgrepRequest +from .rules import PartitionMetadata, SemgrepLintRequest from .rules import rules as semgrep_rules -from .subsystem import Semgrep, SemgrepFieldSet +from .subsystem import SemgrepFieldSet, SemgrepSubsystem from .subsystem import rules as semgrep_subsystem_rules -from .target_types import SemgrepRuleSource, SemgrepRuleSourcesGeneratorTarget +from .target_types import SemgrepRuleSourcesGeneratorTarget, SemgrepRuleSourceTarget DIR = "src" @@ -90,11 +90,11 @@ def rule_runner() -> RuleRunner: *semgrep_rules(), *semgrep_subsystem_rules(), *source_files.rules(), - QueryRule(Partitions, (SemgrepRequest.PartitionRequest,)), - QueryRule(LintResult, (SemgrepRequest.Batch,)), + QueryRule(Partitions, (SemgrepLintRequest.PartitionRequest,)), + QueryRule(LintResult, (SemgrepLintRequest.Batch,)), QueryRule(SourceFiles, (SourceFilesRequest,)), ], - target_types=[SemgrepRuleSource, SemgrepRuleSourcesGeneratorTarget, FileTarget], + target_types=[SemgrepRuleSourceTarget, SemgrepRuleSourcesGeneratorTarget, FileTarget], ) @@ -116,12 +116,16 @@ def run_semgrep( ) partitions = rule_runner.request( Partitions[SemgrepFieldSet, PartitionMetadata], - [SemgrepRequest.PartitionRequest(tuple(SemgrepFieldSet.create(tgt) for tgt in targets))], + [ + SemgrepLintRequest.PartitionRequest( + tuple(SemgrepFieldSet.create(tgt) for tgt in targets) + ) + ], ) return tuple( rule_runner.request( - LintResult, [SemgrepRequest.Batch("", partition.elements, partition.metadata)] + LintResult, [SemgrepLintRequest.Batch("", partition.elements, partition.metadata)] ) for partition in partitions ) @@ -142,7 +146,7 @@ def assert_success( @pytest.mark.parametrize( "major_minor_interpreter", - all_major_minor_python_versions(Semgrep.default_interpreter_constraints), + all_major_minor_python_versions(SemgrepSubsystem.default_interpreter_constraints), ) def test_passing(rule_runner: RuleRunner, major_minor_interpreter: str) -> None: rule_runner.write_files(GOOD_FILE_LAYOUT) @@ -313,7 +317,7 @@ def semgrep(dir: str) -> dict[str, str]: partitions = rule_runner.request( Partitions[SemgrepFieldSet, PartitionMetadata], - [SemgrepRequest.PartitionRequest(field_sets)], + [SemgrepLintRequest.PartitionRequest(field_sets)], ) sorted_partitions = sorted( diff --git a/src/python/pants/backend/tools/semgrep/subsystem.py b/src/python/pants/backend/tools/semgrep/subsystem.py index 034a63c2345..95072c16c7c 100644 --- a/src/python/pants/backend/tools/semgrep/subsystem.py +++ b/src/python/pants/backend/tools/semgrep/subsystem.py @@ -37,7 +37,7 @@ def opt_out(cls, tgt: Target) -> bool: return False -class Semgrep(PythonToolBase): +class SemgrepSubsystem(PythonToolBase): name = "Semgrep" options_scope = "semgrep" help = "Lightweight static analysis for many languages. Find bug variants with patterns that look like source code. (https://semgrep.dev/)" @@ -91,11 +91,13 @@ class Semgrep(PythonToolBase): class SemgrepLockfileSentinel(GeneratePythonToolLockfileSentinel): - resolve_name = Semgrep.options_scope + resolve_name = SemgrepSubsystem.options_scope @rule -def setup_semgrep_lockfile(_: SemgrepLockfileSentinel, semgrep: Semgrep) -> GeneratePythonLockfile: +def setup_semgrep_lockfile( + _: SemgrepLockfileSentinel, semgrep: SemgrepSubsystem +) -> GeneratePythonLockfile: return semgrep.to_lockfile_request() @@ -104,7 +106,7 @@ class SemgrepExportSentinel(ExportPythonToolSentinel): @rule -def semgrep_export(_: SemgrepExportSentinel, semgrep: Semgrep) -> ExportPythonTool: +def semgrep_export(_: SemgrepExportSentinel, semgrep: SemgrepSubsystem) -> ExportPythonTool: if not semgrep.export: return ExportPythonTool(resolve_name=semgrep.options_scope, pex_request=None) return ExportPythonTool( diff --git a/src/python/pants/backend/tools/semgrep/tailor.py b/src/python/pants/backend/tools/semgrep/tailor.py index 361c3237fc3..703315e2dba 100644 --- a/src/python/pants/backend/tools/semgrep/tailor.py +++ b/src/python/pants/backend/tools/semgrep/tailor.py @@ -8,7 +8,7 @@ from dataclasses import dataclass from typing import Iterable -from pants.backend.tools.semgrep.subsystem import Semgrep +from pants.backend.tools.semgrep.subsystem import SemgrepSubsystem from pants.backend.tools.semgrep.target_types import SemgrepRuleSourcesGeneratorTarget from pants.core.goals.tailor import ( AllOwnedSources, @@ -47,7 +47,7 @@ def _group_by_semgrep_dir(paths: Iterable[str]) -> dict[str, set[str]]: async def find_putative_targets( req: PutativeSemgrepTargetsRequest, all_owned_sources: AllOwnedSources, - semgrep: Semgrep, + semgrep: SemgrepSubsystem, ) -> PutativeTargets: pts = [] diff --git a/src/python/pants/backend/tools/semgrep/target_types.py b/src/python/pants/backend/tools/semgrep/target_types.py index 6557867ee10..e7fd539c5ea 100644 --- a/src/python/pants/backend/tools/semgrep/target_types.py +++ b/src/python/pants/backend/tools/semgrep/target_types.py @@ -32,7 +32,7 @@ class SemgrepRuleGeneratingSourcesField(MultipleSourcesField): ) -class SemgrepRuleSource(Target): +class SemgrepRuleSourceTarget(Target): alias = "semgrep_rule_source" core_fields = (*COMMON_TARGET_FIELDS, SemgrepRuleSourceField) @@ -45,7 +45,7 @@ class SemgrepRuleSourcesGeneratorTarget(TargetFilesGenerator): *COMMON_TARGET_FIELDS, SemgrepRuleGeneratingSourcesField, ) - generated_target_cls = SemgrepRuleSource + generated_target_cls = SemgrepRuleSourceTarget copied_fields = COMMON_TARGET_FIELDS moved_fields = () help = "Generate a `semgrep_rule_source` target for each file in the `sources` field." From cee2487724b5410ec348f3ffee1456e96d5f3947 Mon Sep 17 00:00:00 2001 From: Huon Wilson Date: Sat, 29 Apr 2023 12:11:24 +1000 Subject: [PATCH 36/49] Outdated features --- src/python/pants/backend/tools/semgrep/subsystem.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/python/pants/backend/tools/semgrep/subsystem.py b/src/python/pants/backend/tools/semgrep/subsystem.py index 95072c16c7c..aa8c4bbd77e 100644 --- a/src/python/pants/backend/tools/semgrep/subsystem.py +++ b/src/python/pants/backend/tools/semgrep/subsystem.py @@ -21,7 +21,6 @@ from pants.engine.target import Dependencies, FieldSet, SingleSourceField, Target from pants.engine.unions import UnionRule from pants.option.option_types import ArgsListOption, BoolOption, SkipOption -from pants.util.docutil import git_url from pants.util.strutil import softwrap @@ -51,7 +50,6 @@ class SemgrepSubsystem(PythonToolBase): register_lockfile = True default_lockfile_resource = ("pants.backend.tools.semgrep", "semgrep.lock") default_lockfile_path = "src/python/pants/backend/tools/semgrep/semgrep.lock" - default_lockfile_url = git_url(default_lockfile_path) lockfile_rules_type = LockfileRules.SIMPLE export = ExportToolOption() From 05c49c43d276a6c90e1f99c582b158b29611843f Mon Sep 17 00:00:00 2001 From: Huon Wilson Date: Sat, 29 Apr 2023 12:15:12 +1000 Subject: [PATCH 37/49] Update to semgrep 1.20 --- .../pants/backend/tools/semgrep/semgrep.lock | 56 +++++++++---------- .../pants/backend/tools/semgrep/subsystem.py | 2 +- 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/src/python/pants/backend/tools/semgrep/semgrep.lock b/src/python/pants/backend/tools/semgrep/semgrep.lock index b1434ff52af..99629988501 100644 --- a/src/python/pants/backend/tools/semgrep/semgrep.lock +++ b/src/python/pants/backend/tools/semgrep/semgrep.lock @@ -9,7 +9,7 @@ // "CPython<4,>=3.7" // ], // "generated_with_requirements": [ -// "semgrep<2,>=1.19.0" +// "semgrep<2,>=1.20.0" // ], // "manylinux": "manylinux2014", // "requirement_constraints": [], @@ -604,13 +604,13 @@ "artifacts": [ { "algorithm": "sha256", - "hash": "03ba783c3a2c69d751b109fc0c94a62c51f581b3d6acf8ed1331b6d5729321ff", - "url": "https://files.pythonhosted.org/packages/50/a2/75da1ccf2cc043aeb866e18c9a196f0b42abfe5d421c657019c68ebec1e9/importlib_metadata-6.5.0-py3-none-any.whl" + "hash": "43dd286a2cd8995d5eaef7fee2066340423b818ed3fd70adf0bad5f1fac53fed", + "url": "https://files.pythonhosted.org/packages/30/bb/bf2944b8b88c65b797acc2c6a2cb0fb817f7364debf0675792e034013858/importlib_metadata-6.6.0-py3-none-any.whl" }, { "algorithm": "sha256", - "hash": "7a8bdf1bc3a726297f5cfbc999e6e7ff6b4fa41b26bba4afc580448624460045", - "url": "https://files.pythonhosted.org/packages/9d/ce/dc7221b3044c7382d5abad27d2599e63b0e0ccabc49491244203ee1ded96/importlib_metadata-6.5.0.tar.gz" + "hash": "92501cdf9cc66ebd3e612f1b4f0c0765dfa42f0fa38ffb319b6bd84dd675d705", + "url": "https://files.pythonhosted.org/packages/0b/1f/9de392c2b939384e08812ef93adf37684ec170b5b6e7ea302d9f163c2ea0/importlib_metadata-6.6.0.tar.gz" } ], "project_name": "importlib-metadata", @@ -639,7 +639,7 @@ "zipp>=0.5" ], "requires_python": ">=3.7", - "version": "6.5.0" + "version": "6.6.0" }, { "artifacts": [ @@ -970,13 +970,13 @@ "artifacts": [ { "algorithm": "sha256", - "hash": "64299f4909223da747622c030b781c0d7811e359c37124b4bd368fb8c6518baa", - "url": "https://files.pythonhosted.org/packages/d2/f4/274d1dbe96b41cf4e0efb70cbced278ffd61b5c7bb70338b62af94ccb25b/requests-2.28.2-py3-none-any.whl" + "hash": "e8f3c9be120d3333921d213eef078af392fba3933ab7ed2d1cba3b56f2568c3b", + "url": "https://files.pythonhosted.org/packages/cf/e1/2aa539876d9ed0ddc95882451deb57cfd7aa8dbf0b8dbce68e045549ba56/requests-2.29.0-py3-none-any.whl" }, { "algorithm": "sha256", - "hash": "98b1b2782e3c6c4904938b84c0eb932721069dfdb9134313beff7c83c2df24bf", - "url": "https://files.pythonhosted.org/packages/9d/ee/391076f5937f0a8cdf5e53b701ffc91753e87b07d66bae4a09aa671897bf/requests-2.28.2.tar.gz" + "hash": "f2e34a75f4749019bb0e3effb66683630e4ffeaf75819fb51bebef1bf5aef059", + "url": "https://files.pythonhosted.org/packages/4c/d2/70fc708727b62d55bc24e43cc85f073039023212d482553d853c44e57bdb/requests-2.29.0.tar.gz" } ], "project_name": "requests", @@ -988,20 +988,20 @@ "idna<4,>=2.5", "urllib3<1.27,>=1.21.1" ], - "requires_python": "<4,>=3.7", - "version": "2.28.2" + "requires_python": ">=3.7", + "version": "2.29.0" }, { "artifacts": [ { "algorithm": "sha256", - "hash": "22b74cae0278fd5086ff44144d3813be1cedc9115bdfabbfefd86400cb88b20a", - "url": "https://files.pythonhosted.org/packages/9d/1a/28117ae737aec7c004ed5067034a8949adab43730420b50312821f466c3f/rich-13.3.4-py3-none-any.whl" + "hash": "69cdf53799e63f38b95b9bf9c875f8c90e78dd62b2f00c13a911c7a3b9fa4704", + "url": "https://files.pythonhosted.org/packages/39/03/6de23bdd88f5ee7f8b03f94f6e88108f5d7ffe6d207e95cdb06d9aa4cd57/rich-13.3.5-py3-none-any.whl" }, { "algorithm": "sha256", - "hash": "b5d573e13605423ec80bdd0cd5f8541f7844a0e71a13f74cf454ccb2f490708b", - "url": "https://files.pythonhosted.org/packages/31/3b/2360352760b436f822258396e66ffb6d42585518a9cde2f93f142e64c5eb/rich-13.3.4.tar.gz" + "hash": "2d11b9b8dd03868f09b4fffadc84a6a8cda574e40dc90821bd845720ebb8e89c", + "url": "https://files.pythonhosted.org/packages/3d/0b/8dd34d20929c4b5e474db2e64426175469c2b7fea5ba71c6d4b3397a9729/rich-13.3.5.tar.gz" } ], "project_name": "rich", @@ -1012,7 +1012,7 @@ "typing-extensions<5.0,>=4.0.0; python_version < \"3.9\"" ], "requires_python": ">=3.7.0", - "version": "13.3.4" + "version": "13.3.5" }, { "artifacts": [ @@ -1148,23 +1148,23 @@ "artifacts": [ { "algorithm": "sha256", - "hash": "89cfde0b8bb5a3b0624d9710ec7ac73e74598bf227c3c61fd1fa489dd29e33eb", - "url": "https://files.pythonhosted.org/packages/a5/59/4ffda356d7a85b84746c44e81fdcff8bffb155e5dda9933508c7f53d7b99/semgrep-1.19.0-cp37.cp38.cp39.py37.py38.py39-none-macosx_11_0_arm64.whl" + "hash": "517ed80d50a408d18851ffd74323f43c78650c232b6a8c1defce92ec42d9f575", + "url": "https://files.pythonhosted.org/packages/4a/b0/4a46f38b4661576e3c12df7f80e3a7bea846bf1efe942da7bbf4fc27d6a7/semgrep-1.20.0-cp37.cp38.cp39.py37.py38.py39-none-macosx_11_0_arm64.whl" }, { "algorithm": "sha256", - "hash": "723429f9548bd4c959b12c25b6f88fc48858c129d0a13f0fc7904be866f344de", - "url": "https://files.pythonhosted.org/packages/a5/0f/87759b6f16c2195f367b3a5543190d823449a66263edd2e713c2688765b1/semgrep-1.19.0-cp37.cp38.cp39.py37.py38.py39-none-any.whl" + "hash": "db43b3fdbe427f9fe0caf8bafaf681fd3478a128e2b887a8036067cb02d3f272", + "url": "https://files.pythonhosted.org/packages/10/ac/d7972c3cf4bd5d9c0cf5d22d34f640967dccf005e6b5e60d78db997afb96/semgrep-1.20.0.tar.gz" }, { "algorithm": "sha256", - "hash": "e59784801146a8d799bc553da68e5e5acbf338a3cae1e8486a9b7aaa925cb266", - "url": "https://files.pythonhosted.org/packages/c4/32/3642576d14b367cd68c5842112a4ac2763b2de72568d3a78e38e94ae5391/semgrep-1.19.0-cp37.cp38.cp39.py37.py38.py39-none-macosx_10_14_x86_64.whl" + "hash": "2b543d367acedaf5d3efa07d63b8f2444880255ce35d1106778db68ec559da5d", + "url": "https://files.pythonhosted.org/packages/a6/67/8b96236d72f9a4afd633e25ff9d37a5e54b1d8ceb2ab4c45e93b3d2e0d9e/semgrep-1.20.0-cp37.cp38.cp39.py37.py38.py39-none-macosx_10_14_x86_64.whl" }, { "algorithm": "sha256", - "hash": "5d5e32ffb1bd963e0ede87f0b801305b8367ad660a73f8e0dd8e14f0c8d3e956", - "url": "https://files.pythonhosted.org/packages/ee/68/573843402f3025f153dd70e6af37e352d061fa22bef7f4903198a31057dd/semgrep-1.19.0.tar.gz" + "hash": "613fc656ee23771d0e7ad9f093366e6446787e5ef4b84c93e106576eb1ef4cd7", + "url": "https://files.pythonhosted.org/packages/eb/49/58c6f38a458e42c86ae807f0bd5fab00a745e4125f0eef4851164f44ed69/semgrep-1.20.0-cp37.cp38.cp39.py37.py38.py39-none-any.whl" } ], "project_name": "semgrep", @@ -1190,7 +1190,7 @@ "wcmatch~=8.3" ], "requires_python": ">=3.7", - "version": "1.19.0" + "version": "1.20.0" }, { "artifacts": [ @@ -1587,11 +1587,11 @@ } ], "path_mappings": {}, - "pex_version": "2.1.133", + "pex_version": "2.1.134", "pip_version": "23.0.1", "prefer_older_binary": false, "requirements": [ - "semgrep<2,>=1.19.0" + "semgrep<2,>=1.20.0" ], "requires_python": [ "<4,>=3.7" diff --git a/src/python/pants/backend/tools/semgrep/subsystem.py b/src/python/pants/backend/tools/semgrep/subsystem.py index aa8c4bbd77e..70d274065a5 100644 --- a/src/python/pants/backend/tools/semgrep/subsystem.py +++ b/src/python/pants/backend/tools/semgrep/subsystem.py @@ -41,7 +41,7 @@ class SemgrepSubsystem(PythonToolBase): options_scope = "semgrep" help = "Lightweight static analysis for many languages. Find bug variants with patterns that look like source code. (https://semgrep.dev/)" - default_version = "semgrep>=1.19.0,<2" + default_version = "semgrep>=1.20.0,<2" default_main = ConsoleScript("semgrep") default_requirements = [default_version] From a3c7e07b4f8d2841bf2a7e87401e8b908832a2a4 Mon Sep 17 00:00:00 2001 From: Huon Wilson Date: Sat, 29 Apr 2023 12:20:15 +1000 Subject: [PATCH 38/49] loop to method --- .../pants/backend/tools/semgrep/rules.py | 39 ++++++++++--------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/src/python/pants/backend/tools/semgrep/rules.py b/src/python/pants/backend/tools/semgrep/rules.py index b8ad5f3bd91..3dee30da05a 100644 --- a/src/python/pants/backend/tools/semgrep/rules.py +++ b/src/python/pants/backend/tools/semgrep/rules.py @@ -13,6 +13,7 @@ from pants.core.goals.lint import LintResult, LintTargetsRequest from pants.core.util_rules.partitions import Partition, Partitions from pants.core.util_rules.source_files import SourceFiles, SourceFilesRequest +from pants.engine.addresses import Address from pants.engine.fs import CreateDigest, Digest, FileContent, MergeDigests, PathGlobs, Snapshot from pants.engine.process import FallibleProcessResult, ProcessCacheScope from pants.engine.rules import Get, MultiGet, Rule, collect_rules, rule @@ -73,6 +74,24 @@ class SemgrepIgnoreFiles: class AllSemgrepConfigs: targets: dict[str, list[Target]] + def ancestor_targets(self, address: Address) -> Iterable[Target]: + # TODO: introspect the semgrep rules and determine which (if any) apply to the files, e.g. a # + # Python file shouldn't depend on a .semgrep.yml that doesn't have any 'python' or 'generic' # + # rules, and similarly if there's path inclusions/exclusions. + # TODO: this would be better as actual dependency inference (e.g. allows inspection, manual + # addition/exclusion), but that can only infer 'full' dependencies and it is wrong (e.g. JVM + # things break) for real code files to depend on this sort of non-code linter config; requires + # dependency scopes or similar (https://github.com/pantsbuild/pants/issues/12794) + spec = address.spec_path + + while True: + yield from self.targets.get(spec, []) + + if not spec: + break + + spec = os.path.dirname(spec) + @rule async def find_all_semgrep_configs(all_targets: AllTargets) -> AllSemgrepConfigs: @@ -92,25 +111,7 @@ class InferSemgrepDependenciesRequest: async def infer_semgrep_dependencies( request: InferSemgrepDependenciesRequest, all_semgrep: AllSemgrepConfigs ) -> Targets: - # TODO: introspect the semgrep rules and determine which (if any) apply to the files, e.g. a # - # Python file shouldn't depend on a .semgrep.yml that doesn't have any 'python' or 'generic' # - # rules, and similarly if there's path inclusions/exclusions. - # TODO: this would be better as actual dependency inference (e.g. allows inspection, manual - # addition/exclusion), but that can only infer 'full' dependencies and it is wrong (e.g. JVM - # things break) for real code files to depend on this sort of non-code linter config; requires - # dependency scopes or similar (https://github.com/pantsbuild/pants/issues/12794) - spec = request.field_set.address.spec_path - found: list[Target] = [] - - while True: - found.extend(all_semgrep.targets.get(spec, [])) - - if not spec: - break - - spec = os.path.dirname(spec) - - return Targets(found) + return Targets(tuple(all_semgrep.ancestor_targets(request.field_set.address))) @rule From 195d0908520d7696a79dd03cfc2fca77caf8a45a Mon Sep 17 00:00:00 2001 From: Huon Wilson Date: Fri, 5 May 2023 19:28:38 +1000 Subject: [PATCH 39/49] pathlib for ancestor_targets --- .../pants/backend/tools/semgrep/rules.py | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/src/python/pants/backend/tools/semgrep/rules.py b/src/python/pants/backend/tools/semgrep/rules.py index 3dee30da05a..c60a983096d 100644 --- a/src/python/pants/backend/tools/semgrep/rules.py +++ b/src/python/pants/backend/tools/semgrep/rules.py @@ -2,10 +2,11 @@ # Licensed under the Apache License, Version 2.0 (see LICENSE). from __future__ import annotations +import itertools import logging -import os from collections import defaultdict from dataclasses import dataclass +from pathlib import Path from typing import Iterable from pants.backend.python.util_rules import pex @@ -75,22 +76,18 @@ class AllSemgrepConfigs: targets: dict[str, list[Target]] def ancestor_targets(self, address: Address) -> Iterable[Target]: - # TODO: introspect the semgrep rules and determine which (if any) apply to the files, e.g. a # - # Python file shouldn't depend on a .semgrep.yml that doesn't have any 'python' or 'generic' # + # TODO: introspect the semgrep rules and determine which (if any) apply to the files, e.g. a + # Python file shouldn't depend on a .semgrep.yml that doesn't have any 'python' or 'generic' # rules, and similarly if there's path inclusions/exclusions. # TODO: this would be better as actual dependency inference (e.g. allows inspection, manual # addition/exclusion), but that can only infer 'full' dependencies and it is wrong (e.g. JVM # things break) for real code files to depend on this sort of non-code linter config; requires # dependency scopes or similar (https://github.com/pantsbuild/pants/issues/12794) - spec = address.spec_path + spec = Path(address.spec_path) - while True: - yield from self.targets.get(spec, []) - - if not spec: - break - - spec = os.path.dirname(spec) + for ancestor in itertools.chain([spec], spec.parents): + spec_path = str(ancestor) + yield from self.targets.get("" if spec_path == "." else spec_path, []) @rule From 9c684313a44a5b2551d12994be20a2e9df34b469 Mon Sep 17 00:00:00 2001 From: Huon Wilson Date: Fri, 5 May 2023 19:41:02 +1000 Subject: [PATCH 40/49] Remove unnecessary lockfile juggling --- .../pants/backend/tools/semgrep/subsystem.py | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/src/python/pants/backend/tools/semgrep/subsystem.py b/src/python/pants/backend/tools/semgrep/subsystem.py index 70d274065a5..66634332c34 100644 --- a/src/python/pants/backend/tools/semgrep/subsystem.py +++ b/src/python/pants/backend/tools/semgrep/subsystem.py @@ -6,17 +6,13 @@ from dataclasses import dataclass from typing import Iterable -from pants.backend.python.goals import lockfile from pants.backend.python.goals.export import ExportPythonTool, ExportPythonToolSentinel -from pants.backend.python.goals.lockfile import GeneratePythonLockfile from pants.backend.python.subsystems.python_tool_base import ( ExportToolOption, LockfileRules, PythonToolBase, ) from pants.backend.python.target_types import ConsoleScript -from pants.backend.python.util_rules.pex_requirements import GeneratePythonToolLockfileSentinel -from pants.core.goals.generate_lockfiles import GenerateToolLockfileSentinel from pants.engine.rules import Rule, collect_rules, rule from pants.engine.target import Dependencies, FieldSet, SingleSourceField, Target from pants.engine.unions import UnionRule @@ -49,7 +45,6 @@ class SemgrepSubsystem(PythonToolBase): register_lockfile = True default_lockfile_resource = ("pants.backend.tools.semgrep", "semgrep.lock") - default_lockfile_path = "src/python/pants/backend/tools/semgrep/semgrep.lock" lockfile_rules_type = LockfileRules.SIMPLE export = ExportToolOption() @@ -88,17 +83,6 @@ class SemgrepSubsystem(PythonToolBase): ) -class SemgrepLockfileSentinel(GeneratePythonToolLockfileSentinel): - resolve_name = SemgrepSubsystem.options_scope - - -@rule -def setup_semgrep_lockfile( - _: SemgrepLockfileSentinel, semgrep: SemgrepSubsystem -) -> GeneratePythonLockfile: - return semgrep.to_lockfile_request() - - class SemgrepExportSentinel(ExportPythonToolSentinel): pass @@ -115,7 +99,5 @@ def semgrep_export(_: SemgrepExportSentinel, semgrep: SemgrepSubsystem) -> Expor def rules() -> Iterable[Rule | UnionRule]: return ( *collect_rules(), - *lockfile.rules(), - UnionRule(GenerateToolLockfileSentinel, SemgrepLockfileSentinel), UnionRule(ExportPythonToolSentinel, SemgrepExportSentinel), ) From ace84a692b813ba7cf4e9c0fede19dd6cbc91c0e Mon Sep 17 00:00:00 2001 From: Huon Wilson Date: Fri, 5 May 2023 19:42:51 +1000 Subject: [PATCH 41/49] Remove fancy ignore file handling for now --- .../pants/backend/tools/semgrep/rules.py | 22 +------------- .../tools/semgrep/rules_integration_test.py | 29 ------------------- .../pants/backend/tools/semgrep/subsystem.py | 5 ---- 3 files changed, 1 insertion(+), 55 deletions(-) diff --git a/src/python/pants/backend/tools/semgrep/rules.py b/src/python/pants/backend/tools/semgrep/rules.py index c60a983096d..912aaaf0079 100644 --- a/src/python/pants/backend/tools/semgrep/rules.py +++ b/src/python/pants/backend/tools/semgrep/rules.py @@ -22,7 +22,7 @@ from pants.engine.unions import UnionRule from pants.option.global_options import GlobalOptions from pants.util.logging import LogLevel -from pants.util.strutil import pluralize, softwrap +from pants.util.strutil import pluralize from .subsystem import SemgrepFieldSet, SemgrepSubsystem from .target_types import SemgrepRuleSourceField @@ -48,24 +48,6 @@ def description(self) -> str: _IGNORE_FILE_NAME = ".semgrepignore" -def warn_about_ignore_files_if_required(ignore_files: Snapshot, semgrep: SemgrepSubsystem) -> None: - non_root_files = sorted(name for name in ignore_files.files if name != _IGNORE_FILE_NAME) - if non_root_files and not semgrep.acknowledge_nested_semgrepignore_files_are_not_used: - # https://github.com/returntocorp/semgrep/issues/5669 - logger.warning( - softwrap( - f""" - Semgrep does not obey {_IGNORE_FILE_NAME} outside the working directory, which is - the build root when run by pants. These files may not have the desired effect: - {', '.join(non_root_files)} - - Set `acknowledge_nested_semgrepignore_files_are_not_used = true` in the `[semgrep]` - section of pants.toml to silence this warning. - """ - ) - ) - - @dataclass class SemgrepIgnoreFiles: snapshot: Snapshot @@ -126,8 +108,6 @@ async def partition( if semgrep.skip: return Partitions() - warn_about_ignore_files_if_required(ignore_files.snapshot, semgrep) - dependencies = await MultiGet( Get(Targets, InferSemgrepDependenciesRequest(field_set)) for field_set in request.field_sets ) diff --git a/src/python/pants/backend/tools/semgrep/rules_integration_test.py b/src/python/pants/backend/tools/semgrep/rules_integration_test.py index da243308a88..fad1ad91598 100644 --- a/src/python/pants/backend/tools/semgrep/rules_integration_test.py +++ b/src/python/pants/backend/tools/semgrep/rules_integration_test.py @@ -253,35 +253,6 @@ def test_semgrepignore(rule_runner: RuleRunner) -> None: assert result.report == EMPTY_DIGEST -def test_semgrepignore_nested_ignored(rule_runner: RuleRunner, caplog) -> None: - rule_runner.write_files({**BAD_FILE_LAYOUT, f"{DIR}/.semgrepignore": "file.txt"}) - - tgt = rule_runner.get_target(Address(DIR, target_name="f")) - results = run_semgrep(rule_runner, [tgt]) - - # a nested semgrep ignore file is completely unused... - assert len(results) == 1 - result = results[0] - assert "find-bad-pattern" in result.stdout - assert "Ran 1 rule on 1 file: 1 finding" in result.stderr - assert result.exit_code == SEMGREP_ERROR_FAILURE_RETURN_CODE - assert result.report == EMPTY_DIGEST - - # ...but there's a pants warning about it... - assert "Semgrep does not obey .semgrepignore outside the working directory" in caplog.text - assert f"{DIR}/.semgrepignore" in caplog.text - - caplog.clear() - - # ... that can be silenced - results = run_semgrep( - rule_runner, - [tgt], - extra_args=["--semgrep-acknowledge-nested-semgrepignore-files-are-not-used"], - ) - assert not caplog.records - - def test_partition_by_config(rule_runner: RuleRunner) -> None: file_dirs = [] diff --git a/src/python/pants/backend/tools/semgrep/subsystem.py b/src/python/pants/backend/tools/semgrep/subsystem.py index 66634332c34..ba911009bb9 100644 --- a/src/python/pants/backend/tools/semgrep/subsystem.py +++ b/src/python/pants/backend/tools/semgrep/subsystem.py @@ -63,11 +63,6 @@ class SemgrepSubsystem(PythonToolBase): advanced=True, ) - acknowledge_nested_semgrepignore_files_are_not_used = BoolOption( - default=False, - help="Set to true suppress the warning about `.semgrepignore` files not at the build root not being used", - ) - force = BoolOption( default=False, help=softwrap( From 25c4f480828178a45ef6a652be0011957db341c7 Mon Sep 17 00:00:00 2001 From: Huon Wilson Date: Mon, 28 Aug 2023 11:50:02 +1000 Subject: [PATCH 42/49] Tweaks for upstream changes --- .../experimental/tools/semgrep/__init__.py | 0 .../pants/backend/tools/semgrep/subsystem.py | 33 +++---------------- .../pants/backend/tools/semgrep/tailor.py | 2 +- 3 files changed, 5 insertions(+), 30 deletions(-) create mode 100644 src/python/pants/backend/experimental/tools/semgrep/__init__.py diff --git a/src/python/pants/backend/experimental/tools/semgrep/__init__.py b/src/python/pants/backend/experimental/tools/semgrep/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/python/pants/backend/tools/semgrep/subsystem.py b/src/python/pants/backend/tools/semgrep/subsystem.py index ba911009bb9..4cffaa7e75d 100644 --- a/src/python/pants/backend/tools/semgrep/subsystem.py +++ b/src/python/pants/backend/tools/semgrep/subsystem.py @@ -6,14 +6,9 @@ from dataclasses import dataclass from typing import Iterable -from pants.backend.python.goals.export import ExportPythonTool, ExportPythonToolSentinel -from pants.backend.python.subsystems.python_tool_base import ( - ExportToolOption, - LockfileRules, - PythonToolBase, -) +from pants.backend.python.subsystems.python_tool_base import PythonToolBase from pants.backend.python.target_types import ConsoleScript -from pants.engine.rules import Rule, collect_rules, rule +from pants.engine.rules import Rule, collect_rules from pants.engine.target import Dependencies, FieldSet, SingleSourceField, Target from pants.engine.unions import UnionRule from pants.option.option_types import ArgsListOption, BoolOption, SkipOption @@ -37,17 +32,13 @@ class SemgrepSubsystem(PythonToolBase): options_scope = "semgrep" help = "Lightweight static analysis for many languages. Find bug variants with patterns that look like source code. (https://semgrep.dev/)" - default_version = "semgrep>=1.20.0,<2" default_main = ConsoleScript("semgrep") - default_requirements = [default_version] + default_requirements = ["semgrep>=1.20.0,<2"] register_interpreter_constraints = True register_lockfile = True default_lockfile_resource = ("pants.backend.tools.semgrep", "semgrep.lock") - lockfile_rules_type = LockfileRules.SIMPLE - - export = ExportToolOption() args = ArgsListOption( example="--verbose", @@ -78,21 +69,5 @@ class SemgrepSubsystem(PythonToolBase): ) -class SemgrepExportSentinel(ExportPythonToolSentinel): - pass - - -@rule -def semgrep_export(_: SemgrepExportSentinel, semgrep: SemgrepSubsystem) -> ExportPythonTool: - if not semgrep.export: - return ExportPythonTool(resolve_name=semgrep.options_scope, pex_request=None) - return ExportPythonTool( - resolve_name=semgrep.options_scope, pex_request=semgrep.to_pex_request() - ) - - def rules() -> Iterable[Rule | UnionRule]: - return ( - *collect_rules(), - UnionRule(ExportPythonToolSentinel, SemgrepExportSentinel), - ) + return collect_rules() diff --git a/src/python/pants/backend/tools/semgrep/tailor.py b/src/python/pants/backend/tools/semgrep/tailor.py index 703315e2dba..89ca412bc2d 100644 --- a/src/python/pants/backend/tools/semgrep/tailor.py +++ b/src/python/pants/backend/tools/semgrep/tailor.py @@ -43,7 +43,7 @@ def _group_by_semgrep_dir(paths: Iterable[str]) -> dict[str, set[str]]: return ret -@rule(level=LogLevel.DEBUG, desc="Determine candidate Python targets to create") +@rule(level=LogLevel.DEBUG, desc="Determine candidate Semgrep targets to create") async def find_putative_targets( req: PutativeSemgrepTargetsRequest, all_owned_sources: AllOwnedSources, From 385666ac7ef6e1ef0e52bfdc05b78598a337fc94 Mon Sep 17 00:00:00 2001 From: Huon Wilson Date: Mon, 28 Aug 2023 12:52:53 +1000 Subject: [PATCH 43/49] Rewrite to just use pathglobs, no new targets --- .../pants/backend/tools/semgrep/rules.py | 89 ++++++++++++------- .../tools/semgrep/rules_integration_test.py | 2 +- 2 files changed, 58 insertions(+), 33 deletions(-) diff --git a/src/python/pants/backend/tools/semgrep/rules.py b/src/python/pants/backend/tools/semgrep/rules.py index 912aaaf0079..7fa86e08bba 100644 --- a/src/python/pants/backend/tools/semgrep/rules.py +++ b/src/python/pants/backend/tools/semgrep/rules.py @@ -15,17 +15,23 @@ from pants.core.util_rules.partitions import Partition, Partitions from pants.core.util_rules.source_files import SourceFiles, SourceFilesRequest from pants.engine.addresses import Address -from pants.engine.fs import CreateDigest, Digest, FileContent, MergeDigests, PathGlobs, Snapshot +from pants.engine.fs import ( + CreateDigest, + Digest, + FileContent, + MergeDigests, + PathGlobs, + Paths, + Snapshot, +) from pants.engine.process import FallibleProcessResult, ProcessCacheScope from pants.engine.rules import Get, MultiGet, Rule, collect_rules, rule -from pants.engine.target import AllTargets, Target, Targets from pants.engine.unions import UnionRule from pants.option.global_options import GlobalOptions from pants.util.logging import LogLevel from pants.util.strutil import pluralize from .subsystem import SemgrepFieldSet, SemgrepSubsystem -from .target_types import SemgrepRuleSourceField logger = logging.getLogger(__name__) @@ -37,16 +43,24 @@ class SemgrepLintRequest(LintTargetsRequest): @dataclass(frozen=True) class PartitionMetadata: - config_files: frozenset[SemgrepRuleSourceField] + config_files: frozenset[Path] ignore_files: Snapshot @property def description(self) -> str: - return ", ".join(sorted(field.file_path for field in self.config_files)) + return ", ".join(sorted(str(path) for path in self.config_files)) _IGNORE_FILE_NAME = ".semgrepignore" +_RULES_DIR_NAME = ".semgrep" +_RULES_FILES_GLOBS = ( + ".semgrep.yml", + ".semgrep.yaml", + f"{_RULES_DIR_NAME}/*.yml", + f"{_RULES_DIR_NAME}/*.yaml", +) + @dataclass class SemgrepIgnoreFiles: @@ -55,9 +69,9 @@ class SemgrepIgnoreFiles: @dataclass class AllSemgrepConfigs: - targets: dict[str, list[Target]] + configs_by_dir: dict[Path, list[Path]] - def ancestor_targets(self, address: Address) -> Iterable[Target]: + def ancestor_configs(self, address: Address) -> Iterable[Path]: # TODO: introspect the semgrep rules and determine which (if any) apply to the files, e.g. a # Python file shouldn't depend on a .semgrep.yml that doesn't have any 'python' or 'generic' # rules, and similarly if there's path inclusions/exclusions. @@ -68,29 +82,43 @@ def ancestor_targets(self, address: Address) -> Iterable[Target]: spec = Path(address.spec_path) for ancestor in itertools.chain([spec], spec.parents): - spec_path = str(ancestor) - yield from self.targets.get("" if spec_path == "." else spec_path, []) + yield from self.configs_by_dir.get(ancestor, []) + + +def _group_by_config(all_paths: Paths) -> AllSemgrepConfigs: + configs_by_dir = defaultdict(list) + for path_ in all_paths.files: + path = Path(path_) + # A rule like foo/bar/.semgrep/baz.yaml should behave like it's in in foo/bar, not + # foo/bar/.semgrep + parent = path.parent + config_directory = parent.parent if parent.name == _RULES_DIR_NAME else parent + + configs_by_dir[config_directory].append(path) + + return AllSemgrepConfigs(configs_by_dir) @rule -async def find_all_semgrep_configs(all_targets: AllTargets) -> AllSemgrepConfigs: - targets = defaultdict(list) - for tgt in all_targets: - if tgt.has_field(SemgrepRuleSourceField): - targets[tgt.address.spec_path].append(tgt) - return AllSemgrepConfigs(targets) +async def find_all_semgrep_configs() -> AllSemgrepConfigs: + all_paths = await Get(Paths, PathGlobs([f"**/{file_glob}" for file_glob in _RULES_FILES_GLOBS])) + return _group_by_config(all_paths) @dataclass(frozen=True) -class InferSemgrepDependenciesRequest: +class RelevantSemgrepConfigsRequest: field_set: SemgrepFieldSet +class RelevantSemgrepConfigs(frozenset[Path]): + pass + + @rule -async def infer_semgrep_dependencies( - request: InferSemgrepDependenciesRequest, all_semgrep: AllSemgrepConfigs -) -> Targets: - return Targets(tuple(all_semgrep.ancestor_targets(request.field_set.address))) +async def infer_relevant_semgrep_configs( + request: RelevantSemgrepConfigsRequest, all_semgrep: AllSemgrepConfigs +) -> RelevantSemgrepConfigs: + return RelevantSemgrepConfigs(all_semgrep.ancestor_configs(request.field_set.address)) @rule @@ -108,19 +136,16 @@ async def partition( if semgrep.skip: return Partitions() - dependencies = await MultiGet( - Get(Targets, InferSemgrepDependenciesRequest(field_set)) for field_set in request.field_sets + all_configs = await MultiGet( + Get(RelevantSemgrepConfigs, RelevantSemgrepConfigsRequest(field_set)) + for field_set in request.field_sets ) # partition by the sets of configs that apply to each input by_config = defaultdict(list) - for field_set, deps in zip(request.field_sets, dependencies): - semgrep_configs = frozenset( - d[SemgrepRuleSourceField] for d in deps if d.has_field(SemgrepRuleSourceField) - ) - - if semgrep_configs: - by_config[semgrep_configs].append(field_set) + for field_set, configs in zip(request.field_sets, all_configs): + if configs: + by_config[configs].append(field_set) return Partitions( Partition(tuple(field_sets), PartitionMetadata(configs, ignore_files.snapshot)) @@ -143,7 +168,7 @@ async def lint( global_options: GlobalOptions, ) -> LintResult: config_files, semgrep_pex, input_files, settings = await MultiGet( - Get(SourceFiles, SourceFilesRequest(request.partition_metadata.config_files)), + Get(Snapshot, PathGlobs(str(s) for s in request.partition_metadata.config_files)), Get(VenvPex, PexRequest, semgrep.to_pex_request()), Get(SourceFiles, SourceFilesRequest(field_set.source for field_set in request.elements)), Get(Digest, CreateDigest([_DEFAULT_SETTINGS])), @@ -154,7 +179,7 @@ async def lint( MergeDigests( ( input_files.snapshot.digest, - config_files.snapshot.digest, + config_files.digest, settings, request.partition_metadata.ignore_files.digest, ) @@ -172,7 +197,7 @@ async def lint( semgrep_pex, argv=( "scan", - *(f"--config={f}" for f in config_files.snapshot.files), + *(f"--config={f}" for f in config_files.files), "--jobs={pants_concurrency}", "--error", *semgrep.args, diff --git a/src/python/pants/backend/tools/semgrep/rules_integration_test.py b/src/python/pants/backend/tools/semgrep/rules_integration_test.py index fad1ad91598..3cdc580822c 100644 --- a/src/python/pants/backend/tools/semgrep/rules_integration_test.py +++ b/src/python/pants/backend/tools/semgrep/rules_integration_test.py @@ -294,7 +294,7 @@ def semgrep(dir: str) -> dict[str, str]: sorted_partitions = sorted( ( sorted(field_set.address.spec for field_set in partition.elements), - sorted(f.address.filename for f in partition.metadata.config_files), + sorted(str(f) for f in partition.metadata.config_files), ) for partition in partitions ) From 5db7e97fd05ae8ff8b3991796c4d69b5f1c8d528 Mon Sep 17 00:00:00 2001 From: Huon Wilson Date: Mon, 28 Aug 2023 12:53:06 +1000 Subject: [PATCH 44/49] Remove now-dead code --- .../experimental/tools/semgrep/register.py | 10 -- .../tools/semgrep/rules_integration_test.py | 16 +-- .../pants/backend/tools/semgrep/tailor.py | 75 ----------- .../backend/tools/semgrep/tailor_test.py | 116 ------------------ .../backend/tools/semgrep/target_types.py | 51 -------- 5 files changed, 3 insertions(+), 265 deletions(-) delete mode 100644 src/python/pants/backend/tools/semgrep/tailor.py delete mode 100644 src/python/pants/backend/tools/semgrep/tailor_test.py delete mode 100644 src/python/pants/backend/tools/semgrep/target_types.py diff --git a/src/python/pants/backend/experimental/tools/semgrep/register.py b/src/python/pants/backend/experimental/tools/semgrep/register.py index 87a75b0a022..589cf36713b 100644 --- a/src/python/pants/backend/experimental/tools/semgrep/register.py +++ b/src/python/pants/backend/experimental/tools/semgrep/register.py @@ -14,23 +14,13 @@ from pants.backend.python.goals import lockfile as python_lockfile from pants.backend.tools.semgrep import rules as semgrep_rules from pants.backend.tools.semgrep import subsystem as subsystem -from pants.backend.tools.semgrep import tailor -from pants.backend.tools.semgrep.target_types import ( - SemgrepRuleSourcesGeneratorTarget, - SemgrepRuleSourceTarget, -) from pants.engine.rules import Rule from pants.engine.unions import UnionRule -def target_types(): - return [SemgrepRuleSourceTarget, SemgrepRuleSourcesGeneratorTarget] - - def rules() -> Iterable[Rule | UnionRule]: return ( *semgrep_rules.rules(), *subsystem.rules(), *python_lockfile.rules(), - *tailor.rules(), ) diff --git a/src/python/pants/backend/tools/semgrep/rules_integration_test.py b/src/python/pants/backend/tools/semgrep/rules_integration_test.py index 3cdc580822c..0b66e2c8674 100644 --- a/src/python/pants/backend/tools/semgrep/rules_integration_test.py +++ b/src/python/pants/backend/tools/semgrep/rules_integration_test.py @@ -22,7 +22,6 @@ from .rules import rules as semgrep_rules from .subsystem import SemgrepFieldSet, SemgrepSubsystem from .subsystem import rules as semgrep_subsystem_rules -from .target_types import SemgrepRuleSourcesGeneratorTarget, SemgrepRuleSourceTarget DIR = "src" @@ -67,7 +66,6 @@ SINGLE_FILE_BUILD = dedent( """\ file(name="f", source="file.txt") - semgrep_rule_sources(name="s") """ ) @@ -94,7 +92,7 @@ def rule_runner() -> RuleRunner: QueryRule(LintResult, (SemgrepLintRequest.Batch,)), QueryRule(SourceFiles, (SourceFilesRequest,)), ], - target_types=[SemgrepRuleSourceTarget, SemgrepRuleSourcesGeneratorTarget, FileTarget], + target_types=[FileTarget], ) @@ -181,7 +179,6 @@ def test_multiple_targets(rule_runner: RuleRunner) -> None: """\ file(name="g", source="good.txt") file(name="b", source="bad.txt") - semgrep_rule_sources(name="s") """ ), } @@ -208,7 +205,6 @@ def test_multiple_targets(rule_runner: RuleRunner) -> None: { **BAD_FILE_LAYOUT, ".semgrep.yml": RULES2, - "BUILD": """semgrep_rule_sources(name="s")""", }, id="via nesting", ), @@ -218,7 +214,6 @@ def test_multiple_targets(rule_runner: RuleRunner) -> None: f"{DIR}/BUILD": """file(name="f", source="bad.txt")""", ".semgrep/one.yml": RULES, ".semgrep/two.yml": RULES2, - "BUILD": """semgrep_rule_sources(name="s")""", }, id="via .semgrep directory", ), @@ -264,7 +259,7 @@ def file___(dir: str) -> dict[str, str]: } def semgrep(dir: str) -> dict[str, str]: - return {f"{dir}/.semgrep.yml": RULES, f"{dir}/BUILD": """semgrep_rule_sources(name="s")"""} + return {f"{dir}/.semgrep.yml": RULES} rule_runner.write_files( { @@ -379,12 +374,7 @@ def test_semgrep_pex_contents_is_ignored(rule_runner: RuleRunner) -> None: # which will, naively, find the __main__.py in the PEX. "__main__.py": "", ".semgrep.yml": RULES, - "BUILD": dedent( - """\ - file(name="f", source="__main__.py") - semgrep_rule_sources(name="s") - """ - ), + "BUILD": """file(name="f", source="__main__.py")""", } ) diff --git a/src/python/pants/backend/tools/semgrep/tailor.py b/src/python/pants/backend/tools/semgrep/tailor.py deleted file mode 100644 index 89ca412bc2d..00000000000 --- a/src/python/pants/backend/tools/semgrep/tailor.py +++ /dev/null @@ -1,75 +0,0 @@ -# Copyright 2023 Pants project contributors (see CONTRIBUTORS.md). -# Licensed under the Apache License, Version 2.0 (see LICENSE). - -from __future__ import annotations - -import os -from collections import defaultdict -from dataclasses import dataclass -from typing import Iterable - -from pants.backend.tools.semgrep.subsystem import SemgrepSubsystem -from pants.backend.tools.semgrep.target_types import SemgrepRuleSourcesGeneratorTarget -from pants.core.goals.tailor import ( - AllOwnedSources, - PutativeTarget, - PutativeTargets, - PutativeTargetsRequest, -) -from pants.engine.fs import PathGlobs, Paths -from pants.engine.internals.selectors import Get -from pants.engine.rules import collect_rules, rule -from pants.engine.unions import UnionRule -from pants.util.logging import LogLevel - - -@dataclass(frozen=True) -class PutativeSemgrepTargetsRequest(PutativeTargetsRequest): - pass - - -def _group_by_semgrep_dir(paths: Iterable[str]) -> dict[str, set[str]]: - ret = defaultdict(set) - for path in paths: - dirname, filename = os.path.split(path) - dir2name, dir_basename = os.path.split(dirname) - if dir_basename == ".semgrep": - # rules from foo/bar/.semgrep/ should behave like they're in foo/bar, not - # foo/bar/,semgrep - ret[dir2name].add(os.path.join(dir_basename, filename)) - else: - ret[dirname].add(filename) - - return ret - - -@rule(level=LogLevel.DEBUG, desc="Determine candidate Semgrep targets to create") -async def find_putative_targets( - req: PutativeSemgrepTargetsRequest, - all_owned_sources: AllOwnedSources, - semgrep: SemgrepSubsystem, -) -> PutativeTargets: - pts = [] - - if semgrep.tailor_rule_targets: - all_files_globs = req.path_globs( - ".semgrep.yml", ".semgrep.yaml", ".semgrep/*.yml", ".semgrep/*.yaml" - ) - all_files = await Get(Paths, PathGlobs, all_files_globs) - unowned = set(all_files.files) - set(all_owned_sources) - - for dirname, filenames in _group_by_semgrep_dir(unowned).items(): - pt = PutativeTarget.for_target_type( - SemgrepRuleSourcesGeneratorTarget, - path=dirname, - name="semgrep", - triggering_sources=sorted(filenames), - ) - - pts.append(pt) - - return PutativeTargets(pts) - - -def rules(): - return [*collect_rules(), UnionRule(PutativeTargetsRequest, PutativeSemgrepTargetsRequest)] diff --git a/src/python/pants/backend/tools/semgrep/tailor_test.py b/src/python/pants/backend/tools/semgrep/tailor_test.py deleted file mode 100644 index 2f790cea458..00000000000 --- a/src/python/pants/backend/tools/semgrep/tailor_test.py +++ /dev/null @@ -1,116 +0,0 @@ -# Copyright 2023 Pants project contributors (see CONTRIBUTORS.md). -# Licensed under the Apache License, Version 2.0 (see LICENSE). - -from __future__ import annotations - -import pytest - -from pants.backend.tools.semgrep import tailor -from pants.backend.tools.semgrep.tailor import PutativeSemgrepTargetsRequest -from pants.backend.tools.semgrep.target_types import SemgrepRuleSourcesGeneratorTarget -from pants.core.goals.tailor import AllOwnedSources, PutativeTarget, PutativeTargets -from pants.engine.rules import QueryRule -from pants.testutil.rule_runner import RuleRunner - - -@pytest.mark.parametrize( - ("paths", "expected"), - [ - ((), {}), - (("foo/bar/.semgrep.yml",), {"foo/bar": {".semgrep.yml"}}), - (("foo/bar/.semgrep/baz.yml",), {"foo/bar": {".semgrep/baz.yml"}}), - ( - ( - "foo/bar/.semgrep.yml", - "foo/bar/.semgrep/baz.yml", - ), - {"foo/bar": {".semgrep.yml", ".semgrep/baz.yml"}}, - ), - ( - ( - "foo/.semgrep/baz.yml", - "foo/bar/.semgrep.yml", - "foo/bar/qux/.semgrep.yml", - ), - { - "foo": {".semgrep/baz.yml"}, - "foo/bar": {".semgrep.yml"}, - "foo/bar/qux": {".semgrep.yml"}, - }, - ), - # at the top level should be okay too - ((".semgrep.yml", ".semgrep/foo.yml"), {"": {".semgrep.yml", ".semgrep/foo.yml"}}), - ], -) -def test_group_by_semgrep_dir(paths: tuple[str, ...], expected: dict[str, set[str]]): - assert tailor._group_by_semgrep_dir(paths) == expected - - -@pytest.fixture -def rule_runner() -> RuleRunner: - return RuleRunner( - rules=[ - *tailor.rules(), - QueryRule(PutativeTargets, (PutativeSemgrepTargetsRequest, AllOwnedSources)), - ], - target_types=[SemgrepRuleSourcesGeneratorTarget], - ) - - -def test_find_putative_targets(rule_runner: RuleRunner) -> None: - rule_runner.write_files( - { - "src/owned/.semgrep.yml": "rules: []", - "src/owned/sub/.semgrep/foo.yaml": "rules: []", - "src/unowned/.semgrep.yaml": "rules: []", - "src/unowned/sub/.semgrep/foo.yml": "rules: []", - "src/unowned/sub/.semgrep/bar.yaml": "rules: []", - "src/unowned/sub/.semgrep.yml": "rules: []", - # other YAML files aren't always Semgrep - "src/unowned/not_obviously_semgrep.yaml": "rules: []", - } - ) - - putative_targets = rule_runner.request( - PutativeTargets, - [ - PutativeSemgrepTargetsRequest(("src/owned", "src/unowned", "src/unowned/sub")), - AllOwnedSources(["src/owned/.semgrep.yml", "src/owned/sub/.semgrep/foo.yaml"]), - ], - ) - - assert putative_targets == PutativeTargets( - [ - PutativeTarget.for_target_type( - SemgrepRuleSourcesGeneratorTarget, - "src/unowned", - "semgrep", - [".semgrep.yaml"], - ), - PutativeTarget.for_target_type( - SemgrepRuleSourcesGeneratorTarget, - "src/unowned/sub", - "semgrep", - [".semgrep.yml", ".semgrep/bar.yaml", ".semgrep/foo.yml"], - ), - ], - ) - - -def test_find_putative_targets_when_disabled(rule_runner: RuleRunner) -> None: - rule_runner.write_files( - { - "src/unowned/.semgrep.yml": "{}", - } - ) - - rule_runner.set_options(["--no-semgrep-tailor-rule-targets"]) - - putative_targets = rule_runner.request( - PutativeTargets, - [ - PutativeSemgrepTargetsRequest(("src/unowned",)), - AllOwnedSources(), - ], - ) - assert putative_targets == PutativeTargets() diff --git a/src/python/pants/backend/tools/semgrep/target_types.py b/src/python/pants/backend/tools/semgrep/target_types.py deleted file mode 100644 index e7fd539c5ea..00000000000 --- a/src/python/pants/backend/tools/semgrep/target_types.py +++ /dev/null @@ -1,51 +0,0 @@ -# Copyright 2023 Pants project contributors (see CONTRIBUTORS.md). -# Licensed under the Apache License, Version 2.0 (see LICENSE). - -from __future__ import annotations - -from typing import ClassVar - -from pants.engine.target import ( - COMMON_TARGET_FIELDS, - MultipleSourcesField, - SingleSourceField, - Target, - TargetFilesGenerator, - generate_multiple_sources_field_help_message, -) - - -class SemgrepRuleSourceField(SingleSourceField): - expected_file_extensions: ClassVar[tuple[str, ...]] = (".yml", ".yaml") - - -class SemgrepRuleGeneratingSourcesField(MultipleSourcesField): - expected_file_extensions: ClassVar[tuple[str, ...]] = (".yml", ".yaml") - default = ( - ".semgrep.yml", - ".semgrep.yaml", - ".semgrep/*.yml", - ".semgrep/*.yaml", - ) - help = generate_multiple_sources_field_help_message( - "Example: `sources=['.semgrep.yml', '.semgrep.yaml', '.semgrep/*.yml', '.semgrep/*.yaml',]`" - ) - - -class SemgrepRuleSourceTarget(Target): - alias = "semgrep_rule_source" - core_fields = (*COMMON_TARGET_FIELDS, SemgrepRuleSourceField) - - help = "A single source file containing Semgrep rules" - - -class SemgrepRuleSourcesGeneratorTarget(TargetFilesGenerator): - alias = "semgrep_rule_sources" - core_fields = ( - *COMMON_TARGET_FIELDS, - SemgrepRuleGeneratingSourcesField, - ) - generated_target_cls = SemgrepRuleSourceTarget - copied_fields = COMMON_TARGET_FIELDS - moved_fields = () - help = "Generate a `semgrep_rule_source` target for each file in the `sources` field." From 16f22c43f89fcdd451c49e4ceeaf67b68d65fa67 Mon Sep 17 00:00:00 2001 From: Huon Wilson Date: Mon, 28 Aug 2023 13:22:13 +1000 Subject: [PATCH 45/49] Test some pure code --- .../pants/backend/tools/semgrep/rules.py | 10 +- .../pants/backend/tools/semgrep/rules_test.py | 131 ++++++++++++++++++ 2 files changed, 136 insertions(+), 5 deletions(-) create mode 100644 src/python/pants/backend/tools/semgrep/rules_test.py diff --git a/src/python/pants/backend/tools/semgrep/rules.py b/src/python/pants/backend/tools/semgrep/rules.py index 7fa86e08bba..7af017e75f7 100644 --- a/src/python/pants/backend/tools/semgrep/rules.py +++ b/src/python/pants/backend/tools/semgrep/rules.py @@ -69,7 +69,7 @@ class SemgrepIgnoreFiles: @dataclass class AllSemgrepConfigs: - configs_by_dir: dict[Path, list[Path]] + configs_by_dir: dict[Path, set[Path]] def ancestor_configs(self, address: Address) -> Iterable[Path]: # TODO: introspect the semgrep rules and determine which (if any) apply to the files, e.g. a @@ -85,8 +85,8 @@ def ancestor_configs(self, address: Address) -> Iterable[Path]: yield from self.configs_by_dir.get(ancestor, []) -def _group_by_config(all_paths: Paths) -> AllSemgrepConfigs: - configs_by_dir = defaultdict(list) +def _group_by_semgrep_dir(all_paths: Paths) -> AllSemgrepConfigs: + configs_by_dir = defaultdict(set) for path_ in all_paths.files: path = Path(path_) # A rule like foo/bar/.semgrep/baz.yaml should behave like it's in in foo/bar, not @@ -94,7 +94,7 @@ def _group_by_config(all_paths: Paths) -> AllSemgrepConfigs: parent = path.parent config_directory = parent.parent if parent.name == _RULES_DIR_NAME else parent - configs_by_dir[config_directory].append(path) + configs_by_dir[config_directory].add(path) return AllSemgrepConfigs(configs_by_dir) @@ -102,7 +102,7 @@ def _group_by_config(all_paths: Paths) -> AllSemgrepConfigs: @rule async def find_all_semgrep_configs() -> AllSemgrepConfigs: all_paths = await Get(Paths, PathGlobs([f"**/{file_glob}" for file_glob in _RULES_FILES_GLOBS])) - return _group_by_config(all_paths) + return _group_by_semgrep_dir(all_paths) @dataclass(frozen=True) diff --git a/src/python/pants/backend/tools/semgrep/rules_test.py b/src/python/pants/backend/tools/semgrep/rules_test.py new file mode 100644 index 00000000000..951a3a28725 --- /dev/null +++ b/src/python/pants/backend/tools/semgrep/rules_test.py @@ -0,0 +1,131 @@ +# Copyright 2023 Pants project contributors (see CONTRIBUTORS.md). +# Licensed under the Apache License, Version 2.0 (see LICENSE). + +from __future__ import annotations + +from pathlib import Path + +import pytest + +from pants.backend.tools.semgrep import rules +from pants.backend.tools.semgrep.rules import AllSemgrepConfigs +from pants.engine.addresses import Address +from pants.engine.fs import Paths + + +def configs(strs: dict[str, set[str]]) -> AllSemgrepConfigs: + return AllSemgrepConfigs({Path(d): {Path(f) for f in files} for d, files in strs.items()}) + + +@pytest.mark.parametrize( + ("paths", "expected"), + [ + pytest.param((), configs({}), id="nothing"), + pytest.param( + ("foo/bar/.semgrep.yml",), + configs({"foo/bar": {"foo/bar/.semgrep.yml"}}), + id="semgrep_file", + ), + pytest.param( + ("foo/bar/.semgrep/baz.yml", "foo/bar/.semgrep/qux.yml"), + configs({"foo/bar": {"foo/bar/.semgrep/baz.yml", "foo/bar/.semgrep/qux.yml"}}), + id="semgrep_dir", + ), + pytest.param( + ( + "foo/bar/.semgrep.yml", + "foo/bar/.semgrep/baz.yml", + ), + configs({"foo/bar": {"foo/bar/.semgrep.yml", "foo/bar/.semgrep/baz.yml"}}), + id="both_file_and_dir", + ), + pytest.param( + ( + "foo/.semgrep/baz.yml", + "foo/bar/.semgrep.yml", + "foo/bar/qux/.semgrep.yml", + ), + configs( + { + "foo": {"foo/.semgrep/baz.yml"}, + "foo/bar": {"foo/bar/.semgrep.yml"}, + "foo/bar/qux": {"foo/bar/qux/.semgrep.yml"}, + } + ), + id="everything", + ), + # at the top level should be okay too + pytest.param( + (".semgrep.yml", ".semgrep/foo.yml"), + configs({"": {".semgrep.yml", ".semgrep/foo.yml"}}), + id="top_level", + ), + ], +) +def test_group_by_group_by_semgrep_dir(paths: tuple[str, ...], expected: AllSemgrepConfigs): + input = Paths(files=paths, dirs=()) + result = rules._group_by_semgrep_dir(input) + assert result == expected + + +@pytest.mark.parametrize( + ("config", "address", "expected"), + [ + pytest.param(configs({}), Address(""), set(), id="nothing_root"), + pytest.param(configs({}), Address("foo/bar"), set(), id="nothing_nested"), + pytest.param( + configs({"": {".semgrep.yml"}}), + Address(""), + {".semgrep.yml"}, + id="config_root_address_root", + ), + pytest.param( + configs({"": {".semgrep.yml"}}), + Address("foo/bar"), + {".semgrep.yml"}, + id="config_root_address_nested", + ), + pytest.param( + configs({"": {".semgrep.yml", ".semgrep/foo.yml"}}), + Address(""), + {".semgrep.yml", ".semgrep/foo.yml"}, + id="config_root_multiple_address_root", + ), + pytest.param( + configs({"foo/bar": {"foo/bar/.semgrep.yml"}}), + Address(""), + set(), + id="config_nested_address_root", + ), + pytest.param( + configs({"foo/bar": {"foo/bar/.semgrep.yml"}}), + Address("foo/bar"), + {"foo/bar/.semgrep.yml"}, + id="config_nested_address_nested_matching", + ), + pytest.param( + configs({"foo/bar": {"foo/bar/.semgrep.yml"}}), + Address("foo/baz"), + {}, + id="config_nested_address_nested_different", + ), + pytest.param( + configs({"": {".semgrep.yml"}, "foo/bar": {"foo/bar/.semgrep.yml"}}), + Address(""), + {".semgrep.yml"}, + id="config_root_and_nested_address_root", + ), + pytest.param( + configs({"": {".semgrep.yml"}, "foo/bar": {"foo/bar/.semgrep.yml"}}), + Address("foo/bar"), + {".semgrep.yml", "foo/bar/.semgrep.yml"}, + id="config_root_and_nested_address_nested", + ), + ], +) +def test_all_semgrep_configs_ancestor_configs( + config: AllSemgrepConfigs, address: Address, expected: set[str] +): + result = config.ancestor_configs(address) + + assert set(result) == {Path(p) for p in expected} From ed0687abddbb599cdca344b4bc88fd81b6562e83 Mon Sep 17 00:00:00 2001 From: Huon Wilson Date: Mon, 28 Aug 2023 13:22:26 +1000 Subject: [PATCH 46/49] Tweak integration tests --- .../tools/semgrep/rules_integration_test.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/python/pants/backend/tools/semgrep/rules_integration_test.py b/src/python/pants/backend/tools/semgrep/rules_integration_test.py index 0b66e2c8674..dd3e1e3ff9e 100644 --- a/src/python/pants/backend/tools/semgrep/rules_integration_test.py +++ b/src/python/pants/backend/tools/semgrep/rules_integration_test.py @@ -24,6 +24,7 @@ from .subsystem import rules as semgrep_subsystem_rules DIR = "src" +FILE = "file.txt" # https://semgrep.dev/docs/cli-reference/#exit-codes SEMGREP_ERROR_FAILURE_RETURN_CODE = 1 @@ -64,18 +65,18 @@ ) SINGLE_FILE_BUILD = dedent( - """\ - file(name="f", source="file.txt") + f"""\ + file(name="f", source="{FILE}") """ ) BAD_FILE_LAYOUT = { - f"{DIR}/file.txt": BAD_FILE, + f"{DIR}/{FILE}": BAD_FILE, f"{DIR}/.semgrep.yml": RULES, f"{DIR}/BUILD": SINGLE_FILE_BUILD, } GOOD_FILE_LAYOUT = { - f"{DIR}/file.txt": GOOD_FILE, + f"{DIR}/{FILE}": GOOD_FILE, f"{DIR}/.semgrep.yml": RULES, f"{DIR}/BUILD": SINGLE_FILE_BUILD, } @@ -235,7 +236,7 @@ def test_multiple_configs(rule_runner: RuleRunner, files: dict[str, str]) -> Non def test_semgrepignore(rule_runner: RuleRunner) -> None: - rule_runner.write_files({**BAD_FILE_LAYOUT, ".semgrepignore": "file.txt"}) + rule_runner.write_files({**BAD_FILE_LAYOUT, ".semgrepignore": FILE}) tgt = rule_runner.get_target(Address(DIR, target_name="f")) results = run_semgrep(rule_runner, [tgt]) @@ -254,8 +255,8 @@ def test_partition_by_config(rule_runner: RuleRunner) -> None: def file___(dir: str) -> dict[str, str]: file_dirs.append(dir) return { - f"{dir}/file.txt": GOOD_FILE, - f"{dir}/BUILD": """file(name="f", source="file.txt")""", + f"{dir}/{FILE}": GOOD_FILE, + f"{dir}/BUILD": f"""file(name="f", source="{FILE}")""", } def semgrep(dir: str) -> dict[str, str]: @@ -318,7 +319,9 @@ def test_skip(rule_runner: RuleRunner) -> None: @pytest.mark.xfail( reason=""" TODO: --semgrep-force does rerun the underlying process, but the LintResult's contents are the same (same stdout etc.), these are deduped, and thus we cannot detect the - rerun""" + rerun""", + # no point spending time on this + run=False, ) def test_force(rule_runner: RuleRunner) -> None: rule_runner.write_files(GOOD_FILE_LAYOUT) From 4593fb245bcc56c55e9b5e7c7cdf8bca32c91b1a Mon Sep 17 00:00:00 2001 From: Huon Wilson Date: Mon, 28 Aug 2023 13:24:14 +1000 Subject: [PATCH 47/49] Restore accidental deletion --- build-support/bin/generate_builtin_lockfiles.py | 1 + 1 file changed, 1 insertion(+) diff --git a/build-support/bin/generate_builtin_lockfiles.py b/build-support/bin/generate_builtin_lockfiles.py index 0e09d12bdb0..ddc53da1d91 100644 --- a/build-support/bin/generate_builtin_lockfiles.py +++ b/build-support/bin/generate_builtin_lockfiles.py @@ -124,6 +124,7 @@ class JvmTool(Tool[JvmToolBase]): PythonTool(PythonProtobufMypyPlugin, "pants.backend.codegen.protobuf.python"), PythonTool(Pytype, "pants.backend.python.typecheck.pytype", "CPython>=3.7,<3.11"), PythonTool(PyOxidizer, "pants.backend.experimental.python.packaging.pyoxidizer"), + PythonTool(Ruff, "pants.backend.experimental.python.lint.ruff"), PythonTool(SemgrepSubsystem, "pants.backend.experimental.tools.semgrep"), PythonTool(Setuptools, "pants.backend.python"), PythonTool(SetuptoolsSCM, "pants.backend.python"), From db6bc2e4c95b5a65ac906d40a578b7572ffae618 Mon Sep 17 00:00:00 2001 From: Huon Wilson Date: Mon, 28 Aug 2023 13:27:07 +1000 Subject: [PATCH 48/49] Update semgrep lockfile --- .../pants/backend/tools/semgrep/semgrep.lock | 423 +++++++++--------- 1 file changed, 216 insertions(+), 207 deletions(-) diff --git a/src/python/pants/backend/tools/semgrep/semgrep.lock b/src/python/pants/backend/tools/semgrep/semgrep.lock index 99629988501..b16e3dd5552 100644 --- a/src/python/pants/backend/tools/semgrep/semgrep.lock +++ b/src/python/pants/backend/tools/semgrep/semgrep.lock @@ -107,364 +107,364 @@ "artifacts": [ { "algorithm": "sha256", - "hash": "4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18", - "url": "https://files.pythonhosted.org/packages/71/4c/3db2b8021bd6f2f0ceb0e088d6b2d49147671f25832fb17970e9b583d742/certifi-2022.12.7-py3-none-any.whl" + "hash": "92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9", + "url": "https://files.pythonhosted.org/packages/4c/dd/2234eab22353ffc7d94e8d13177aaa050113286e93e7b40eae01fbf7c3d9/certifi-2023.7.22-py3-none-any.whl" }, { "algorithm": "sha256", - "hash": "35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3", - "url": "https://files.pythonhosted.org/packages/37/f7/2b1b0ec44fdc30a3d31dfebe52226be9ddc40cd6c0f34ffc8923ba423b69/certifi-2022.12.7.tar.gz" + "hash": "539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082", + "url": "https://files.pythonhosted.org/packages/98/98/c2ff18671db109c9f10ed27f5ef610ae05b73bd876664139cf95bd1429aa/certifi-2023.7.22.tar.gz" } ], "project_name": "certifi", "requires_dists": [], "requires_python": ">=3.6", - "version": "2022.12.7" + "version": "2023.7.22" }, { "artifacts": [ { "algorithm": "sha256", - "hash": "3d9098b479e78c85080c98e1e35ff40b4a31d8953102bb0fd7d1b6f8a2111a3d", - "url": "https://files.pythonhosted.org/packages/ef/81/14b3b8f01ddaddad6cdec97f2f599aa2fa466bd5ee9af99b08b7713ccd29/charset_normalizer-3.1.0-py3-none-any.whl" + "hash": "8e098148dd37b4ce3baca71fb394c81dc5d9c7728c95df695d2dca218edf40e6", + "url": "https://files.pythonhosted.org/packages/bf/a0/188f223c7d8b924fb9b554b9d27e0e7506fd5bf9cfb6dbacb2dfd5832b53/charset_normalizer-3.2.0-py3-none-any.whl" }, { "algorithm": "sha256", - "hash": "3a5fc78f9e3f501a1614a98f7c54d3969f3ad9bba8ba3d9b438c3bc5d047dd28", - "url": "https://files.pythonhosted.org/packages/00/47/f14533da238134f5067fb1d951eb03d5c4be895d6afb11c7ebd07d111acb/charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl" + "hash": "09393e1b2a9461950b1c9a45d5fd251dc7c6f228acab64da1c9c0165d9c7765c", + "url": "https://files.pythonhosted.org/packages/08/f7/3f36bb1d0d74846155c7e3bf1477004c41243bb510f9082e785809787735/charset_normalizer-3.2.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl" }, { "algorithm": "sha256", - "hash": "3573d376454d956553c356df45bb824262c397c6e26ce43e8203c4c540ee0acb", - "url": "https://files.pythonhosted.org/packages/01/c7/0407de35b70525dba2a58a2724a525cf882ee76c3d2171d834463c5d2881/charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl" + "hash": "855eafa5d5a2034b4621c74925d89c5efef61418570e5ef9b37717d9c796419c", + "url": "https://files.pythonhosted.org/packages/09/79/1b7af063e7c57a51aab7f2aaccd79bb8a694dfae668e8aa79b0b045b17bc/charset_normalizer-3.2.0-cp39-cp39-macosx_10_9_universal2.whl" }, { "algorithm": "sha256", - "hash": "6734e606355834f13445b6adc38b53c0fd45f1a56a9ba06c2058f86893ae8017", - "url": "https://files.pythonhosted.org/packages/0a/67/8d3d162ec6641911879651cdef670c3c6136782b711d7f8e82e2fffe06e0/charset_normalizer-3.1.0-cp311-cp311-macosx_10_9_x86_64.whl" + "hash": "e6a5bf2cba5ae1bb80b154ed68a3cfa2fa00fde979a7f50d6598d3e17d9ac20c", + "url": "https://files.pythonhosted.org/packages/0d/dd/e598cc4e4052aa0779d4c6d5e9840d21ed238834944ccfbc6b33f792c426/charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_s390x.whl" }, { "algorithm": "sha256", - "hash": "3dc5b6a8ecfdc5748a7e429782598e4f17ef378e3e272eeb1340ea57c9109f41", - "url": "https://files.pythonhosted.org/packages/12/12/c5c39f5a149cd6788d2e40cea5618bae37380e2754fcdf53dc9e01bdd33a/charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl" + "hash": "46fb8c61d794b78ec7134a715a3e564aafc8f6b5e338417cb19fe9f57a5a9bf2", + "url": "https://files.pythonhosted.org/packages/0f/16/8d50877a7215d31f024245a0acbda9e484dd70a21794f3109a6d8eaeba99/charset_normalizer-3.2.0-cp311-cp311-macosx_10_9_x86_64.whl" }, { "algorithm": "sha256", - "hash": "38e812a197bf8e71a59fe55b757a84c1f946d0ac114acafaafaf21667a7e169e", - "url": "https://files.pythonhosted.org/packages/12/68/4812f9b05ac0a2b7619ac3dd7d7e3fc52c12006b84617021c615fc2fcf42/charset_normalizer-3.1.0-cp39-cp39-macosx_10_9_universal2.whl" + "hash": "f87f746ee241d30d6ed93969de31e5ffd09a2961a051e60ae6bddde9ec3583aa", + "url": "https://files.pythonhosted.org/packages/13/de/10c14aa51375b90ed62232935e6c8997756178e6972c7695cdf0500a60ad/charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl" }, { "algorithm": "sha256", - "hash": "f645caaf0008bacf349875a974220f1f1da349c5dbe7c4ec93048cdc785a3326", - "url": "https://files.pythonhosted.org/packages/13/b7/21729a6d512246aa0bb872b90aea0d9fcd1b293762cdb1d1d33c01140074/charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_aarch64.whl" + "hash": "70c610f6cbe4b9fce272c407dd9d07e33e6bf7b4aa1b7ffb6f6ded8e634e3592", + "url": "https://files.pythonhosted.org/packages/16/36/72dcb89fbd0ff89c556ed4a2cc79fc1b262dcc95e9082d8a5911744dadc9/charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_x86_64.whl" }, { "algorithm": "sha256", - "hash": "b82fab78e0b1329e183a65260581de4375f619167478dddab510c6c6fb04d9b6", - "url": "https://files.pythonhosted.org/packages/16/58/19fd2f62e6ff44ba0db0cd44b584790555e2cde09293149f4409d654811b/charset_normalizer-3.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl" + "hash": "c8063cf17b19661471ecbdb3df1c84f24ad2e389e326ccaf89e3fb2484d8dd7e", + "url": "https://files.pythonhosted.org/packages/1b/2c/7376d101efdec15e61e9861890cf107c6ce3cceba89eb87cc416ee0528cd/charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_ppc64le.whl" }, { "algorithm": "sha256", - "hash": "0ca564606d2caafb0abe6d1b5311c2649e8071eb241b2d64e75a0d0065107e62", - "url": "https://files.pythonhosted.org/packages/18/36/7ae10a3dd7f9117b61180671f8d1e4802080cca88ad40aaabd3dad8bab0e/charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl" + "hash": "1920d4ff15ce893210c1f0c0e9d19bfbecb7983c76b33f046c13a8ffbd570252", + "url": "https://files.pythonhosted.org/packages/23/59/8011a01cd8b904d08d86b4a49f407e713d20ee34155300dc698892a29f8b/charset_normalizer-3.2.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl" }, { "algorithm": "sha256", - "hash": "10c93628d7497c81686e8e5e557aafa78f230cd9e77dd0c40032ef90c18f2230", - "url": "https://files.pythonhosted.org/packages/1c/9b/de2adc43345623da8e7c958719528a42b6d87d2601017ce1187d43b8a2d7/charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_i686.whl" + "hash": "3b1613dd5aee995ec6d4c69f00378bbd07614702a315a2cf6c1d21461fe17c23", + "url": "https://files.pythonhosted.org/packages/27/19/49de2049561eca73233ba0ed7a843c184d364ef3b8886969a48d6793c830/charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_s390x.whl" }, { "algorithm": "sha256", - "hash": "d16fd5252f883eb074ca55cb622bc0bee49b979ae4e8639fff6ca3ff44f9f854", - "url": "https://files.pythonhosted.org/packages/1f/be/c6c76cf8fcf6918922223203c83ba8192eff1c6a709e8cfec7f5ca3e7d2d/charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl" + "hash": "f25c229a6ba38a35ae6e25ca1264621cc25d4d38dca2942a7fce0b67a4efe918", + "url": "https://files.pythonhosted.org/packages/28/ec/cda85baa366071c48593774eb59a5031793dd974fa26f4982829e971df6b/charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl" }, { "algorithm": "sha256", - "hash": "11d117e6c63e8f495412d37e7dc2e2fff09c34b2d09dbe2bee3c6229577818be", - "url": "https://files.pythonhosted.org/packages/21/16/1b0d8fdcb81bbf180976af4f867ce0f2244d303ab10d452fde361dec3b5c/charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_i686.whl" + "hash": "3bb3d25a8e6c0aedd251753a79ae98a093c7e7b471faa3aa9a93a81431987ace", + "url": "https://files.pythonhosted.org/packages/2a/53/cf0a48de1bdcf6ff6e1c9a023f5f523dfe303e4024f216feac64b6eb7f67/charset-normalizer-3.2.0.tar.gz" }, { "algorithm": "sha256", - "hash": "b06f0d3bf045158d2fb8837c5785fe9ff9b8c93358be64461a1089f5da983137", - "url": "https://files.pythonhosted.org/packages/23/13/cf5d7bb5bc95f120df64d6c470581189df51d7f011560b2a06a395b7a120/charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_ppc64le.whl" + "hash": "1f30b48dd7fa1474554b0b0f3fdfdd4c13b5c737a3c6284d3cdc424ec0ffff3a", + "url": "https://files.pythonhosted.org/packages/2e/29/dc806e009ddb357371458de3e93cfde78ea6e5c995df008fb6b048769457/charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl" }, { "algorithm": "sha256", - "hash": "1c60b9c202d00052183c9be85e5eaf18a4ada0a47d188a83c8f5c5b23252f649", - "url": "https://files.pythonhosted.org/packages/2c/2f/ec805104098085728b7cb610deede7195c6fa59f51942422f02cc427b6f6/charset_normalizer-3.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl" + "hash": "a38856a971c602f98472050165cea2cdc97709240373041b69030be15047691f", + "url": "https://files.pythonhosted.org/packages/2e/56/faee2b51d73e9675b4766366d925f17c253797e5839c28e1c720ec9dfbfc/charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl" }, { "algorithm": "sha256", - "hash": "80d1543d58bd3d6c271b66abf454d437a438dff01c3e62fdbcd68f2a11310d4b", - "url": "https://files.pythonhosted.org/packages/31/8b/81c3515a69d06b501fcce69506af57a7a19bd9f42cabd1a667b1b40f2c55/charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_ppc64le.whl" + "hash": "72814c01533f51d68702802d74f77ea026b5ec52793c791e2da806a3844a46c3", + "url": "https://files.pythonhosted.org/packages/31/e9/ae16eca3cf24a15ebfb1e36d755c884a91d61ed40de5e612de6555827729/charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_s390x.whl" }, { "algorithm": "sha256", - "hash": "ea9f9c6034ea2d93d9147818f17c2a0860d41b71c38b9ce4d55f21b6f9165a11", - "url": "https://files.pythonhosted.org/packages/33/10/c87ba15f779f8251ae55fa147631339cd91e7af51c3c133d2687c6e41800/charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_i686.whl" + "hash": "b0dac0ff919ba34d4df1b6131f59ce95b08b9065233446be7e459f95554c0dc8", + "url": "https://files.pythonhosted.org/packages/45/60/1b2113fe172ac66ac4d210034e937ebe0be30bcae9a7a4d2ae5ad3c018b3/charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl" }, { "algorithm": "sha256", - "hash": "21fa558996782fc226b529fdd2ed7866c2c6ec91cee82735c98a197fae39f706", - "url": "https://files.pythonhosted.org/packages/33/97/9967fb2d364a9da38557e4af323abcd58cc05bdd8f77e9fd5ae4882772cc/charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl" + "hash": "5e86d77b090dbddbe78867a0275cb4df08ea195e660f1f7f13435a4649e954e5", + "url": "https://files.pythonhosted.org/packages/47/03/2cde6c5fba0115e8726272aabfca33b9d84d377cc11c4bab092fa9617d7a/charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl" }, { "algorithm": "sha256", - "hash": "b116502087ce8a6b7a5f1814568ccbd0e9f6cfd99948aa59b0e241dc57cf739f", - "url": "https://files.pythonhosted.org/packages/45/3d/fa2683f5604f99fba5098a7313e5d4846baaecbee754faf115907f21a85f/charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl" + "hash": "45de3f87179c1823e6d9e32156fb14c1927fcc9aba21433f088fdfb555b77c10", + "url": "https://files.pythonhosted.org/packages/47/71/2ce8dca3e8cf1f65c36b6317cf68382bb259966e3a208da6e5550029ab79/charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_x86_64.whl" }, { "algorithm": "sha256", - "hash": "ac3775e3311661d4adace3697a52ac0bab17edd166087d493b52d4f4f553f9f0", - "url": "https://files.pythonhosted.org/packages/4e/11/f7077d78b18aca8ea3186a706c0221aa2bc34c442a3d3bdf3ad401a29052/charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_aarch64.whl" + "hash": "e1c8a2f4c69e08e89632defbfabec2feb8a8d99edc9f89ce33c4b9e36ab63037", + "url": "https://files.pythonhosted.org/packages/49/60/87a026215ed77184c413ebb85bafa6c0a998bdc0d1e03b894fa326f2b0f9/charset_normalizer-3.2.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl" }, { "algorithm": "sha256", - "hash": "74db0052d985cf37fa111828d0dd230776ac99c740e1a758ad99094be4f1803d", - "url": "https://files.pythonhosted.org/packages/4f/18/92866f050f7114ba38aba4f4a69f83cc2a25dc2e5a8af4b44fd1bfd6d528/charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_aarch64.whl" + "hash": "c4fb39a81950ec280984b3a44f5bd12819953dc5fa3a7e6fa7a80db5ee853952", + "url": "https://files.pythonhosted.org/packages/4a/46/a22af93e707f0d3c3865a2c21b4363c778239f5a6405aadd220992ac3058/charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl" }, { "algorithm": "sha256", - "hash": "6f4f4668e1831850ebcc2fd0b1cd11721947b6dc7c00bf1c6bd3c929ae14f2c7", - "url": "https://files.pythonhosted.org/packages/4f/7c/af43743567a7da2a069b4f9fa31874c3c02b963cd1fb84fe1e7568a567e6/charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_ppc64le.whl" + "hash": "c4983bf937209c57240cff65906b18bb35e64ae872da6a0db937d7b4af845dd7", + "url": "https://files.pythonhosted.org/packages/4d/ce/8ce85a7d61bbfb5e49094040642f1558b3cf6cf2ad91bbb3616a967dea38/charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_i686.whl" }, { "algorithm": "sha256", - "hash": "e0ac8959c929593fee38da1c2b64ee9778733cdf03c482c9ff1d508b6b593b2b", - "url": "https://files.pythonhosted.org/packages/4f/a2/9031ba4a008e11a21d7b7aa41751290d2f2035a2f14ecb6e589771a17c47/charset_normalizer-3.1.0-cp310-cp310-macosx_10_9_universal2.whl" + "hash": "9bd9b3b31adcb054116447ea22caa61a285d92e94d710aa5ec97992ff5eb7cf3", + "url": "https://files.pythonhosted.org/packages/59/8e/62651b09599938e5e6d068ea723fd22d3f8c14d773c3c11c58e5e7d1eab7/charset_normalizer-3.2.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl" }, { "algorithm": "sha256", - "hash": "cb7b2ab0188829593b9de646545175547a70d9a6e2b63bf2cd87a0a391599324", - "url": "https://files.pythonhosted.org/packages/56/24/5f2dedcf3d0673931b6200c410832ae44b376848bc899dbf1fa6c91c4ebe/charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_x86_64.whl" + "hash": "f058f6963fd82eb143c692cecdc89e075fa0828db2e5b291070485390b2f1c9c", + "url": "https://files.pythonhosted.org/packages/5a/60/eeb158f11b0dee921d3e44bf37971271060b234ee60b14fa16ccc1947cbe/charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl" }, { "algorithm": "sha256", - "hash": "cf6511efa4801b9b38dc5546d7547d5b5c6ef4b081c60b23e4d941d0eba9cbeb", - "url": "https://files.pythonhosted.org/packages/5d/2b/4d8c80400c04ae3c8dbc847de092e282b5c7b17f8f9505d68bb3e5815c71/charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_ppc64le.whl" + "hash": "e03b8895a6990c9ab2cdcd0f2fe44088ca1c65ae592b8f795c3294af00a461c3", + "url": "https://files.pythonhosted.org/packages/5f/52/e8ca03368aeecdd5c0057bd1f8ef189796d232b152e3de4244bb5a72d135/charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl" }, { "algorithm": "sha256", - "hash": "628c985afb2c7d27a4800bfb609e03985aaecb42f955049957814e0491d4006d", - "url": "https://files.pythonhosted.org/packages/61/e3/ad9ae58b28482d1069eba1edec2be87701f5dd6fd6024a665020d66677a0/charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl" + "hash": "a7647ebdfb9682b7bb97e2a5e7cb6ae735b1c25008a70b906aecca294ee96cf4", + "url": "https://files.pythonhosted.org/packages/63/f9/14ffa4b88c1b42837dfa488b0943b7bd7f54f5b63135bf97e5001f6957e7/charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl" }, { "algorithm": "sha256", - "hash": "e89df2958e5159b811af9ff0f92614dabf4ff617c03a4c1c6ff53bf1c399e0e1", - "url": "https://files.pythonhosted.org/packages/67/30/dbab1fe5ab2ce5d3d517ad9936170d896e9687f3860a092519f1fe359812/charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl" + "hash": "baacc6aee0b2ef6f3d308e197b5d7a81c0e70b06beae1f1fcacffdbd124fe0e3", + "url": "https://files.pythonhosted.org/packages/6b/b7/f042568ee89c378b457f73fda1642fd3b795df79c285520e4ec8a74c8b09/charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_aarch64.whl" }, { "algorithm": "sha256", - "hash": "d7fc3fca01da18fbabe4625d64bb612b533533ed10045a2ac3dd194bfa656b60", - "url": "https://files.pythonhosted.org/packages/67/df/660e9665ace7ad711e275194a86cb757fb4d4e513fae5ff3d39573db4984/charset_normalizer-3.1.0-cp310-cp310-macosx_10_9_x86_64.whl" + "hash": "9e608aafdb55eb9f255034709e20d5a83b6d60c054df0802fa9c9883d0a937aa", + "url": "https://files.pythonhosted.org/packages/6f/14/8e317fa69483a2823ea358a77e243c37f23f536a7add1b605460269593b5/charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_x86_64.whl" }, { "algorithm": "sha256", - "hash": "73dc03a6a7e30b7edc5b01b601e53e7fc924b04e1835e8e407c12c037e81adbd", - "url": "https://files.pythonhosted.org/packages/68/77/af702eba147ba963b27eb00832cef6b8c4cb9fcf7404a476993876434b93/charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_s390x.whl" + "hash": "1a100c6d595a7f316f1b6f01d20815d916e75ff98c27a01ae817439ea7726329", + "url": "https://files.pythonhosted.org/packages/79/55/9aef5046a1765acacf28f80994f5a964ab4f43ab75208b1265191a11004b/charset_normalizer-3.2.0-cp38-cp38-macosx_10_9_x86_64.whl" }, { "algorithm": "sha256", - "hash": "1435ae15108b1cb6fffbcea2af3d468683b7afed0169ad718451f8db5d1aff6f", - "url": "https://files.pythonhosted.org/packages/69/22/66351781e668158feef71c5e3b059a79ecc9efc3ef84a45888b0f3a933d5/charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl" + "hash": "203f0c8871d5a7987be20c72442488a0b8cfd0f43b7973771640fc593f56321f", + "url": "https://files.pythonhosted.org/packages/7b/c6/7f75892d87d7afcf8ed909f3e74de1bc61abd9d77cd9aab1f449430856c5/charset_normalizer-3.2.0-cp39-cp39-macosx_10_9_x86_64.whl" }, { "algorithm": "sha256", - "hash": "20064ead0717cf9a73a6d1e779b23d149b53daf971169289ed2ed43a71e8d3b0", - "url": "https://files.pythonhosted.org/packages/6d/59/59a3f4d8a59ee270da77f9e954a0e284c9d6884d39ec69d696d9aa5ff2f2/charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl" + "hash": "2dee8e57f052ef5353cf608e0b4c871aee320dd1b87d351c28764fc0ca55f9f4", + "url": "https://files.pythonhosted.org/packages/80/75/eadff07a61d5602b6b19859d464bc0983654ae79114ef8aa15797b02271c/charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl" }, { "algorithm": "sha256", - "hash": "c84132a54c750fda57729d1e2599bb598f5fa0344085dbde5003ba429a4798c0", - "url": "https://files.pythonhosted.org/packages/72/90/667a6bc6abe42fc10adf4cd2c1e1c399d78e653dbac4c8018350843d4ab7/charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl" + "hash": "7c70087bfee18a42b4040bb9ec1ca15a08242cf5867c58726530bdf3945672ed", + "url": "https://files.pythonhosted.org/packages/81/a0/96317ce912b512b7998434eae5e24b28bcc5f1680ad85348e31e1ca56332/charset_normalizer-3.2.0-cp310-cp310-macosx_10_9_x86_64.whl" }, { "algorithm": "sha256", - "hash": "de5695a6f1d8340b12a5d6d4484290ee74d61e467c39ff03b39e30df62cf83a0", - "url": "https://files.pythonhosted.org/packages/74/5f/361202de730532028458b729781b8435f320e31a622c27f30e25eec80513/charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl" + "hash": "c1c76a1743432b4b60ab3358c937a3fe1341c828ae6194108a94c69028247f22", + "url": "https://files.pythonhosted.org/packages/85/52/77ab28e0eb07f12a02732c55abfc3be481bd46c91d5ade76a8904dfb59a4/charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_aarch64.whl" }, { "algorithm": "sha256", - "hash": "49919f8400b5e49e961f320c735388ee686a62327e773fa5b3ce6721f7e785ce", - "url": "https://files.pythonhosted.org/packages/74/f1/d0b8385b574f7e086fb6709e104b696707bd3742d54a6caf0cebbb7e975b/charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_s390x.whl" + "hash": "eef9df1eefada2c09a5e7a40991b9fc6ac6ef20b1372abd48d2794a316dc0449", + "url": "https://files.pythonhosted.org/packages/89/f5/88e9dd454756fea555198ddbe6fa40d6408ec4f10ad4f0a911e0b7e471e4/charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl" }, { "algorithm": "sha256", - "hash": "d2686f91611f9e17f4548dbf050e75b079bbc2a82be565832bc8ea9047b61c8c", - "url": "https://files.pythonhosted.org/packages/82/b9/51b66a647be8685dee75b7807e0f750edf5c1e4f29bc562ad285c501e3c7/charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_x86_64.whl" + "hash": "c04a46716adde8d927adb9457bbe39cf473e1e2c2f5d0a16ceb837e5d841ad4f", + "url": "https://files.pythonhosted.org/packages/8b/c4/62b920ec8f4ec7b55cd29db894ced9a649214fd506295ac19fb786fe3c6f/charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_ppc64le.whl" }, { "algorithm": "sha256", - "hash": "dd5653e67b149503c68c4018bf07e42eeed6b4e956b24c00ccdf93ac79cdff84", - "url": "https://files.pythonhosted.org/packages/84/23/f60cda6c70ae922ad78368982f06e7fef258fba833212f26275fe4727dc4/charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_s390x.whl" + "hash": "4957669ef390f0e6719db3613ab3a7631e68424604a7b448f079bee145da6e09", + "url": "https://files.pythonhosted.org/packages/8e/a2/77cf1f042a4697822070fd5f3f5f58fd0e3ee798d040e3863eac43e3a2e5/charset_normalizer-3.2.0-cp311-cp311-macosx_10_9_universal2.whl" }, { "algorithm": "sha256", - "hash": "f8303414c7b03f794347ad062c0516cee0e15f7a612abd0ce1e25caf6ceb47df", - "url": "https://files.pythonhosted.org/packages/85/e8/18d408d8fe29a56012c10d6b15960940b83f06620e9d7481581cdc6d9901/charset_normalizer-3.1.0-cp311-cp311-macosx_11_0_arm64.whl" + "hash": "f779d3ad205f108d14e99bb3859aa7dd8e9c68874617c72354d7ecaec2a054ac", + "url": "https://files.pythonhosted.org/packages/91/e6/8fa919fc84a106e9b04109de62bdf8526899e2754a64da66e1cd50ac1faa/charset_normalizer-3.2.0-cp311-cp311-macosx_11_0_arm64.whl" }, { "algorithm": "sha256", - "hash": "6f6c7a8a57e9405cad7485f4c9d3172ae486cfef1344b5ddd8e5239582d7355e", - "url": "https://files.pythonhosted.org/packages/94/70/23981e7bf098efbc4037e7c66d28a10e950d9296c08c6dea8ef290f9c79e/charset_normalizer-3.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl" + "hash": "3bb7fda7260735efe66d5107fb7e6af6a7c04c7fce9b2514e04b7a74b06bf5dd", + "url": "https://files.pythonhosted.org/packages/94/fc/53e12f67fff7a127fe2998de3469a9856c6c7cf67f18dc5f417df3e5e60f/charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_ppc64le.whl" }, { "algorithm": "sha256", - "hash": "c3af8e0f07399d3176b179f2e2634c3ce9c1301379a6b8c9c9aeecd481da494f", - "url": "https://files.pythonhosted.org/packages/9a/f1/ff81439aa09070fee64173e6ca6ce1342f2b1cca997bcaae89e443812684/charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl" + "hash": "d62e51710986674142526ab9f78663ca2b0726066ae26b78b22e0f5e571238dd", + "url": "https://files.pythonhosted.org/packages/95/d2/6f25fddfbe31448ceea236e03b70d2bbd647d4bc9148bf9665307794c4f2/charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_x86_64.whl" }, { "algorithm": "sha256", - "hash": "aaf53a6cebad0eae578f062c7d462155eada9c172bd8c4d250b8c1d8eb7f916a", - "url": "https://files.pythonhosted.org/packages/9e/62/a1e0a8f8830c92014602c8a88a1a20b8a68d636378077381f671e6e1cec9/charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl" + "hash": "cd6dbe0238f7743d0efe563ab46294f54f9bc8f4b9bcf57c3c666cc5bc9d1299", + "url": "https://files.pythonhosted.org/packages/95/d3/ed29b2d14ec9044a223dcf7c439fa550ef9c6d06c9372cd332374d990559/charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_s390x.whl" }, { "algorithm": "sha256", - "hash": "0be65ccf618c1e7ac9b849c315cc2e8a8751d9cfdaa43027d4f6624bd587ab7e", - "url": "https://files.pythonhosted.org/packages/a2/6c/5167f08da5298f383036c33cb749ab5b3405fd07853edc8314c6882c01b8/charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_s390x.whl" + "hash": "c57921cda3a80d0f2b8aec7e25c8aa14479ea92b5b51b6876d975d925a2ea346", + "url": "https://files.pythonhosted.org/packages/95/ee/8bb03c3518a228dc5956d1b4f46d8258639ff118881fba456b72b06561cf/charset_normalizer-3.2.0-cp37-cp37m-macosx_10_9_x86_64.whl" }, { "algorithm": "sha256", - "hash": "1e8fcdd8f672a1c4fc8d0bd3a2b576b152d2a349782d1eb0f6b8e52e9954731d", - "url": "https://files.pythonhosted.org/packages/a4/03/355281b62c26712a50c6a9dd75339d8cdd58488fd7bf2556ba1320ebd315/charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_i686.whl" + "hash": "95eb302ff792e12aba9a8b8f8474ab229a83c103d74a750ec0bd1c1eea32e669", + "url": "https://files.pythonhosted.org/packages/97/f6/0bae7bdfb07ca42bf5e3e37dbd0cce02d87dd6e87ea85dff43106dfc1f48/charset_normalizer-3.2.0-cp38-cp38-macosx_10_9_universal2.whl" }, { "algorithm": "sha256", - "hash": "53d0a3fa5f8af98a1e261de6a3943ca631c526635eb5817a87a59d9a57ebf48f", - "url": "https://files.pythonhosted.org/packages/a9/83/138d2624fdbcb62b7e14715eb721d44347e41a1b4c16544661e940793f49/charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_x86_64.whl" + "hash": "7a4826ad2bd6b07ca615c74ab91f32f6c96d08f6fcc3902ceeedaec8cdc3bcd6", + "url": "https://files.pythonhosted.org/packages/99/23/7262c6a7c8a8c2ec783886166a432985915f67277bc44020d181e5c04584/charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_ppc64le.whl" }, { "algorithm": "sha256", - "hash": "8f25e17ab3039b05f762b0a55ae0b3632b2e073d9c8fc88e89aca31a6198e88f", - "url": "https://files.pythonhosted.org/packages/ac/7f/62d5dff4e9cb993e4b0d4ea78a74cc84d7d92120879529e0ce0965765936/charset_normalizer-3.1.0-cp39-cp39-macosx_11_0_arm64.whl" + "hash": "6339d047dab2780cc6220f46306628e04d9750f02f983ddb37439ca47ced7149", + "url": "https://files.pythonhosted.org/packages/9c/71/bf12b8e0d6e1d84ed29c3e16ea1efc47ae96487bde823130d12139c434a0/charset_normalizer-3.2.0-cp38-cp38-macosx_11_0_arm64.whl" }, { "algorithm": "sha256", - "hash": "3a06f32c9634a8705f4ca9946d667609f52cf130d5548881401f1eb2c39b1e2c", - "url": "https://files.pythonhosted.org/packages/ac/c5/990bc41a98b7fa2677c665737fdf278bb74ad4b199c56b6b564b3d4cbfc5/charset_normalizer-3.1.0-cp38-cp38-macosx_10_9_x86_64.whl" + "hash": "41b25eaa7d15909cf3ac4c96088c1f266a9a93ec44f87f1d13d4a0e86c81b982", + "url": "https://files.pythonhosted.org/packages/9c/74/10a518cd27c2c595768f70ddbd7d05c9acb01b26033f79433105ccc73308/charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl" }, { "algorithm": "sha256", - "hash": "6f5c2e7bc8a4bf7c426599765b1bd33217ec84023033672c1e9a8b35eaeaaaf8", - "url": "https://files.pythonhosted.org/packages/b0/55/d8ef4c8c7d2a8b3a16e7d9b03c59475c2ee96a0e0c90b14c99faaac0ee3b/charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_x86_64.whl" + "hash": "193cbc708ea3aca45e7221ae58f0fd63f933753a9bfb498a3b474878f12caaad", + "url": "https://files.pythonhosted.org/packages/a4/65/057bf29660aae6ade0816457f8db4e749e5c0bfa2366eb5f67db9912fa4c/charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl" }, { "algorithm": "sha256", - "hash": "11d3bcb7be35e7b1bba2c23beedac81ee893ac9871d0ba79effc7fc01167db6c", - "url": "https://files.pythonhosted.org/packages/bb/dc/58fdef3ab85e8e7953a8b89ef1d2c06938b8ad88d9617f22967e1a90e6b8/charset_normalizer-3.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl" + "hash": "e4b749b9cc6ee664a3300bb3a273c1ca8068c46be705b6c31cf5d276f8628a94", + "url": "https://files.pythonhosted.org/packages/ad/0d/9aa61083c35dc21e75a97c0ee53619daf0e5b4fd3b8b4d8bb5e7e56ed302/charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl" }, { "algorithm": "sha256", - "hash": "891cf9b48776b5c61c700b55a598621fdb7b1e301a550365571e9624f270c203", - "url": "https://files.pythonhosted.org/packages/c2/35/dfb4032f5712747d3dcfdd19d0768f6d8f60910ae24ed066ecbf442be013/charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_aarch64.whl" + "hash": "2efb1bd13885392adfda4614c33d3b68dee4921fd0ac1d3988f8cbb7d589e72a", + "url": "https://files.pythonhosted.org/packages/af/3d/57e7e401f8db6dd0c56e366d69dc7366173fc549bcd533dea15f2a805000/charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl" }, { "algorithm": "sha256", - "hash": "04eefcee095f58eaabe6dc3cc2262f3bcd776d2c67005880894f447b3f2cb9c1", - "url": "https://files.pythonhosted.org/packages/c6/ab/43ea052756b2f2dcb6a131897811c0e2704b0288f090336217d3346cd682/charset_normalizer-3.1.0-cp310-cp310-macosx_11_0_arm64.whl" + "hash": "0b87549028f680ca955556e3bd57013ab47474c3124dc069faa0b6545b6c9710", + "url": "https://files.pythonhosted.org/packages/af/6f/b9b1613a5b672004f08ef3c02242b07406ff36164725ff15207737601de5/charset_normalizer-3.2.0-cp310-cp310-macosx_10_9_universal2.whl" }, { "algorithm": "sha256", - "hash": "bd7163182133c0c7701b25e604cf1611c0d87712e56e88e7ee5d72deab3e76b5", - "url": "https://files.pythonhosted.org/packages/c9/8c/a76dd9f2c8803eb147e1e715727f5c3ba0ef39adaadf66a7b3698c113180/charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_aarch64.whl" + "hash": "3170c9399da12c9dc66366e9d14da8bf7147e1e9d9ea566067bbce7bb74bd9c2", + "url": "https://files.pythonhosted.org/packages/b6/2a/03e909cad170b0df5ce8b731fecbc872b7b922a1d38da441b5062a89e53f/charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_i686.whl" }, { "algorithm": "sha256", - "hash": "75f2568b4189dda1c567339b48cba4ac7384accb9c2a7ed655cd86b04055c795", - "url": "https://files.pythonhosted.org/packages/cc/f6/21a66e524658bd1dd7b89ac9d1ee8f7823f2d9701a2fbc458ab9ede53c63/charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl" + "hash": "246de67b99b6851627d945db38147d1b209a899311b1305dd84916f2b88526c6", + "url": "https://files.pythonhosted.org/packages/bc/85/ef25d4ba14c7653c3020a1c6e1a7413e6791ef36a0ac177efa605fc2c737/charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl" }, { "algorithm": "sha256", - "hash": "6baf0baf0d5d265fa7944feb9f7451cc316bfe30e8df1a61b1bb08577c554f31", - "url": "https://files.pythonhosted.org/packages/d5/92/86c0f0e66e897f6818c46dadef328a5b345d061688f9960fc6ca1fd03dbe/charset_normalizer-3.1.0-cp39-cp39-macosx_10_9_x86_64.whl" + "hash": "89f1b185a01fe560bc8ae5f619e924407efca2191b56ce749ec84982fc59a32a", + "url": "https://files.pythonhosted.org/packages/cb/e7/5e43745003bf1f90668c7be23fc5952b3a2b9c2558f16749411c18039b36/charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl" }, { "algorithm": "sha256", - "hash": "e1b25e3ad6c909f398df8921780d6a3d120d8c09466720226fc621605b6f92b1", - "url": "https://files.pythonhosted.org/packages/d7/4c/37ad75674e8c6bc22ab01bef673d2d6e46ee44203498c9a26aa23959afe5/charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl" + "hash": "a386ebe437176aab38c041de1260cd3ea459c6ce5263594399880bbc398225b2", + "url": "https://files.pythonhosted.org/packages/cb/f9/a652e1b495345000bb7f0e2a960a82ca941db55cb6de158d542918f8b52b/charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_i686.whl" }, { "algorithm": "sha256", - "hash": "7381c66e0561c5757ffe616af869b916c8b4e42b367ab29fedc98481d1e74e14", - "url": "https://files.pythonhosted.org/packages/d8/ca/a7ff600781bf1e5f702ba26bb82f2ba1d3a873a3f8ad73cc44c79dfaefa9/charset_normalizer-3.1.0-cp38-cp38-macosx_11_0_arm64.whl" + "hash": "e857a2232ba53ae940d3456f7533ce6ca98b81917d47adc3c7fd55dad8fab858", + "url": "https://files.pythonhosted.org/packages/d3/d8/50a33f82bdf25e71222a55cef146310e3e9fe7d5790be5281d715c012eae/charset_normalizer-3.2.0-cp39-cp39-macosx_11_0_arm64.whl" }, { "algorithm": "sha256", - "hash": "ac0aa6cd53ab9a31d397f8303f92c42f534693528fafbdb997c82bae6e477ad9", - "url": "https://files.pythonhosted.org/packages/dd/39/6276cf5a395ffd39b77dadf0e2fcbfca8dbfe48c56ada250c40086055143/charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl" + "hash": "bf420121d4c8dce6b889f0e8e4ec0ca34b7f40186203f06a946fa0276ba54029", + "url": "https://files.pythonhosted.org/packages/e8/74/077cb06aed5d41118a5803e842943311032ab2fb94cf523be620c5be9911/charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_i686.whl" }, { "algorithm": "sha256", - "hash": "9a3267620866c9d17b959a84dd0bd2d45719b817245e49371ead79ed4f710d19", - "url": "https://files.pythonhosted.org/packages/e1/7c/398600268fc98b7e007f5a716bd60903fff1ecff75e45f5700212df5cd76/charset_normalizer-3.1.0-cp311-cp311-macosx_10_9_universal2.whl" + "hash": "ccd16eb18a849fd8dcb23e23380e2f0a354e8daa0c984b8a732d9cfaba3a776d", + "url": "https://files.pythonhosted.org/packages/e8/ad/ac491a1cf960ec5873c1b0e4fd4b90b66bfed4a1063933612f2da8189eb8/charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_ppc64le.whl" }, { "algorithm": "sha256", - "hash": "0c95f12b74681e9ae127728f7e5409cbbef9cd914d5896ef238cc779b8152373", - "url": "https://files.pythonhosted.org/packages/e1/b4/53678b2a14e0496fc167fe9b9e726ad33d670cfd2011031aa5caeee6b784/charset_normalizer-3.1.0-cp37-cp37m-macosx_10_9_x86_64.whl" + "hash": "a103b3a7069b62f5d4890ae1b8f0597618f628b286b03d4bc9195230b154bfa9", + "url": "https://files.pythonhosted.org/packages/ec/a7/96835706283d63fefbbbb4f119d52f195af00fc747e67cc54397c56312c8/charset_normalizer-3.2.0-cp310-cp310-macosx_11_0_arm64.whl" }, { "algorithm": "sha256", - "hash": "abc1185d79f47c0a7aaf7e2412a0eb2c03b724581139193d2d82b3ad8cbb00ac", - "url": "https://files.pythonhosted.org/packages/e5/aa/9d2d60d6a566423da96c15cd11cbb88a70f9aff9a4db096094ee19179cab/charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_s390x.whl" + "hash": "f7560358a6811e52e9c4d142d497f1a6e10103d3a6881f18d04dbce3729c0e2c", + "url": "https://files.pythonhosted.org/packages/ed/21/03b4a3533b7a845ee31ed4542ca06debdcf7f12c099ae3dd6773c275b0df/charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_i686.whl" }, { "algorithm": "sha256", - "hash": "e633940f28c1e913615fd624fcdd72fdba807bf53ea6925d6a588e84e1151531", - "url": "https://files.pythonhosted.org/packages/ea/38/d31c7906c4be13060c1a5034087966774ef33ab57ff2eee76d71265173c3/charset_normalizer-3.1.0-cp38-cp38-macosx_10_9_universal2.whl" + "hash": "ee4006268ed33370957f55bf2e6f4d263eaf4dc3cfc473d1d90baff6ed36ce4a", + "url": "https://files.pythonhosted.org/packages/ee/ff/997d61ca61efe90662181f494c8e9fdac14e32de26cc6cb7c7a3fe96c862/charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_aarch64.whl" }, { "algorithm": "sha256", - "hash": "fca62a8301b605b954ad2e9c3666f9d97f63872aa4efcae5492baca2056b74ab", - "url": "https://files.pythonhosted.org/packages/f2/b7/e21e16c98575616f4ce09dc766dbccdac0ca119c176b184d46105e971a84/charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl" + "hash": "94aea8eff76ee6d1cdacb07dd2123a68283cb5569e0250feab1240058f53b623", + "url": "https://files.pythonhosted.org/packages/f0/24/7e6c604d80a8eb4378cb075647e65b7905f06645243b43c79fe4b7487ed7/charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl" }, { "algorithm": "sha256", - "hash": "5f008525e02908b20e04707a4f704cd286d94718f48bb33edddc7d7b584dddc1", - "url": "https://files.pythonhosted.org/packages/f2/d7/6ee92c11eda3f3c9cac1e059901092bfdf07388be7d2e60ac627527eee62/charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_i686.whl" + "hash": "db901e2ac34c931d73054d9797383d0f8009991e723dab15109740a63e7f902a", + "url": "https://files.pythonhosted.org/packages/f1/f2/ef1479e741a7ed166b8253987071b2cf2d2b727fc8fa081520e3f7c97e44/charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl" }, { "algorithm": "sha256", - "hash": "22908891a380d50738e1f978667536f6c6b526a2064156203d418f4856d6e86a", - "url": "https://files.pythonhosted.org/packages/f4/0a/8c03913ed1eca9d831db0c28759edb6ce87af22bb55dbc005a52525a75b6/charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_x86_64.whl" + "hash": "1249cbbf3d3b04902ff081ffbb33ce3377fa6e4c7356f759f3cd076cc138d020", + "url": "https://files.pythonhosted.org/packages/f2/e8/d9651a0afd4ee792207b24bd1d438ed750f1c0f29df62bd73d24ded428f9/charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_x86_64.whl" }, { "algorithm": "sha256", - "hash": "3747443b6a904001473370d7810aa19c3a180ccd52a7157aacc264a5ac79265e", - "url": "https://files.pythonhosted.org/packages/f6/0f/de1c4030fd669e6719277043e3b0f152a83c118dd1020cf85b51d443d04a/charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl" + "hash": "2f4ac36d8e2b4cc1aa71df3dd84ff8efbe3bfb97ac41242fbcfc053c67434f46", + "url": "https://files.pythonhosted.org/packages/f4/39/b024eb6c2a2b8136f1f48fd2f2eee22ed98fbfe3cd7ddf81dad2b8dd3c1b/charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_aarch64.whl" }, { "algorithm": "sha256", - "hash": "78cacd03e79d009d95635e7d6ff12c21eb89b894c354bd2b2ed0b4763373693b", - "url": "https://files.pythonhosted.org/packages/f8/ed/500609cb2457b002242b090c814549997424d72690ef3058cfdfca91f68b/charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl" + "hash": "aaf63899c94de41fe3cf934601b0f7ccb6b428c6e4eeb80da72c58eab077b19a", + "url": "https://files.pythonhosted.org/packages/f5/50/410da81fd67eb1becef9d633f6aae9f6e296f60126cfc3d19631f7919f76/charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_s390x.whl" }, { "algorithm": "sha256", - "hash": "04afa6387e2b282cf78ff3dbce20f0cc071c12dc8f685bd40960cc68644cfea6", - "url": "https://files.pythonhosted.org/packages/fa/8e/2e5c742c3082bce3eea2ddd5b331d08050cda458bc362d71c48e07a44719/charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_ppc64le.whl" + "hash": "8700f06d0ce6f128de3ccdbc1acaea1ee264d2caa9ca05daaf492fde7c2a7200", + "url": "https://files.pythonhosted.org/packages/f9/0d/514be8597d7a96243e5467a37d337b9399cec117a513fcf9328405d911c0/charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl" }, { "algorithm": "sha256", - "hash": "34e0a2f9c370eb95597aae63bf85eb5e96826d81e3dcf88b8886012906f509b5", - "url": "https://files.pythonhosted.org/packages/ff/d7/8d757f8bd45be079d76309248845a04f09619a7b17d6dfc8c9ff6433cac2/charset-normalizer-3.1.0.tar.gz" + "hash": "8c2f5e83493748286002f9369f3e6607c565a6a90425a3a1fef5ae32a36d749d", + "url": "https://files.pythonhosted.org/packages/fd/17/0a1dba835ec37a3cc025f5c49653effb23f8cd391dea5e60a5696d639a92/charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_aarch64.whl" } ], "project_name": "charset-normalizer", "requires_dists": [], "requires_python": ">=3.7.0", - "version": "3.1.0" + "version": "3.2.0" }, { "artifacts": [ { "algorithm": "sha256", - "hash": "bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48", - "url": "https://files.pythonhosted.org/packages/c2/f1/df59e28c642d583f7dacffb1e0965d0e00b218e0186d7858ac5233dce840/click-8.1.3-py3-none-any.whl" + "hash": "ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", + "url": "https://files.pythonhosted.org/packages/00/2e/d53fa4befbf2cfa713304affc7ca780ce4fc1fd8710527771b58311a3229/click-8.1.7-py3-none-any.whl" }, { "algorithm": "sha256", - "hash": "7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e", - "url": "https://files.pythonhosted.org/packages/59/87/84326af34517fca8c58418d148f2403df25303e02736832403587318e9e8/click-8.1.3.tar.gz" + "hash": "ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de", + "url": "https://files.pythonhosted.org/packages/96/d3/f04c7bfcf5c1862a2a5b845c6b2b360488cf47af55dfa79c98f6a6bf98b5/click-8.1.7.tar.gz" } ], "project_name": "click", @@ -473,35 +473,35 @@ "importlib-metadata; python_version < \"3.8\"" ], "requires_python": ">=3.7", - "version": "8.1.3" + "version": "8.1.7" }, { "artifacts": [ { "algorithm": "sha256", - "hash": "0f8ca79bc9b1d6fcaafdbe194b17ba1a2dde44ddf19087235c3efed2ad288143", - "url": "https://files.pythonhosted.org/packages/cf/b2/808e028b944a1f7c21005205762ee88654c40b73b9de2a04e18384d1c9cd/click_option_group-0.5.5-py3-none-any.whl" + "hash": "38a26d963ee3ad93332ddf782f9259c5bdfe405e73408d943ef5e7d0c3767ec7", + "url": "https://files.pythonhosted.org/packages/af/75/81ea958bc0f7e410257cb2a42531b93a7695a31930cde87192c010a52c50/click_option_group-0.5.6-py3-none-any.whl" }, { "algorithm": "sha256", - "hash": "78ee474f07a0ca0ef6c0317bb3ebe79387aafb0c4a1e03b1d8b2b0be1e42fc78", - "url": "https://files.pythonhosted.org/packages/4c/29/ff7cd69825b5bfb48e39853b75d5dc2e98a581730f2b6c9c014188730755/click-option-group-0.5.5.tar.gz" + "hash": "97d06703873518cc5038509443742b25069a3c7562d1ea72ff08bfadde1ce777", + "url": "https://files.pythonhosted.org/packages/e7/b8/91054601a2e05fd9060cb1baf56be5b24145817b059e078669e1099529c7/click-option-group-0.5.6.tar.gz" } ], "project_name": "click-option-group", "requires_dists": [ "Click<9,>=7.0", "Pallets-Sphinx-Themes; extra == \"docs\"", - "coverage<6; extra == \"tests_cov\"", + "coverage; extra == \"tests_cov\"", "coveralls; extra == \"tests_cov\"", "m2r2; extra == \"docs\"", "pytest-cov; extra == \"tests_cov\"", "pytest; extra == \"tests\"", "pytest; extra == \"tests_cov\"", - "sphinx<6,>=3.0; extra == \"docs\"" + "sphinx; extra == \"docs\"" ], "requires_python": "<4,>=3.6", - "version": "0.5.5" + "version": "0.5.6" }, { "artifacts": [ @@ -604,18 +604,17 @@ "artifacts": [ { "algorithm": "sha256", - "hash": "43dd286a2cd8995d5eaef7fee2066340423b818ed3fd70adf0bad5f1fac53fed", - "url": "https://files.pythonhosted.org/packages/30/bb/bf2944b8b88c65b797acc2c6a2cb0fb817f7364debf0675792e034013858/importlib_metadata-6.6.0-py3-none-any.whl" + "hash": "cb52082e659e97afc5dac71e79de97d8681de3aa07ff18578330904a9d18e5b5", + "url": "https://files.pythonhosted.org/packages/ff/94/64287b38c7de4c90683630338cf28f129decbba0a44f0c6db35a873c73c4/importlib_metadata-6.7.0-py3-none-any.whl" }, { "algorithm": "sha256", - "hash": "92501cdf9cc66ebd3e612f1b4f0c0765dfa42f0fa38ffb319b6bd84dd675d705", - "url": "https://files.pythonhosted.org/packages/0b/1f/9de392c2b939384e08812ef93adf37684ec170b5b6e7ea302d9f163c2ea0/importlib_metadata-6.6.0.tar.gz" + "hash": "1aaf550d4f73e5d6783e7acb77aec43d49da8017410afae93822cc9cca98c4d4", + "url": "https://files.pythonhosted.org/packages/a3/82/f6e29c8d5c098b6be61460371c2c5591f4a335923639edec43b3830650a4/importlib_metadata-6.7.0.tar.gz" } ], "project_name": "importlib-metadata", "requires_dists": [ - "flake8<5; extra == \"testing\"", "flufl.flake8; extra == \"testing\"", "furo; extra == \"docs\"", "importlib-resources>=1.3; python_version < \"3.9\" and extra == \"testing\"", @@ -628,9 +627,9 @@ "pytest-checkdocs>=2.4; extra == \"testing\"", "pytest-cov; extra == \"testing\"", "pytest-enabler>=1.3; extra == \"testing\"", - "pytest-flake8; python_version < \"3.12\" and extra == \"testing\"", "pytest-mypy>=0.9.1; platform_python_implementation != \"PyPy\" and extra == \"testing\"", "pytest-perf>=0.9.2; extra == \"testing\"", + "pytest-ruff; extra == \"testing\"", "pytest>=6; extra == \"testing\"", "rst.linker>=1.9; extra == \"docs\"", "sphinx-lint; extra == \"docs\"", @@ -639,7 +638,7 @@ "zipp>=0.5" ], "requires_python": ">=3.7", - "version": "6.6.0" + "version": "6.7.0" }, { "artifacts": [ @@ -800,14 +799,14 @@ "artifacts": [ { "algorithm": "sha256", - "hash": "10769981198c7311f84a0ca8db892fa213303a8eb1305deb795a71e7bd606a91", - "url": "https://files.pythonhosted.org/packages/a9/50/1dd5ea74c559df4afb8391f8d05f0fec685dbe8effba13bb9072901eb288/peewee-3.16.2.tar.gz" + "hash": "12b30e931193bc37b11f7c2ac646e3f67125a8b1a543ad6ab37ad124c8df7d16", + "url": "https://files.pythonhosted.org/packages/e2/1e/6455dc3c759af3e565414985c5c6f845d3e5f83bbf4a24cdd0aef9cc3f83/peewee-3.16.3.tar.gz" } ], "project_name": "peewee", "requires_dists": [], "requires_python": null, - "version": "3.16.2" + "version": "3.16.3" }, { "artifacts": [ @@ -831,13 +830,13 @@ "artifacts": [ { "algorithm": "sha256", - "hash": "db2db3deb4b4179f399a09054b023b6a586b76499d36965813c71aa8ed7b5fd1", - "url": "https://files.pythonhosted.org/packages/34/a7/37c8d68532ba71549db4212cb036dbd6161b40e463aba336770e80c72f84/Pygments-2.15.1-py3-none-any.whl" + "hash": "13fc09fa63bc8d8671a6d247e1eb303c4b343eaee81d861f3404db2935653692", + "url": "https://files.pythonhosted.org/packages/43/88/29adf0b44ba6ac85045e63734ae0997d3c58d8b1a91c914d240828d0d73d/Pygments-2.16.1-py3-none-any.whl" }, { "algorithm": "sha256", - "hash": "8ace4d3c1dd481894b2005f560ead0f9f19ee64fe983366be1a21e171d12775c", - "url": "https://files.pythonhosted.org/packages/89/6b/2114e54b290824197006e41be3f9bbe1a26e9c39d1f5fa20a6d62945a0b3/Pygments-2.15.1.tar.gz" + "hash": "1daff0494820c69bc8941e407aa20f577374ee88364ee10a98fdbe0aece96e29", + "url": "https://files.pythonhosted.org/packages/d6/f7/4d461ddf9c2bcd6a4d7b2b139267ca32a69439387cc1f02a924ff8883825/Pygments-2.16.1.tar.gz" } ], "project_name": "pygments", @@ -845,7 +844,7 @@ "importlib-metadata; python_version < \"3.8\" and extra == \"plugins\"" ], "requires_python": ">=3.7", - "version": "2.15.1" + "version": "2.16.1" }, { "artifacts": [ @@ -970,13 +969,13 @@ "artifacts": [ { "algorithm": "sha256", - "hash": "e8f3c9be120d3333921d213eef078af392fba3933ab7ed2d1cba3b56f2568c3b", - "url": "https://files.pythonhosted.org/packages/cf/e1/2aa539876d9ed0ddc95882451deb57cfd7aa8dbf0b8dbce68e045549ba56/requests-2.29.0-py3-none-any.whl" + "hash": "58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f", + "url": "https://files.pythonhosted.org/packages/70/8e/0e2d847013cb52cd35b38c009bb167a1a26b2ce6cd6965bf26b47bc0bf44/requests-2.31.0-py3-none-any.whl" }, { "algorithm": "sha256", - "hash": "f2e34a75f4749019bb0e3effb66683630e4ffeaf75819fb51bebef1bf5aef059", - "url": "https://files.pythonhosted.org/packages/4c/d2/70fc708727b62d55bc24e43cc85f073039023212d482553d853c44e57bdb/requests-2.29.0.tar.gz" + "hash": "942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1", + "url": "https://files.pythonhosted.org/packages/9d/be/10918a2eac4ae9f02f6cfe6414b7a155ccd8f7f9d4380d62fd5b955065c3/requests-2.31.0.tar.gz" } ], "project_name": "requests", @@ -986,55 +985,55 @@ "chardet<6,>=3.0.2; extra == \"use_chardet_on_py3\"", "charset-normalizer<4,>=2", "idna<4,>=2.5", - "urllib3<1.27,>=1.21.1" + "urllib3<3,>=1.21.1" ], "requires_python": ">=3.7", - "version": "2.29.0" + "version": "2.31.0" }, { "artifacts": [ { "algorithm": "sha256", - "hash": "69cdf53799e63f38b95b9bf9c875f8c90e78dd62b2f00c13a911c7a3b9fa4704", - "url": "https://files.pythonhosted.org/packages/39/03/6de23bdd88f5ee7f8b03f94f6e88108f5d7ffe6d207e95cdb06d9aa4cd57/rich-13.3.5-py3-none-any.whl" + "hash": "146a90b3b6b47cac4a73c12866a499e9817426423f57c5a66949c086191a8808", + "url": "https://files.pythonhosted.org/packages/8d/5f/21a93b2ec205f4b79853ff6e838e3c99064d5dbe85ec6b05967506f14af0/rich-13.5.2-py3-none-any.whl" }, { "algorithm": "sha256", - "hash": "2d11b9b8dd03868f09b4fffadc84a6a8cda574e40dc90821bd845720ebb8e89c", - "url": "https://files.pythonhosted.org/packages/3d/0b/8dd34d20929c4b5e474db2e64426175469c2b7fea5ba71c6d4b3397a9729/rich-13.3.5.tar.gz" + "hash": "fb9d6c0a0f643c99eed3875b5377a184132ba9be4d61516a55273d3554d75a39", + "url": "https://files.pythonhosted.org/packages/ad/1a/94fe086875350afbd61795c3805e38ef085af466a695db605bcdd34b4c9c/rich-13.5.2.tar.gz" } ], "project_name": "rich", "requires_dists": [ "ipywidgets<9,>=7.5.1; extra == \"jupyter\"", - "markdown-it-py<3.0.0,>=2.2.0", + "markdown-it-py>=2.2.0", "pygments<3.0.0,>=2.13.0", "typing-extensions<5.0,>=4.0.0; python_version < \"3.9\"" ], "requires_python": ">=3.7.0", - "version": "13.3.5" + "version": "13.5.2" }, { "artifacts": [ { "algorithm": "sha256", - "hash": "742b35d3d665023981bd6d16b3d24248ce5df75fdb4e2924e93a05c1f8b61ca7", - "url": "https://files.pythonhosted.org/packages/9e/cb/938214ac358fbef7058343b3765c79a1b7ed0c366f7f992ce7ff38335652/ruamel.yaml-0.17.21-py3-none-any.whl" + "hash": "23cd2ed620231677564646b0c6a89d138b6822a0d78656df7abda5879ec4f447", + "url": "https://files.pythonhosted.org/packages/d9/0e/2a05efa11ea33513fbdf4a2e2576fe94fd8fa5ad226dbb9c660886390974/ruamel.yaml-0.17.32-py3-none-any.whl" }, { "algorithm": "sha256", - "hash": "8b7ce697a2f212752a35c1ac414471dc16c424c9573be4926b56ff3f5d23b7af", - "url": "https://files.pythonhosted.org/packages/46/a9/6ed24832095b692a8cecc323230ce2ec3480015fbfa4b79941bd41b23a3c/ruamel.yaml-0.17.21.tar.gz" + "hash": "ec939063761914e14542972a5cba6d33c23b0859ab6342f61cf070cfc600efc2", + "url": "https://files.pythonhosted.org/packages/63/dd/b4719a290e49015536bd0ab06ab13e3b468d8697bec6c2f668ac48b05661/ruamel.yaml-0.17.32.tar.gz" } ], "project_name": "ruamel-yaml", "requires_dists": [ - "ruamel.yaml.clib>=0.2.6; platform_python_implementation == \"CPython\" and python_version < \"3.11\"", + "ruamel.yaml.clib>=0.2.7; platform_python_implementation == \"CPython\" and python_version < \"3.12\"", "ruamel.yaml.jinja2>=0.2; extra == \"jinja2\"", "ryd; extra == \"docs\"" ], "requires_python": ">=3", - "version": "0.17.21" + "version": "0.17.32" }, { "artifacts": [ @@ -1118,6 +1117,16 @@ "hash": "91a789b4aa0097b78c93e3dc4b40040ba55bef518f84a40d4442f713b4094acb", "url": "https://files.pythonhosted.org/packages/d6/b0/4b7cab1c2ac7bfb31283bc9d00e6e05a118aff1d0c81776215cfc96810ba/ruamel.yaml.clib-0.2.7-cp38-cp38-macosx_10_9_x86_64.whl" }, + { + "algorithm": "sha256", + "hash": "9c7617df90c1365638916b98cdd9be833d31d337dbcd722485597b43c4a215bf", + "url": "https://files.pythonhosted.org/packages/f3/1d/291c1d38b0e6b9cbaacfdea24448453456a42f580d70b60e9c9e3dd35f9d/ruamel.yaml.clib-0.2.7-cp311-cp311-manylinux2014_aarch64.whl" + }, + { + "algorithm": "sha256", + "hash": "1a6391a7cabb7641c32517539ca42cf84b87b667bad38b78d4d42dd23e957c81", + "url": "https://files.pythonhosted.org/packages/f5/23/b8ff333e40fa194678b01b66c1aced9dc5ecbc16a043b0d09beb6a37377c/ruamel.yaml.clib-0.2.7-cp311-cp311-macosx_13_0_arm64.whl" + }, { "algorithm": "sha256", "hash": "5bc0667c1eb8f83a3752b71b9c4ba55ef7c7058ae57022dd9b29065186a113d9", @@ -1128,11 +1137,6 @@ "hash": "f01da5790e95815eb5a8a138508c01c758e5f5bc0ce4286c4f7028b8dd7ac3d0", "url": "https://files.pythonhosted.org/packages/fb/c0/de69d49a6d0a346fb27ddf3114d807380b08a40d8e22e0fbaf19be8b6044/ruamel.yaml.clib-0.2.7-cp37-cp37m-macosx_12_0_arm64.whl" }, - { - "algorithm": "sha256", - "hash": "721bc4ba4525f53f6a611ec0967bdcee61b31df5a56801281027a3a6d1c2daf5", - "url": "https://files.pythonhosted.org/packages/fc/47/a8e865b6097969e162c2e9efcfed8ded0582ea18305b09426dd648a6b2d4/ruamel.yaml.clib-0.2.7-cp311-cp311-macosx_12_6_arm64.whl" - }, { "algorithm": "sha256", "hash": "bf9a6bc4a0221538b1a7de3ed7bca4c93c02346853f44e1cd764be0023cd3640", @@ -1148,23 +1152,28 @@ "artifacts": [ { "algorithm": "sha256", - "hash": "517ed80d50a408d18851ffd74323f43c78650c232b6a8c1defce92ec42d9f575", - "url": "https://files.pythonhosted.org/packages/4a/b0/4a46f38b4661576e3c12df7f80e3a7bea846bf1efe942da7bbf4fc27d6a7/semgrep-1.20.0-cp37.cp38.cp39.py37.py38.py39-none-macosx_11_0_arm64.whl" + "hash": "9a9140cdfac8cb24ff5177fef39b8cf8d59255df67d93d6da5b577799ce5e3cd", + "url": "https://files.pythonhosted.org/packages/0e/dd/c6e3c2abb9eb6cd2a994ab1322f843c982415f48e4df7a08fac3a42341f5/semgrep-1.37.0-cp37.cp38.cp39.cp310.cp311.py37.py38.py39.py310.py311-none-manylinux2014_aarch64.whl" + }, + { + "algorithm": "sha256", + "hash": "4a3ffab7364cc9140024bd3d5ffdf382f7464c884d89ba8ef2eb0ab2eb3d5ecb", + "url": "https://files.pythonhosted.org/packages/18/4f/7453e7318a6305308536408163a9080d15bf438251858efe7f79e082df56/semgrep-1.37.0-cp37.cp38.cp39.cp310.cp311.py37.py38.py39.py310.py311-none-any.whl" }, { "algorithm": "sha256", - "hash": "db43b3fdbe427f9fe0caf8bafaf681fd3478a128e2b887a8036067cb02d3f272", - "url": "https://files.pythonhosted.org/packages/10/ac/d7972c3cf4bd5d9c0cf5d22d34f640967dccf005e6b5e60d78db997afb96/semgrep-1.20.0.tar.gz" + "hash": "2cdaec7455125467ddafd20f99eebe4f2116dc02bf1baacce53c407bb57cbf25", + "url": "https://files.pythonhosted.org/packages/27/ae/8a6fa8c69ddd3b46f8ab82d0c8ba6b480ec373328205fdbcbacdaf323327/semgrep-1.37.0.tar.gz" }, { "algorithm": "sha256", - "hash": "2b543d367acedaf5d3efa07d63b8f2444880255ce35d1106778db68ec559da5d", - "url": "https://files.pythonhosted.org/packages/a6/67/8b96236d72f9a4afd633e25ff9d37a5e54b1d8ceb2ab4c45e93b3d2e0d9e/semgrep-1.20.0-cp37.cp38.cp39.py37.py38.py39-none-macosx_10_14_x86_64.whl" + "hash": "842d385649d91ba69860d5fb96abe470ea15b257b036a9586b1fa8d675764dc3", + "url": "https://files.pythonhosted.org/packages/96/21/72e77e74f9ee42641bccc6de2e64521009c5ee9c5f4e214a3d4681137bce/semgrep-1.37.0-cp37.cp38.cp39.cp310.cp311.py37.py38.py39.py310.py311-none-macosx_10_14_x86_64.whl" }, { "algorithm": "sha256", - "hash": "613fc656ee23771d0e7ad9f093366e6446787e5ef4b84c93e106576eb1ef4cd7", - "url": "https://files.pythonhosted.org/packages/eb/49/58c6f38a458e42c86ae807f0bd5fab00a745e4125f0eef4851164f44ed69/semgrep-1.20.0-cp37.cp38.cp39.py37.py38.py39-none-any.whl" + "hash": "d05fa774ce3ed1d9f17fd69cc16bc6748cbd898592aa2c52f48ccec5af79ff23", + "url": "https://files.pythonhosted.org/packages/e9/c5/0b16ee1791ee6f9b04358b32379d2a5e470c14250360da3816bd10b9f905/semgrep-1.37.0-cp37.cp38.cp39.cp310.cp311.py37.py38.py39.py310.py311-none-macosx_11_0_arm64.whl" } ], "project_name": "semgrep", @@ -1190,7 +1199,7 @@ "wcmatch~=8.3" ], "requires_python": ">=3.7", - "version": "1.20.0" + "version": "1.37.0" }, { "artifacts": [ @@ -1214,19 +1223,19 @@ "artifacts": [ { "algorithm": "sha256", - "hash": "fb33085c39dd998ac16d1431ebc293a8b3eedd00fd4a32de0ff79002c19511b4", - "url": "https://files.pythonhosted.org/packages/31/25/5abcd82372d3d4a3932e1fa8c3dbf9efac10cc7c0d16e78467460571b404/typing_extensions-4.5.0-py3-none-any.whl" + "hash": "440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36", + "url": "https://files.pythonhosted.org/packages/ec/6b/63cc3df74987c36fe26157ee12e09e8f9db4de771e0f3404263117e75b95/typing_extensions-4.7.1-py3-none-any.whl" }, { "algorithm": "sha256", - "hash": "5cb5f4a79139d699607b3ef622a1dedafa84e115ab0024e0d9c044a9479ca7cb", - "url": "https://files.pythonhosted.org/packages/d3/20/06270dac7316220643c32ae61694e451c98f8caf4c8eab3aa80a2bedf0df/typing_extensions-4.5.0.tar.gz" + "hash": "b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2", + "url": "https://files.pythonhosted.org/packages/3c/8b/0111dd7d6c1478bf83baa1cab85c686426c7a6274119aceb2bd9d35395ad/typing_extensions-4.7.1.tar.gz" } ], "project_name": "typing-extensions", "requires_dists": [], "requires_python": ">=3.7", - "version": "4.5.0" + "version": "4.7.1" }, { "artifacts": [ @@ -1500,13 +1509,13 @@ "artifacts": [ { "algorithm": "sha256", - "hash": "aa751d169e23c7479ce47a0cb0da579e3ede798f994f5816a74e4f4500dcea42", - "url": "https://files.pythonhosted.org/packages/7b/f5/890a0baca17a61c1f92f72b81d3c31523c99bec609e60c292ea55b387ae8/urllib3-1.26.15-py2.py3-none-any.whl" + "hash": "8d36afa7616d8ab714608411b4a3b13e58f463aee519024578e062e141dce20f", + "url": "https://files.pythonhosted.org/packages/c5/05/c214b32d21c0b465506f95c4f28ccbcba15022e000b043b72b3df7728471/urllib3-1.26.16-py2.py3-none-any.whl" }, { "algorithm": "sha256", - "hash": "8a388717b9476f934a21484e8c8e61875ab60644d29b9b39e11e4b9dc1c6b305", - "url": "https://files.pythonhosted.org/packages/21/79/6372d8c0d0641b4072889f3ff84f279b738cd8595b64c8e0496d4e848122/urllib3-1.26.15.tar.gz" + "hash": "8f135f6502756bde6b2a9b28989df5fbe87c9970cecaa69041edcce7f0589b14", + "url": "https://files.pythonhosted.org/packages/e2/7d/539e6f0cf9f0b95b71dd701a56dae89f768cd39fd8ce0096af3546aeb5a3/urllib3-1.26.16.tar.gz" } ], "project_name": "urllib3", @@ -1523,7 +1532,7 @@ "urllib3-secure-extra; extra == \"secure\"" ], "requires_python": "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7", - "version": "1.26.15" + "version": "1.26.16" }, { "artifacts": [ @@ -1587,8 +1596,8 @@ } ], "path_mappings": {}, - "pex_version": "2.1.134", - "pip_version": "23.0.1", + "pex_version": "2.1.137", + "pip_version": "23.1.2", "prefer_older_binary": false, "requirements": [ "semgrep<2,>=1.20.0" From 7e7135fa55d4148f87d11731c6fbc7568ce725b0 Mon Sep 17 00:00:00 2001 From: Huon Wilson Date: Mon, 28 Aug 2023 14:12:57 +1000 Subject: [PATCH 49/49] Tweak subsystem --- .../pants/backend/tools/semgrep/subsystem.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/python/pants/backend/tools/semgrep/subsystem.py b/src/python/pants/backend/tools/semgrep/subsystem.py index 4cffaa7e75d..9b03146207f 100644 --- a/src/python/pants/backend/tools/semgrep/subsystem.py +++ b/src/python/pants/backend/tools/semgrep/subsystem.py @@ -30,7 +30,16 @@ def opt_out(cls, tgt: Target) -> bool: class SemgrepSubsystem(PythonToolBase): name = "Semgrep" options_scope = "semgrep" - help = "Lightweight static analysis for many languages. Find bug variants with patterns that look like source code. (https://semgrep.dev/)" + help = softwrap( + """\ + Lightweight static analysis for many languages. Find bug variants with patterns that look + like source code. (https://semgrep.dev/) + + Pants automatically finds config files (`.semgrep.yml`, `.semgrep.yaml`, and `.yml` or + `.yaml` files within `.semgrep/` directories), and runs semgrep against all _targets_ known + to Pants. + """ + ) default_main = ConsoleScript("semgrep") default_requirements = ["semgrep>=1.20.0,<2"] @@ -48,12 +57,6 @@ class SemgrepSubsystem(PythonToolBase): skip = SkipOption("lint") - tailor_rule_targets = BoolOption( - default=True, - help="If true, add `semgrep_rule_sources` targets with the `tailor` goal.", - advanced=True, - ) - force = BoolOption( default=False, help=softwrap(