diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..c6865dd --- /dev/null +++ b/Dockerfile @@ -0,0 +1,73 @@ +# =========================== Build stage =========================== +FROM astral/uv:python3.12-bookworm-slim AS builder +ENV UV_COMPILE_BYTECODE=1 UV_LINK_MODE=copy UV_PYTHON_DOWNLOADS=0 + +WORKDIR /app +COPY pyproject.toml /app/pyproject.toml +COPY uv.lock /app/uv.lock +RUN --mount=type=cache,target=/root/.cache/uv \ + uv sync --locked --no-install-project --no-dev --extra test + +COPY codesectools /app/codesectools +RUN --mount=type=cache,target=/root/.cache/uv \ + uv sync --locked --no-dev --extra test + +# =========================== Base =========================== +FROM python:3.12-slim-bookworm + +ARG UID=1000 +ARG GID=1000 + +SHELL ["/bin/bash", "-c"] + +RUN apt update -qq && \ + DEBIAN_FRONTEND=noninteractive \ + apt install \ + sudo \ + curl git \ + cloc \ + openjdk-17-jdk-headless maven \ + build-essential bear \ + -y -qq --no-install-recommends && \ + rm -rf /var/lib/apt/lists/* + +RUN groupadd -g $GID codesectools && \ + useradd -l -u $UID -g codesectools -m codesectools -s /bin/bash && \ + echo "codesectools ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/codesectools && \ + chmod 0440 /etc/sudoers.d/codesectools + +USER codesectools +WORKDIR /home/codesectools + +RUN curl -LsSf https://astral.sh/uv/install.sh | sh +ENV PATH="/home/codesectools/.local/bin:$PATH" + +# =========================== SAST tools =========================== +RUN uv venv sasts +ENV PATH="/home/codesectools/sasts:$PATH" +ENV PATH="/home/codesectools/sasts/bin:$PATH" + +# Semgrep Community Edition +RUN uv pip install --no-cache semgrep + +# Bearer +RUN curl -sfL https://raw.githubusercontent.com/Bearer/bearer/main/contrib/install.sh | BINDIR=/home/codesectools/sasts sh + +# SpotBugs +RUN curl -sL https://github.com/spotbugs/spotbugs/releases/download/4.9.8/spotbugs-4.9.8.tgz | tar -xzvf - && \ + mv spotbugs-* /home/codesectools/sasts/spotbugs && \ + curl -sL https://search.maven.org/remotecontent?filepath=com/h3xstream/findsecbugs/findsecbugs-plugin/1.14.0/findsecbugs-plugin-1.14.0.jar > /home/codesectools/sasts/spotbugs/plugin/findsecbugs-plugin-1.14.0.jar +ENV PATH="/home/codesectools/sasts/spotbugs/bin:$PATH" + +# Cppcheck +RUN sudo apt update -qq && \ + DEBIAN_FRONTEND=noninteractive sudo apt install cppcheck -y -qq --no-install-recommends && \ + sudo rm -rf /var/lib/apt/lists/* + +# =========================== CodeSecTools =========================== +COPY --from=builder --chown=codesectools:codesectools /app /app +ENV PATH="/app/.venv/bin:$PATH" + +# https://github.com/sarugaku/shellingham/issues/87 +RUN find /app -path "*/shellingham/__init__.py" -exec sed -i 's#raise ShellDetectionFailure()#return ("bash", "/bin/bash")#g' {} \; && \ + cstools --install-completion \ No newline at end of file diff --git a/Makefile b/Makefile index 907ef4d..c03621b 100644 --- a/Makefile +++ b/Makefile @@ -1,3 +1,7 @@ +# Export variables +export UID := $(shell id -u) +export GID := $(shell id -g) + # Add a help target to a Makefile that will allow all targets to be self documenting # https://gist.github.com/prwhite/8168133 all: @@ -17,19 +21,17 @@ profile: ## Run profiling test: ## Run tests in a Docker container @docker compose build 1>/dev/null - @docker compose run --rm no-sast - @docker compose run --rm with-sast + @docker compose run --rm test test-force: ## Run tests in a Docker container while ignoring any stored state @docker volume rm codesectools_pytest-cache 2>&1 1>/dev/null || true @docker volume rm codesectools_cstools-cache 2>&1 1>/dev/null || true @docker compose build 1>/dev/null - @docker compose run --rm no-sast - @docker compose run --rm with-sast + @docker compose run --rm test test-debug: ## Spawn an interactive shell in the test container to debug @docker compose build - @docker compose run --rm with-sast /bin/bash + @docker compose run --rm test /bin/bash docs-serve: ## Serve the documentation locally @mkdocs serve --livereload \ No newline at end of file diff --git a/README.md b/README.md index 5a99dab..b707699 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ A framework for code security that provides abstractions for static analysis too - [SAST Tool Integration Status](#sast-tool-integration-status) - [Usage](#usage) - [Command-line interface](#command-line-interface) + - [Docker](#docker) - [Python API](#python-api) @@ -59,28 +60,58 @@ For more details on the design and integration of SAST tools and datasets in Cod ```bash $ cstools - - Usage: cstools [OPTIONS] COMMAND [ARGS]... - - CodeSecTools: A framework for code security that provides abstractions for static analysis tools and datasets to support their integration, - testing, and evaluation. - -╭─ Options ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ -│ --debug -d Show debugging messages and disable pretty exceptions. │ -│ --version -v Show the tool's version. │ -│ --install-completion Install completion for the current shell. │ -│ --show-completion Show completion for the current shell, to copy it or customize the installation. │ -│ --help Show this message and exit. │ -╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ -╭─ Commands ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ -│ status Display the availability of SAST tools and datasets. │ -│ allsast Run all available SAST tools together. │ -│ bearer Bearer SAST │ -│ coverity Coverity Static Analysis │ -│ semgrepce Semgrep Community Edition Engine │ -│ snykcode Snyk Code │ -│ spotbugs SpotBugs │ -╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ + + Usage: cstools [OPTIONS] COMMAND [ARGS]... + + CodeSecTools CLI. + +╭─ Options ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ +│ --debug -d Show debugging messages and disable pretty exceptions. │ +│ --version -v Show the tool's version. │ +│ --install-completion Install completion for the current shell. │ +│ --show-completion Show completion for the current shell, to copy it or customize the installation. │ +╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ +╭─ Commands ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ +│ status Display the availability of SAST tools and datasets. │ +│ allsast Run all available SAST tools together. │ +│ bearer Bearer SAST │ +│ coverity Coverity Static Analysis │ +│ cppcheck Cppcheck │ +│ semgrepce Semgrep Community Edition Engine │ +│ snykcode Snyk Code │ +│ spotbugs SpotBugs │ +╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ +``` + +#### Docker + +A Docker image is available with only free and offline SAST tools pre-installed. + +```bash +UID=$(id -u) GID=$(id -g) docker compose build main +docker run -it -v $HOME/.codesectools:/home/codesectools/.codesectools codesectools /bin/bash +``` + +Mount necessary directories if you want to include: + +- a target (`-v ./myproject:/home/codesectools/myproject`) +- existing CodeSecTools data (`-v $HOME/.codesectools:/home/codesectools/.codesectools`) + +A better way is to use the CLI: + +```bash +$ cstools -d docker --help + + Usage: cstools docker [OPTIONS] + + Start the Docker environment for the specified target (current directory by default). + +╭─ Options ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ +│ --target PATH The directory to mount inside the container. [default: .] │ +│ --isolation --no-isolation Enable network isolation for the container (disables host network sharing). [default: no-isolation] │ +│ --help Show this message and exit. │ +╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ + ``` #### Python API diff --git a/codesectools/cli.py b/codesectools/cli.py index 34e856f..3126c02 100755 --- a/codesectools/cli.py +++ b/codesectools/cli.py @@ -5,6 +5,7 @@ """ import os +import shutil from pathlib import Path from typing import Optional @@ -201,5 +202,26 @@ def download( cli.add_typer(build_all_sast_cli()) +if shutil.which("docker"): + + @cli.command() + def docker( + target: Annotated[ + Path, typer.Option(help="The directory to mount inside the container.") + ] = Path("."), + isolation: Annotated[ + bool, + typer.Option( + help="Enable network isolation for the container (disables host network sharing)." + ), + ] = False, + ) -> None: + """Start the Docker environment for the specified target (current directory by default).""" + from codesectools.shared.docker import AnalysisEnvironment + + env = AnalysisEnvironment(isolation=isolation) + env.start(target=target.resolve()) + + for _, sast_data in SASTS_ALL.items(): cli.add_typer(sast_data["cli_factory"].build_cli()) diff --git a/codesectools/shared/docker.py b/codesectools/shared/docker.py new file mode 100644 index 0000000..7973de1 --- /dev/null +++ b/codesectools/shared/docker.py @@ -0,0 +1,109 @@ +"""Docker client wrapper module.""" + +import os +import sys +from hashlib import sha256 +from pathlib import Path + +from python_on_whales import DockerClient +from python_on_whales.client_config import ClientNotFoundError + +from codesectools.utils import PACKAGE_DIR, USER_DIR + +UID = os.getuid() +GID = os.getgid() + + +class Docker(DockerClient): + """Wrapper around DockerClient to handle initialization errors.""" + + def __init__(self) -> None: + """Initialize the Docker client and verify availability.""" + try: + super().__init__() + self.info() + except ClientNotFoundError as e: + print(e) + sys.exit(1) + + +class AnalysisEnvironment: + """Manage the Docker environment for code analysis.""" + + build_args = {"UID": str(UID), "GID": str(GID)} + + def __init__(self, isolation: bool) -> None: + """Initialize the analysis environment.""" + self.isolation = isolation + self.docker = Docker() + self.dockerfile = PACKAGE_DIR.parent / "Dockerfile" + self.name = "codesectools" + + def build(self) -> None: + """Build the Docker image if it does not exist or if the file hash changed.""" + file_hash = sha256(self.dockerfile.read_bytes()).hexdigest() + if not self.docker.images( + all=True, filters={"label": f"file_hash={file_hash}"} + ): + self.docker.image.build( + PACKAGE_DIR.parent, + tags=self.name, + labels={"file_hash": file_hash}, + file=self.dockerfile, + build_args=self.build_args, + ) + + def start(self, target: Path) -> None: + """Start the Docker container and attach to it. + + Build the image if necessary, then create and start a container + with the target directory mounted. If a container for the target + already exists, it starts and attaches to it. + + Args: + target: The path to the directory to mount in the container. + + """ + self.build() + + if self.isolation: + target_container_name = f"codesectools-{target.name}-isolated" + else: + target_container_name = f"codesectools-{target.name}" + target_container_home = Path("/home/codesectools") + target_container_workdir = target_container_home / target.name + + if containers := self.docker.ps( + all=True, + filters=[ + ("name", "codesectools-*"), + ("label", f"target={str(target.resolve())}"), + ("label", f"isolation={self.isolation}"), + ], + ): + container = containers[0] + if not container.state.running: + self.docker.start(container) + + container.execute( + ["/bin/bash"], + interactive=True, + tty=True, + ) + else: + container = self.docker.run( + self.name, + name=target_container_name, + command=["/bin/bash"], + labels={ + "target": str(target.resolve()), + "isolation": str(self.isolation), + }, + networks=["none"] if self.isolation else [], + volumes=[ + (target, target_container_workdir), + (USER_DIR, target_container_home / USER_DIR.name), + ], + interactive=True, + tty=True, + ) diff --git a/docker-compose.yml b/docker-compose.yml index 4dd6c5a..caaf6b0 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,38 +1,42 @@ services: - no-sast: - image: cstools_no-sast + main: + image: codesectools build: context: . - dockerfile: tests/Dockerfile - target: no-sast + dockerfile: Dockerfile + + args: + UID: "${UID:-1000}" + GID: "${GID:-1000}" tty: true stdin_open: true - volumes: - - pytest-cache:/app/.pytest_cache - - cstools-cache:/root/.codesectools/cache + working_dir: /home/codesectools - environment: - _TYPER_STANDARD_TRACEBACK: 1 - - with-sast: - image: cstools_with-sast + command: /bin/bash + test: + image: codesectools build: context: . - dockerfile: tests/Dockerfile - target: with-sast + dockerfile: Dockerfile + + args: + UID: "${UID:-1000}" + GID: "${GID:-1000}" tty: true stdin_open: true - volumes: - - pytest-cache:/app/.pytest_cache - - cstools-cache:/root/.codesectools/cache + volumes: + - ./tests:/app/tests:ro + working_dir: /app + environment: _TYPER_STANDARD_TRACEBACK: 1 + + command: pytest volumes: - pytest-cache: cstools-cache: \ No newline at end of file diff --git a/docs/home/quick_start_guide.md b/docs/home/quick_start_guide.md index c5d9a21..f04bb6f 100644 --- a/docs/home/quick_start_guide.md +++ b/docs/home/quick_start_guide.md @@ -8,7 +8,7 @@ This guide mainly used the tool on Java projects, it is perfectly possible to ru For this guide, there are two ways to install the tool: -!!! cube "Normal installation" +!!! cube "Local installation" - You will need to install the following packages: - `git` @@ -23,13 +23,8 @@ For this guide, there are two ways to install the tool: - [SpotBugs](/sast/supported/spotbugs.j2.html){:target="_blank"} !!! docker "Docker image" - A Docker image used to run tests is available with the prerequisites installed. - You can use it to test CodeSecTools without installing extra packages on your system. - - ⚠️ However, this container is intended for running tests, not for normal usage. - Therefore, any data and results inside the container will be deleted when you exit. - - Please perform a normal installation if you want to keep your results. + A Docker image is available with the prerequisites installed. + You can use it to run CodeSecTools without installing extra packages on your system. ## 2. Installation @@ -60,17 +55,26 @@ For this guide, there are two ways to install the tool: ``` !!! docker "Docker image" - To start the Docker container, run the command: + Create a new directory which will be mounted in the docker container and start the container: + ```bash + mkdir codesectools_quick_start_guide + cd codesectools_quick_start_guide + cstools docker + ``` + + Then inside the container: ```bash - make test-debug + cd codesectools_quick_start_guide ``` - ⚠️ The container will be deleted when you exit. + Only data inside `./codesectools_quick_start_guide` are saved. ## 3. First run !!! abstract "Install completion (optional)" + *Completion is already installed in the Docker container.* + ```bash cstools --install-completion # For bash diff --git a/pyproject.toml b/pyproject.toml index 80cf002..395a5db 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "CodeSecTools" -version = "0.13.8" +version = "0.14.0" description = "A framework for code security that provides abstractions for static analysis tools and datasets to support their integration, testing, and evaluation." readme = "README.md" license = "AGPL-3.0-only" @@ -15,6 +15,7 @@ dependencies = [ "lxml>=6.0.2", "matplotlib>=3.10.3", "numpy>=2.3.1", + "python-on-whales>=0.79.0", "pyyaml>=6.0.2", "requests>=2.32.4", "tqdm>=4.67.1", diff --git a/requirements.txt b/requirements.txt index 640f4cd..9223e34 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,10 @@ # This file was autogenerated by uv via the following command: # uv export --frozen --output-file=requirements.txt -e . +annotated-types==0.7.0 \ + --hash=sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53 \ + --hash=sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89 + # via pydantic certifi==2025.10.5 \ --hash=sha256:0f212c2744a9bb6de0c56639a6f68afe01ecd92d91f14ae897c4fe7bbeeef0de \ --hash=sha256:47c09d31ccf2acf0be3f701ea53595ee7e0b8fa08801c6624be771df09ae7b43 @@ -495,6 +499,73 @@ pillow==11.3.0 \ --hash=sha256:f944255db153ebb2b19c51fe85dd99ef0ce494123f21b9db4877ffdfc5590c7c \ --hash=sha256:fdae223722da47b024b867c1ea0be64e0df702c5e0a60e27daad39bf960dd1e4 # via matplotlib +pydantic==2.12.5 \ + --hash=sha256:4d351024c75c0f085a9febbb665ce8c0c6ec5d30e903bdb6394b7ede26aebb49 \ + --hash=sha256:e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d + # via python-on-whales +pydantic-core==2.41.5 \ + --hash=sha256:0177272f88ab8312479336e1d777f6b124537d47f2123f89cb37e0accea97f90 \ + --hash=sha256:01a3d0ab748ee531f4ea6c3e48ad9dac84ddba4b0d82291f87248f2f9de8d740 \ + --hash=sha256:03ca43e12fab6023fc79d28ca6b39b05f794ad08ec2feccc59a339b02f2b3d33 \ + --hash=sha256:070259a8818988b9a84a449a2a7337c7f430a22acc0859c6b110aa7212a6d9c0 \ + --hash=sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e \ + --hash=sha256:0cbaad15cb0c90aa221d43c00e77bb33c93e8d36e0bf74760cd00e732d10a6a0 \ + --hash=sha256:112e305c3314f40c93998e567879e887a3160bb8689ef3d2c04b6cc62c33ac34 \ + --hash=sha256:1746d4a3d9a794cacae06a5eaaccb4b8643a131d45fbc9af23e353dc0a5ba5c3 \ + --hash=sha256:1962293292865bca8e54702b08a4f26da73adc83dd1fcf26fbc875b35d81c815 \ + --hash=sha256:1d1d9764366c73f996edd17abb6d9d7649a7eb690006ab6adbda117717099b14 \ + --hash=sha256:22f0fb8c1c583a3b6f24df2470833b40207e907b90c928cc8d3594b76f874375 \ + --hash=sha256:239edca560d05757817c13dc17c50766136d21f7cd0fac50295499ae24f90fdf \ + --hash=sha256:25e1c2af0fce638d5f1988b686f3b3ea8cd7de5f244ca147c777769e798a9cd1 \ + --hash=sha256:2782c870e99878c634505236d81e5443092fba820f0373997ff75f90f68cd553 \ + --hash=sha256:2a5e06546e19f24c6a96a129142a75cee553cc018ffee48a460059b1185f4470 \ + --hash=sha256:2b761d210c9ea91feda40d25b4efe82a1707da2ef62901466a42492c028553a2 \ + --hash=sha256:2ff4321e56e879ee8d2a879501c8e469414d948f4aba74a2d4593184eb326660 \ + --hash=sha256:3006c3dd9ba34b0c094c544c6006cc79e87d8612999f1a5d43b769b89181f23c \ + --hash=sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008 \ + --hash=sha256:3f37a19d7ebcdd20b96485056ba9e8b304e27d9904d233d7b1015db320e51f0a \ + --hash=sha256:3f84d5c1b4ab906093bdc1ff10484838aca54ef08de4afa9de0f5f14d69639cd \ + --hash=sha256:406bf18d345822d6c21366031003612b9c77b3e29ffdb0f612367352aab7d586 \ + --hash=sha256:4819fa52133c9aa3c387b3328f25c1facc356491e6135b459f1de698ff64d869 \ + --hash=sha256:482c982f814460eabe1d3bb0adfdc583387bd4691ef00b90575ca0d2b6fe2294 \ + --hash=sha256:506d766a8727beef16b7adaeb8ee6217c64fc813646b424d0804d67c16eddb66 \ + --hash=sha256:5cb1b2f9742240e4bb26b652a5aeb840aa4b417c7748b6f8387927bc6e45e40d \ + --hash=sha256:63510af5e38f8955b8ee5687740d6ebf7c2a0886d15a6d65c32814613681bc07 \ + --hash=sha256:650ae77860b45cfa6e2cdafc42618ceafab3a2d9a3811fcfbd3bbf8ac3c40d36 \ + --hash=sha256:6561e94ba9dacc9c61bce40e2d6bdc3bfaa0259d3ff36ace3b1e6901936d2e3e \ + --hash=sha256:65840751b72fbfd82c3c640cff9284545342a4f1eb1586ad0636955b261b0b05 \ + --hash=sha256:72f6c8b11857a856bcfa48c86f5368439f74453563f951e473514579d44aa612 \ + --hash=sha256:76d0819de158cd855d1cbb8fcafdf6f5cf1eb8e470abe056d5d161106e38062b \ + --hash=sha256:79ec52ec461e99e13791ec6508c722742ad745571f234ea6255bed38c6480f11 \ + --hash=sha256:7da7087d756b19037bc2c06edc6c170eeef3c3bafcb8f532ff17d64dc427adfd \ + --hash=sha256:80aa89cad80b32a912a65332f64a4450ed00966111b6615ca6816153d3585a8c \ + --hash=sha256:8566def80554c3faa0e65ac30ab0932b9e3a5cd7f8323764303d468e5c37595a \ + --hash=sha256:8e7c86f27c585ef37c35e56a96363ab8de4e549a95512445b85c96d3e2f7c1bf \ + --hash=sha256:915c3d10f81bec3a74fbd4faebe8391013ba61e5a1a8d48c4455b923bdda7858 \ + --hash=sha256:941103c9be18ac8daf7b7adca8228f8ed6bb7a1849020f643b3a14d15b1924d9 \ + --hash=sha256:97aeba56665b4c3235a0e52b2c2f5ae9cd071b8a8310ad27bddb3f7fb30e9aa2 \ + --hash=sha256:a39455728aabd58ceabb03c90e12f71fd30fa69615760a075b9fec596456ccc3 \ + --hash=sha256:aabf5777b5c8ca26f7824cb4a120a740c9588ed58df9b2d196ce92fba42ff8dc \ + --hash=sha256:aec5cf2fd867b4ff45b9959f8b20ea3993fc93e63c7363fe6851424c8a7e7c23 \ + --hash=sha256:b4ececa40ac28afa90871c2cc2b9ffd2ff0bf749380fbdf57d165fd23da353aa \ + --hash=sha256:b74557b16e390ec12dca509bce9264c3bbd128f8a2c376eaa68003d7f327276d \ + --hash=sha256:b80aa5095cd3109962a298ce14110ae16b8c1aece8b72f9dafe81cf597ad80b3 \ + --hash=sha256:b93590ae81f7010dbe380cdeab6f515902ebcbefe0b9327cc4804d74e93ae69d \ + --hash=sha256:bd3d54f38609ff308209bd43acea66061494157703364ae40c951f83ba99a1a9 \ + --hash=sha256:bfea2a5f0b4d8d43adf9d7b8bf019fb46fdd10a2e5cde477fbcb9d1fa08c68e1 \ + --hash=sha256:c007fe8a43d43b3969e8469004e9845944f1a80e6acd47c150856bb87f230c56 \ + --hash=sha256:c23e27686783f60290e36827f9c626e63154b82b116d7fe9adba1fda36da706c \ + --hash=sha256:d0d2568a8c11bf8225044aa94409e21da0cb09dcdafe9ecd10250b2baad531a9 \ + --hash=sha256:d38548150c39b74aeeb0ce8ee1d8e82696f4a4e16ddc6de7b1d8823f7de4b9b5 \ + --hash=sha256:dc799088c08fa04e43144b164feb0c13f9a0bc40503f8df3e9fde58a3c0c101e \ + --hash=sha256:e536c98a7626a98feb2d3eaf75944ef6f3dbee447e1f841eae16f2f0a72d8ddc \ + --hash=sha256:e56ba91f47764cc14f1daacd723e3e82d1a89d783f0f5afe9c364b8bb491ccdb \ + --hash=sha256:e672ba74fbc2dc8eea59fb6d4aed6845e6905fc2a8afe93175d94a83ba2a01a0 \ + --hash=sha256:e96cea19e34778f8d59fe40775a7a574d95816eb150850a85a7a4c8f4b94ac69 \ + --hash=sha256:eceb81a8d74f9267ef4081e246ffd6d129da5d87e37a77c9bde550cb04870c1c \ + --hash=sha256:ed2e99c456e3fadd05c991f8f437ef902e00eedf34320ba2b0842bd1c3ca3a75 \ + --hash=sha256:f41a7489d32336dbf2199c8c0a215390a751c5b014c2c1c5366e817202e9cdf7 + # via pydantic pygments==2.19.2 \ --hash=sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887 \ --hash=sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b @@ -507,6 +578,10 @@ python-dateutil==2.9.0.post0 \ --hash=sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3 \ --hash=sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427 # via matplotlib +python-on-whales==0.79.0 \ + --hash=sha256:4a39ab107a4e740e4302c853c0efc5ced4b4048f55acbb959a2faf53b7003c27 \ + --hash=sha256:919bba304cc04db4b75cdd7fb14c0a8fea6ebeafacf7989f6956641cb58210e0 + # via codesectools pyyaml==6.0.3 \ --hash=sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c \ --hash=sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3 \ @@ -579,7 +654,16 @@ typer==0.20.0 \ typing-extensions==4.15.0 \ --hash=sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466 \ --hash=sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548 - # via typer + # via + # pydantic + # pydantic-core + # python-on-whales + # typer + # typing-inspection +typing-inspection==0.4.2 \ + --hash=sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7 \ + --hash=sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464 + # via pydantic urllib3==2.5.0 \ --hash=sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760 \ --hash=sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc diff --git a/tests/Dockerfile b/tests/Dockerfile deleted file mode 100644 index c223b6a..0000000 --- a/tests/Dockerfile +++ /dev/null @@ -1,66 +0,0 @@ -# =========================== Build stage =========================== -FROM astral/uv:python3.12-bookworm-slim AS builder -ENV UV_COMPILE_BYTECODE=1 UV_LINK_MODE=copy UV_PYTHON_DOWNLOADS=0 - -WORKDIR /app -COPY pyproject.toml /app/pyproject.toml -COPY uv.lock /app/uv.lock -RUN --mount=type=cache,target=/root/.cache/uv \ - uv sync --locked --no-install-project --no-dev --extra test - -COPY codesectools /app/codesectools -RUN --mount=type=cache,target=/root/.cache/uv \ - uv sync --locked --no-dev --extra test - -# =========================== Base =========================== -FROM python:3.12-slim-bookworm AS test-base - -RUN apt update -qq && \ - DEBIAN_FRONTEND=noninteractive \ - apt install \ - curl git \ - cloc \ - openjdk-17-jdk-headless maven \ - build-essential bear \ - -y -qq --no-install-recommends && \ - rm -rf /var/lib/apt/lists/* - -# =========================== No SAST Tools =========================== -FROM test-base AS no-sast -ENV TEST_TYPE=no-sast - -# === Run tests === -COPY --from=builder --chown=app:app /app /app -ENV PATH="/app/.venv/bin:$PATH" - -WORKDIR /app -COPY tests /app/tests - -CMD ["pytest"] - -# =========================== With SAST Tools =========================== -FROM test-base AS with-sast -ENV TEST_TYPE=with-sast - -# === Free SAST tools only === -# Semgrep Community Edition -RUN pip install --no-cache semgrep -# Bearer -RUN curl -sfL https://raw.githubusercontent.com/Bearer/bearer/main/contrib/install.sh | BINDIR=/usr/bin sh -# SpotBugs -RUN curl -sL https://github.com/spotbugs/spotbugs/releases/download/4.9.8/spotbugs-4.9.8.tgz | tar -xzvf - && \ - mv spotbugs-* /tmp/spotbugs -ENV PATH="/tmp/spotbugs/bin:$PATH" -# Cppcheck -RUN apt update -qq && \ - DEBIAN_FRONTEND=noninteractive apt install cppcheck -y -qq --no-install-recommends && \ - rm -rf /var/lib/apt/lists/* - -# === Run tests === -COPY --from=builder --chown=app:app /app /app -ENV PATH="/app/.venv/bin:$PATH" - -WORKDIR /app -COPY tests /app/tests - -CMD ["pytest"] \ No newline at end of file diff --git a/tests/conftest.py b/tests/conftest.py index 88c21c6..3fa7c68 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,7 +1,5 @@ """Defines shared fixtures and hooks for pytest.""" -import hashlib -import json import logging import os import shutil @@ -10,63 +8,13 @@ import pytest -from codesectools.utils import USER_CACHE_DIR - # Fix: I/O operation on closed (https://github.com/pallets/click/issues/824) logging.getLogger("matplotlib").setLevel(logging.ERROR) -test_type = os.environ.get("TEST_TYPE") -state_file = Path(f".pytest_cache/state_{test_type}.json") - - -def gen_state() -> dict[str, str]: - """Generate a state dictionary of source file paths and their SHA256 hashes. - - Monitors .py files in 'codesectools' and 'tests' directories. - """ - state = {} - for directory in ["codesectools", "tests"]: - for code_path in Path(directory).rglob("*.py"): - path = str(code_path) - file_hash = hashlib.sha256(code_path.read_bytes()).hexdigest() - state[path] = file_hash - - return state - - -def source_code_changed() -> bool: - """Check if monitored source code has changed since the last successful test run. - - Compares the current state with a saved state in '.pytest_cache/state.json'. - """ - if not state_file.is_file(): - return True - - with state_file.open("r") as f: - try: - old_state = json.load(f) - except json.JSONDecodeError: - return True - - new_state = gen_state() - - return new_state != old_state - def pytest_sessionstart(session: pytest.Session) -> None: - """Pytest hook that runs at the beginning of a test session. - - Skips the entire test session if no source files have changed. - """ - if USER_CACHE_DIR.is_dir(): - for child in USER_CACHE_DIR.iterdir(): - if child.is_file(): - child.unlink() - elif child.is_dir(): - shutil.rmtree(child) - - if not source_code_changed(): - pytest.exit("No changes in source code, skipping test session.", returncode=0) + """Initialize the test session by copying test codes to a temporary directory.""" + shutil.copytree(Path("tests/testcodes"), Path("/tmp/tests/testcodes")) @pytest.fixture(autouse=True, scope="session") @@ -74,15 +22,3 @@ def constant_random() -> GeneratorType: """Set a constant random seed for reproducible tests.""" os.environ["CONSTANT_RANDOM"] = os.urandom(16).hex() yield - - -def pytest_sessionfinish(session: pytest.Session) -> None: - """Pytest hook that runs at the end of a test session. - - Saves the current source code state if the test session was successful. - """ - if session.testscollected > 0 and session.testsfailed == 0: - new_state = gen_state() - state_file.parent.mkdir(exist_ok=True, parents=True) - with state_file.open("w") as f: - json.dump(new_state, f, indent=2) diff --git a/tests/test_all_sasts.py b/tests/test_all_sasts.py index 70945c7..a816188 100644 --- a/tests/test_all_sasts.py +++ b/tests/test_all_sasts.py @@ -1,7 +1,6 @@ """Test the 'allsast' command integration.""" import logging -import os from pathlib import Path from types import GeneratorType @@ -14,11 +13,6 @@ from codesectools.sasts.all.sast import AllSAST from codesectools.utils import run_command -if os.environ.get("TEST_TYPE") == "no-sast": - pytest.skip( - "Skipping SAST tools testing in no-sast environment", allow_module_level=True - ) - all_sast = AllSAST() diff --git a/tests/test_sasts.py b/tests/test_sasts.py index 7630b63..a127c8a 100644 --- a/tests/test_sasts.py +++ b/tests/test_sasts.py @@ -1,7 +1,6 @@ """Test SAST integration functionalities.""" import logging -import os import tempfile from pathlib import Path from types import GeneratorType @@ -19,11 +18,6 @@ from codesectools.sasts import SASTS_ALL from codesectools.utils import run_command -if os.environ.get("TEST_TYPE") == "no-sast": - pytest.skip( - "Skipping SAST tools testing in no-sast environment", allow_module_level=True - ) - @pytest.fixture(autouse=True, scope="module") def update_sast_module_state() -> GeneratorType: @@ -44,7 +38,7 @@ def update_sast_module_state() -> GeneratorType: runner = CliRunner(env={"COLUMNS": "200"}) -TEST_CODES_DIR = Path("tests/testcodes").resolve() +TEST_CODES_DIR = Path("/tmp/tests/testcodes").resolve() TEST_CODES = { "java": {"build_command": "javac {filename}"}, "c": {"build_command": "bear -- g++ {filename}"}, diff --git a/uv.lock b/uv.lock index 908262a..8890b0c 100644 --- a/uv.lock +++ b/uv.lock @@ -2,6 +2,15 @@ version = 1 revision = 3 requires-python = ">=3.12" +[[package]] +name = "annotated-types" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, +] + [[package]] name = "babel" version = "2.17.0" @@ -221,7 +230,7 @@ wheels = [ [[package]] name = "codesectools" -version = "0.13.8" +version = "0.14.0" source = { editable = "." } dependencies = [ { name = "gitpython" }, @@ -229,6 +238,7 @@ dependencies = [ { name = "lxml" }, { name = "matplotlib" }, { name = "numpy" }, + { name = "python-on-whales" }, { name = "pyyaml" }, { name = "requests" }, { name = "tqdm" }, @@ -284,6 +294,7 @@ requires-dist = [ { name = "pre-commit", marker = "extra == 'dev'", specifier = ">=4.3.0" }, { name = "pytest", marker = "extra == 'test'", specifier = ">=8.4.1" }, { name = "pytest-order", marker = "extra == 'test'", specifier = ">=1.3.0" }, + { name = "python-on-whales", specifier = ">=0.79.0" }, { name = "pyyaml", specifier = ">=6.0.2" }, { name = "pyyaml", marker = "extra == 'docs'", specifier = ">=6.0.2" }, { name = "requests", specifier = ">=2.32.4" }, @@ -1323,6 +1334,92 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934", size = 118140, upload-time = "2025-09-09T13:23:46.651Z" }, ] +[[package]] +name = "pydantic" +version = "2.12.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/69/44/36f1a6e523abc58ae5f928898e4aca2e0ea509b5aa6f6f392a5d882be928/pydantic-2.12.5.tar.gz", hash = "sha256:4d351024c75c0f085a9febbb665ce8c0c6ec5d30e903bdb6394b7ede26aebb49", size = 821591, upload-time = "2025-11-26T15:11:46.471Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl", hash = "sha256:e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d", size = 463580, upload-time = "2025-11-26T15:11:44.605Z" }, +] + +[[package]] +name = "pydantic-core" +version = "2.41.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/71/70/23b021c950c2addd24ec408e9ab05d59b035b39d97cdc1130e1bce647bb6/pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e", size = 460952, upload-time = "2025-11-04T13:43:49.098Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5f/5d/5f6c63eebb5afee93bcaae4ce9a898f3373ca23df3ccaef086d0233a35a7/pydantic_core-2.41.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f41a7489d32336dbf2199c8c0a215390a751c5b014c2c1c5366e817202e9cdf7", size = 2110990, upload-time = "2025-11-04T13:39:58.079Z" }, + { url = "https://files.pythonhosted.org/packages/aa/32/9c2e8ccb57c01111e0fd091f236c7b371c1bccea0fa85247ac55b1e2b6b6/pydantic_core-2.41.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:070259a8818988b9a84a449a2a7337c7f430a22acc0859c6b110aa7212a6d9c0", size = 1896003, upload-time = "2025-11-04T13:39:59.956Z" }, + { url = "https://files.pythonhosted.org/packages/68/b8/a01b53cb0e59139fbc9e4fda3e9724ede8de279097179be4ff31f1abb65a/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e96cea19e34778f8d59fe40775a7a574d95816eb150850a85a7a4c8f4b94ac69", size = 1919200, upload-time = "2025-11-04T13:40:02.241Z" }, + { url = "https://files.pythonhosted.org/packages/38/de/8c36b5198a29bdaade07b5985e80a233a5ac27137846f3bc2d3b40a47360/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed2e99c456e3fadd05c991f8f437ef902e00eedf34320ba2b0842bd1c3ca3a75", size = 2052578, upload-time = "2025-11-04T13:40:04.401Z" }, + { url = "https://files.pythonhosted.org/packages/00/b5/0e8e4b5b081eac6cb3dbb7e60a65907549a1ce035a724368c330112adfdd/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65840751b72fbfd82c3c640cff9284545342a4f1eb1586ad0636955b261b0b05", size = 2208504, upload-time = "2025-11-04T13:40:06.072Z" }, + { url = "https://files.pythonhosted.org/packages/77/56/87a61aad59c7c5b9dc8caad5a41a5545cba3810c3e828708b3d7404f6cef/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e536c98a7626a98feb2d3eaf75944ef6f3dbee447e1f841eae16f2f0a72d8ddc", size = 2335816, upload-time = "2025-11-04T13:40:07.835Z" }, + { url = "https://files.pythonhosted.org/packages/0d/76/941cc9f73529988688a665a5c0ecff1112b3d95ab48f81db5f7606f522d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eceb81a8d74f9267ef4081e246ffd6d129da5d87e37a77c9bde550cb04870c1c", size = 2075366, upload-time = "2025-11-04T13:40:09.804Z" }, + { url = "https://files.pythonhosted.org/packages/d3/43/ebef01f69baa07a482844faaa0a591bad1ef129253ffd0cdaa9d8a7f72d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d38548150c39b74aeeb0ce8ee1d8e82696f4a4e16ddc6de7b1d8823f7de4b9b5", size = 2171698, upload-time = "2025-11-04T13:40:12.004Z" }, + { url = "https://files.pythonhosted.org/packages/b1/87/41f3202e4193e3bacfc2c065fab7706ebe81af46a83d3e27605029c1f5a6/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c23e27686783f60290e36827f9c626e63154b82b116d7fe9adba1fda36da706c", size = 2132603, upload-time = "2025-11-04T13:40:13.868Z" }, + { url = "https://files.pythonhosted.org/packages/49/7d/4c00df99cb12070b6bccdef4a195255e6020a550d572768d92cc54dba91a/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:482c982f814460eabe1d3bb0adfdc583387bd4691ef00b90575ca0d2b6fe2294", size = 2329591, upload-time = "2025-11-04T13:40:15.672Z" }, + { url = "https://files.pythonhosted.org/packages/cc/6a/ebf4b1d65d458f3cda6a7335d141305dfa19bdc61140a884d165a8a1bbc7/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:bfea2a5f0b4d8d43adf9d7b8bf019fb46fdd10a2e5cde477fbcb9d1fa08c68e1", size = 2319068, upload-time = "2025-11-04T13:40:17.532Z" }, + { url = "https://files.pythonhosted.org/packages/49/3b/774f2b5cd4192d5ab75870ce4381fd89cf218af999515baf07e7206753f0/pydantic_core-2.41.5-cp312-cp312-win32.whl", hash = "sha256:b74557b16e390ec12dca509bce9264c3bbd128f8a2c376eaa68003d7f327276d", size = 1985908, upload-time = "2025-11-04T13:40:19.309Z" }, + { url = "https://files.pythonhosted.org/packages/86/45/00173a033c801cacf67c190fef088789394feaf88a98a7035b0e40d53dc9/pydantic_core-2.41.5-cp312-cp312-win_amd64.whl", hash = "sha256:1962293292865bca8e54702b08a4f26da73adc83dd1fcf26fbc875b35d81c815", size = 2020145, upload-time = "2025-11-04T13:40:21.548Z" }, + { url = "https://files.pythonhosted.org/packages/f9/22/91fbc821fa6d261b376a3f73809f907cec5ca6025642c463d3488aad22fb/pydantic_core-2.41.5-cp312-cp312-win_arm64.whl", hash = "sha256:1746d4a3d9a794cacae06a5eaaccb4b8643a131d45fbc9af23e353dc0a5ba5c3", size = 1976179, upload-time = "2025-11-04T13:40:23.393Z" }, + { url = "https://files.pythonhosted.org/packages/87/06/8806241ff1f70d9939f9af039c6c35f2360cf16e93c2ca76f184e76b1564/pydantic_core-2.41.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:941103c9be18ac8daf7b7adca8228f8ed6bb7a1849020f643b3a14d15b1924d9", size = 2120403, upload-time = "2025-11-04T13:40:25.248Z" }, + { url = "https://files.pythonhosted.org/packages/94/02/abfa0e0bda67faa65fef1c84971c7e45928e108fe24333c81f3bfe35d5f5/pydantic_core-2.41.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:112e305c3314f40c93998e567879e887a3160bb8689ef3d2c04b6cc62c33ac34", size = 1896206, upload-time = "2025-11-04T13:40:27.099Z" }, + { url = "https://files.pythonhosted.org/packages/15/df/a4c740c0943e93e6500f9eb23f4ca7ec9bf71b19e608ae5b579678c8d02f/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cbaad15cb0c90aa221d43c00e77bb33c93e8d36e0bf74760cd00e732d10a6a0", size = 1919307, upload-time = "2025-11-04T13:40:29.806Z" }, + { url = "https://files.pythonhosted.org/packages/9a/e3/6324802931ae1d123528988e0e86587c2072ac2e5394b4bc2bc34b61ff6e/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:03ca43e12fab6023fc79d28ca6b39b05f794ad08ec2feccc59a339b02f2b3d33", size = 2063258, upload-time = "2025-11-04T13:40:33.544Z" }, + { url = "https://files.pythonhosted.org/packages/c9/d4/2230d7151d4957dd79c3044ea26346c148c98fbf0ee6ebd41056f2d62ab5/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc799088c08fa04e43144b164feb0c13f9a0bc40503f8df3e9fde58a3c0c101e", size = 2214917, upload-time = "2025-11-04T13:40:35.479Z" }, + { url = "https://files.pythonhosted.org/packages/e6/9f/eaac5df17a3672fef0081b6c1bb0b82b33ee89aa5cec0d7b05f52fd4a1fa/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:97aeba56665b4c3235a0e52b2c2f5ae9cd071b8a8310ad27bddb3f7fb30e9aa2", size = 2332186, upload-time = "2025-11-04T13:40:37.436Z" }, + { url = "https://files.pythonhosted.org/packages/cf/4e/35a80cae583a37cf15604b44240e45c05e04e86f9cfd766623149297e971/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:406bf18d345822d6c21366031003612b9c77b3e29ffdb0f612367352aab7d586", size = 2073164, upload-time = "2025-11-04T13:40:40.289Z" }, + { url = "https://files.pythonhosted.org/packages/bf/e3/f6e262673c6140dd3305d144d032f7bd5f7497d3871c1428521f19f9efa2/pydantic_core-2.41.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b93590ae81f7010dbe380cdeab6f515902ebcbefe0b9327cc4804d74e93ae69d", size = 2179146, upload-time = "2025-11-04T13:40:42.809Z" }, + { url = "https://files.pythonhosted.org/packages/75/c7/20bd7fc05f0c6ea2056a4565c6f36f8968c0924f19b7d97bbfea55780e73/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:01a3d0ab748ee531f4ea6c3e48ad9dac84ddba4b0d82291f87248f2f9de8d740", size = 2137788, upload-time = "2025-11-04T13:40:44.752Z" }, + { url = "https://files.pythonhosted.org/packages/3a/8d/34318ef985c45196e004bc46c6eab2eda437e744c124ef0dbe1ff2c9d06b/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:6561e94ba9dacc9c61bce40e2d6bdc3bfaa0259d3ff36ace3b1e6901936d2e3e", size = 2340133, upload-time = "2025-11-04T13:40:46.66Z" }, + { url = "https://files.pythonhosted.org/packages/9c/59/013626bf8c78a5a5d9350d12e7697d3d4de951a75565496abd40ccd46bee/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:915c3d10f81bec3a74fbd4faebe8391013ba61e5a1a8d48c4455b923bdda7858", size = 2324852, upload-time = "2025-11-04T13:40:48.575Z" }, + { url = "https://files.pythonhosted.org/packages/1a/d9/c248c103856f807ef70c18a4f986693a46a8ffe1602e5d361485da502d20/pydantic_core-2.41.5-cp313-cp313-win32.whl", hash = "sha256:650ae77860b45cfa6e2cdafc42618ceafab3a2d9a3811fcfbd3bbf8ac3c40d36", size = 1994679, upload-time = "2025-11-04T13:40:50.619Z" }, + { url = "https://files.pythonhosted.org/packages/9e/8b/341991b158ddab181cff136acd2552c9f35bd30380422a639c0671e99a91/pydantic_core-2.41.5-cp313-cp313-win_amd64.whl", hash = "sha256:79ec52ec461e99e13791ec6508c722742ad745571f234ea6255bed38c6480f11", size = 2019766, upload-time = "2025-11-04T13:40:52.631Z" }, + { url = "https://files.pythonhosted.org/packages/73/7d/f2f9db34af103bea3e09735bb40b021788a5e834c81eedb541991badf8f5/pydantic_core-2.41.5-cp313-cp313-win_arm64.whl", hash = "sha256:3f84d5c1b4ab906093bdc1ff10484838aca54ef08de4afa9de0f5f14d69639cd", size = 1981005, upload-time = "2025-11-04T13:40:54.734Z" }, + { url = "https://files.pythonhosted.org/packages/ea/28/46b7c5c9635ae96ea0fbb779e271a38129df2550f763937659ee6c5dbc65/pydantic_core-2.41.5-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:3f37a19d7ebcdd20b96485056ba9e8b304e27d9904d233d7b1015db320e51f0a", size = 2119622, upload-time = "2025-11-04T13:40:56.68Z" }, + { url = "https://files.pythonhosted.org/packages/74/1a/145646e5687e8d9a1e8d09acb278c8535ebe9e972e1f162ed338a622f193/pydantic_core-2.41.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1d1d9764366c73f996edd17abb6d9d7649a7eb690006ab6adbda117717099b14", size = 1891725, upload-time = "2025-11-04T13:40:58.807Z" }, + { url = "https://files.pythonhosted.org/packages/23/04/e89c29e267b8060b40dca97bfc64a19b2a3cf99018167ea1677d96368273/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25e1c2af0fce638d5f1988b686f3b3ea8cd7de5f244ca147c777769e798a9cd1", size = 1915040, upload-time = "2025-11-04T13:41:00.853Z" }, + { url = "https://files.pythonhosted.org/packages/84/a3/15a82ac7bd97992a82257f777b3583d3e84bdb06ba6858f745daa2ec8a85/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:506d766a8727beef16b7adaeb8ee6217c64fc813646b424d0804d67c16eddb66", size = 2063691, upload-time = "2025-11-04T13:41:03.504Z" }, + { url = "https://files.pythonhosted.org/packages/74/9b/0046701313c6ef08c0c1cf0e028c67c770a4e1275ca73131563c5f2a310a/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4819fa52133c9aa3c387b3328f25c1facc356491e6135b459f1de698ff64d869", size = 2213897, upload-time = "2025-11-04T13:41:05.804Z" }, + { url = "https://files.pythonhosted.org/packages/8a/cd/6bac76ecd1b27e75a95ca3a9a559c643b3afcd2dd62086d4b7a32a18b169/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b761d210c9ea91feda40d25b4efe82a1707da2ef62901466a42492c028553a2", size = 2333302, upload-time = "2025-11-04T13:41:07.809Z" }, + { url = "https://files.pythonhosted.org/packages/4c/d2/ef2074dc020dd6e109611a8be4449b98cd25e1b9b8a303c2f0fca2f2bcf7/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22f0fb8c1c583a3b6f24df2470833b40207e907b90c928cc8d3594b76f874375", size = 2064877, upload-time = "2025-11-04T13:41:09.827Z" }, + { url = "https://files.pythonhosted.org/packages/18/66/e9db17a9a763d72f03de903883c057b2592c09509ccfe468187f2a2eef29/pydantic_core-2.41.5-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2782c870e99878c634505236d81e5443092fba820f0373997ff75f90f68cd553", size = 2180680, upload-time = "2025-11-04T13:41:12.379Z" }, + { url = "https://files.pythonhosted.org/packages/d3/9e/3ce66cebb929f3ced22be85d4c2399b8e85b622db77dad36b73c5387f8f8/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:0177272f88ab8312479336e1d777f6b124537d47f2123f89cb37e0accea97f90", size = 2138960, upload-time = "2025-11-04T13:41:14.627Z" }, + { url = "https://files.pythonhosted.org/packages/a6/62/205a998f4327d2079326b01abee48e502ea739d174f0a89295c481a2272e/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:63510af5e38f8955b8ee5687740d6ebf7c2a0886d15a6d65c32814613681bc07", size = 2339102, upload-time = "2025-11-04T13:41:16.868Z" }, + { url = "https://files.pythonhosted.org/packages/3c/0d/f05e79471e889d74d3d88f5bd20d0ed189ad94c2423d81ff8d0000aab4ff/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:e56ba91f47764cc14f1daacd723e3e82d1a89d783f0f5afe9c364b8bb491ccdb", size = 2326039, upload-time = "2025-11-04T13:41:18.934Z" }, + { url = "https://files.pythonhosted.org/packages/ec/e1/e08a6208bb100da7e0c4b288eed624a703f4d129bde2da475721a80cab32/pydantic_core-2.41.5-cp314-cp314-win32.whl", hash = "sha256:aec5cf2fd867b4ff45b9959f8b20ea3993fc93e63c7363fe6851424c8a7e7c23", size = 1995126, upload-time = "2025-11-04T13:41:21.418Z" }, + { url = "https://files.pythonhosted.org/packages/48/5d/56ba7b24e9557f99c9237e29f5c09913c81eeb2f3217e40e922353668092/pydantic_core-2.41.5-cp314-cp314-win_amd64.whl", hash = "sha256:8e7c86f27c585ef37c35e56a96363ab8de4e549a95512445b85c96d3e2f7c1bf", size = 2015489, upload-time = "2025-11-04T13:41:24.076Z" }, + { url = "https://files.pythonhosted.org/packages/4e/bb/f7a190991ec9e3e0ba22e4993d8755bbc4a32925c0b5b42775c03e8148f9/pydantic_core-2.41.5-cp314-cp314-win_arm64.whl", hash = "sha256:e672ba74fbc2dc8eea59fb6d4aed6845e6905fc2a8afe93175d94a83ba2a01a0", size = 1977288, upload-time = "2025-11-04T13:41:26.33Z" }, + { url = "https://files.pythonhosted.org/packages/92/ed/77542d0c51538e32e15afe7899d79efce4b81eee631d99850edc2f5e9349/pydantic_core-2.41.5-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8566def80554c3faa0e65ac30ab0932b9e3a5cd7f8323764303d468e5c37595a", size = 2120255, upload-time = "2025-11-04T13:41:28.569Z" }, + { url = "https://files.pythonhosted.org/packages/bb/3d/6913dde84d5be21e284439676168b28d8bbba5600d838b9dca99de0fad71/pydantic_core-2.41.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b80aa5095cd3109962a298ce14110ae16b8c1aece8b72f9dafe81cf597ad80b3", size = 1863760, upload-time = "2025-11-04T13:41:31.055Z" }, + { url = "https://files.pythonhosted.org/packages/5a/f0/e5e6b99d4191da102f2b0eb9687aaa7f5bea5d9964071a84effc3e40f997/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3006c3dd9ba34b0c094c544c6006cc79e87d8612999f1a5d43b769b89181f23c", size = 1878092, upload-time = "2025-11-04T13:41:33.21Z" }, + { url = "https://files.pythonhosted.org/packages/71/48/36fb760642d568925953bcc8116455513d6e34c4beaa37544118c36aba6d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72f6c8b11857a856bcfa48c86f5368439f74453563f951e473514579d44aa612", size = 2053385, upload-time = "2025-11-04T13:41:35.508Z" }, + { url = "https://files.pythonhosted.org/packages/20/25/92dc684dd8eb75a234bc1c764b4210cf2646479d54b47bf46061657292a8/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cb1b2f9742240e4bb26b652a5aeb840aa4b417c7748b6f8387927bc6e45e40d", size = 2218832, upload-time = "2025-11-04T13:41:37.732Z" }, + { url = "https://files.pythonhosted.org/packages/e2/09/f53e0b05023d3e30357d82eb35835d0f6340ca344720a4599cd663dca599/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd3d54f38609ff308209bd43acea66061494157703364ae40c951f83ba99a1a9", size = 2327585, upload-time = "2025-11-04T13:41:40Z" }, + { url = "https://files.pythonhosted.org/packages/aa/4e/2ae1aa85d6af35a39b236b1b1641de73f5a6ac4d5a7509f77b814885760c/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ff4321e56e879ee8d2a879501c8e469414d948f4aba74a2d4593184eb326660", size = 2041078, upload-time = "2025-11-04T13:41:42.323Z" }, + { url = "https://files.pythonhosted.org/packages/cd/13/2e215f17f0ef326fc72afe94776edb77525142c693767fc347ed6288728d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0d2568a8c11bf8225044aa94409e21da0cb09dcdafe9ecd10250b2baad531a9", size = 2173914, upload-time = "2025-11-04T13:41:45.221Z" }, + { url = "https://files.pythonhosted.org/packages/02/7a/f999a6dcbcd0e5660bc348a3991c8915ce6599f4f2c6ac22f01d7a10816c/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:a39455728aabd58ceabb03c90e12f71fd30fa69615760a075b9fec596456ccc3", size = 2129560, upload-time = "2025-11-04T13:41:47.474Z" }, + { url = "https://files.pythonhosted.org/packages/3a/b1/6c990ac65e3b4c079a4fb9f5b05f5b013afa0f4ed6780a3dd236d2cbdc64/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:239edca560d05757817c13dc17c50766136d21f7cd0fac50295499ae24f90fdf", size = 2329244, upload-time = "2025-11-04T13:41:49.992Z" }, + { url = "https://files.pythonhosted.org/packages/d9/02/3c562f3a51afd4d88fff8dffb1771b30cfdfd79befd9883ee094f5b6c0d8/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:2a5e06546e19f24c6a96a129142a75cee553cc018ffee48a460059b1185f4470", size = 2331955, upload-time = "2025-11-04T13:41:54.079Z" }, + { url = "https://files.pythonhosted.org/packages/5c/96/5fb7d8c3c17bc8c62fdb031c47d77a1af698f1d7a406b0f79aaa1338f9ad/pydantic_core-2.41.5-cp314-cp314t-win32.whl", hash = "sha256:b4ececa40ac28afa90871c2cc2b9ffd2ff0bf749380fbdf57d165fd23da353aa", size = 1988906, upload-time = "2025-11-04T13:41:56.606Z" }, + { url = "https://files.pythonhosted.org/packages/22/ed/182129d83032702912c2e2d8bbe33c036f342cc735737064668585dac28f/pydantic_core-2.41.5-cp314-cp314t-win_amd64.whl", hash = "sha256:80aa89cad80b32a912a65332f64a4450ed00966111b6615ca6816153d3585a8c", size = 1981607, upload-time = "2025-11-04T13:41:58.889Z" }, + { url = "https://files.pythonhosted.org/packages/9f/ed/068e41660b832bb0b1aa5b58011dea2a3fe0ba7861ff38c4d4904c1c1a99/pydantic_core-2.41.5-cp314-cp314t-win_arm64.whl", hash = "sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008", size = 1974769, upload-time = "2025-11-04T13:42:01.186Z" }, + { url = "https://files.pythonhosted.org/packages/09/32/59b0c7e63e277fa7911c2fc70ccfb45ce4b98991e7ef37110663437005af/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:7da7087d756b19037bc2c06edc6c170eeef3c3bafcb8f532ff17d64dc427adfd", size = 2110495, upload-time = "2025-11-04T13:42:49.689Z" }, + { url = "https://files.pythonhosted.org/packages/aa/81/05e400037eaf55ad400bcd318c05bb345b57e708887f07ddb2d20e3f0e98/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:aabf5777b5c8ca26f7824cb4a120a740c9588ed58df9b2d196ce92fba42ff8dc", size = 1915388, upload-time = "2025-11-04T13:42:52.215Z" }, + { url = "https://files.pythonhosted.org/packages/6e/0d/e3549b2399f71d56476b77dbf3cf8937cec5cd70536bdc0e374a421d0599/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c007fe8a43d43b3969e8469004e9845944f1a80e6acd47c150856bb87f230c56", size = 1942879, upload-time = "2025-11-04T13:42:56.483Z" }, + { url = "https://files.pythonhosted.org/packages/f7/07/34573da085946b6a313d7c42f82f16e8920bfd730665de2d11c0c37a74b5/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76d0819de158cd855d1cbb8fcafdf6f5cf1eb8e470abe056d5d161106e38062b", size = 2139017, upload-time = "2025-11-04T13:42:59.471Z" }, +] + [[package]] name = "pygments" version = "2.19.2" @@ -1394,6 +1491,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, ] +[[package]] +name = "python-on-whales" +version = "0.79.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/51/f5/3a283762a9528dea6c666d28ae4c47cc14ad4ce9994ee976852dd0cb166f/python_on_whales-0.79.0.tar.gz", hash = "sha256:919bba304cc04db4b75cdd7fb14c0a8fea6ebeafacf7989f6956641cb58210e0", size = 114675, upload-time = "2025-10-24T09:07:02.81Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/f5/b8cf36274244c12fa1535a62fafdc72c89e8fe965ef1477a9279c09aa2f6/python_on_whales-0.79.0-py3-none-any.whl", hash = "sha256:4a39ab107a4e740e4302c853c0efc5ced4b4048f55acbb959a2faf53b7003c27", size = 118821, upload-time = "2025-10-24T09:07:01.668Z" }, +] + [[package]] name = "pyyaml" version = "6.0.3" @@ -1669,6 +1779,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, ] +[[package]] +name = "typing-inspection" +version = "0.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" }, +] + [[package]] name = "urllib3" version = "2.5.0"