From 4a2d0da8285df5c13d78967c22d20f80d11e74dc Mon Sep 17 00:00:00 2001 From: Yeison Vargas Date: Mon, 6 Jan 2025 11:11:44 -0500 Subject: [PATCH] chore: switch to pyproject --- .devcontainer/Dockerfile | 69 +++++-- .devcontainer/devcontainer.json | 87 ++++++-- .editorconfig | 18 -- .github/scripts/matrix_generator.py | 55 +++++ .github/workflows/build.yml | 5 +- .github/workflows/ci.yml | 71 +++++++ .gitignore | 3 +- .safety-project.ini | 5 + .vscode/launch.json | 300 ++++++++-------------------- LICENSE | 11 - LICENSES/MIT.txt | 18 ++ LICENSES.md => LICENSES/NOTICE.md | 0 MANIFEST.in | 13 -- pyproject.toml | 241 +++++++++++++++++++++- safety/VERSION | 1 - safety/__init__.py | 9 +- safety/alerts/__init__.py | 9 +- safety/util.py | 4 +- setup.cfg | 68 ------- test_requirements.txt | 23 --- tests/conftest.py | 51 +++++ tox.ini | 13 -- 22 files changed, 655 insertions(+), 419 deletions(-) delete mode 100644 .editorconfig create mode 100644 .github/scripts/matrix_generator.py create mode 100644 .github/workflows/ci.yml create mode 100644 .safety-project.ini delete mode 100644 LICENSE create mode 100644 LICENSES/MIT.txt rename LICENSES.md => LICENSES/NOTICE.md (100%) delete mode 100644 MANIFEST.in delete mode 100644 safety/VERSION delete mode 100644 setup.cfg delete mode 100644 test_requirements.txt create mode 100644 tests/conftest.py delete mode 100644 tox.ini diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index cbb999ee..7c92b580 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,27 +1,54 @@ -# Use the official Python 3.12 slim image -FROM python:3.12-slim +FROM python:3.8-alpine -# Create a non-root user and a directory for the application -RUN useradd -m appuser && \ - mkdir /app && \ - chown appuser:appuser /app +ENV PYTHONDONTWRITEBYTECODE=1 +ENV PYTHONUNBUFFERED=1 -# Set the working directory -WORKDIR /app +RUN apk add --no-cache \ + git \ + curl \ + wget \ + zsh \ + jq \ + sudo \ + docker \ + docker-compose \ + bash \ + grep \ + sed \ + nodejs \ + npm \ + # Build dependencies for Python packages + gcc \ + musl-dev \ + python3-dev \ + libffi-dev \ + openssl-dev \ + cargo \ + rust \ + make && npm install -g pyright -# Set environment variables in a single step -ENV LC_ALL=C.UTF-8 \ - LANG=C.UTF-8 \ - PYTHONPATH="/app" +RUN pip install --no-cache-dir uv \ + && uv pip install --system hatch hatch-containers -# Install necessary dependencies, clean up after installation to reduce image size -RUN apt-get update && \ - apt-get -y install docker.io jq git && \ - apt-get clean && \ - rm -rf /var/lib/apt/lists/* +ARG USERNAME=developer +ARG USER_UID=1000 +ARG USER_GID=$USER_UID -# Copy project files into the container (relative to the build context) -COPY . /app/ +RUN addgroup -g $USER_GID $USERNAME \ + && adduser -u $USER_UID -G $USERNAME -s /bin/zsh -D $USERNAME \ + && echo "$USERNAME ALL=(root) NOPASSWD:ALL" > /etc/sudoers.d/$USERNAME \ + && chmod 0440 /etc/sudoers.d/$USERNAME \ + && addgroup $USERNAME docker -# Switch to the non-root user for security reasons -USER appuser +RUN sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" + +RUN sed -i 's|/bin/ash|/bin/zsh|' /etc/passwd + +RUN cp -r /root/.oh-my-zsh /home/$USERNAME/ \ + && cp /root/.zshrc /home/$USERNAME/ \ + && chown -R $USERNAME:$USERNAME /home/$USERNAME/.oh-my-zsh \ + && chown $USERNAME:$USERNAME /home/$USERNAME/.zshrc + +USER $USERNAME + +CMD ["zsh"] \ No newline at end of file diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 20c25d99..cc7412cc 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,19 +1,70 @@ { - "name": "Safety-CLI Dev Container", - "build": { - "dockerfile": "Dockerfile", - "context": "..", - "args": { - "SAFETY_VERSION": "DEV" - } - }, - "extensions": [ - "ms-python.python", - "ms-python.vscode-pylance", - "ms-python.debugpy" - ], - "postCreateCommand": "pip install -r test_requirements.txt && pip install ruff requests pre-commit", - "remoteUser": "root", - "workspaceFolder": "/workspaces/safety", - "forwardPorts": [49152] -} + "name": "Safety CLI Development Environment", + + "build": { + "dockerfile": "Dockerfile", + "context": "." + }, + + "remoteUser": "developer", + "workspaceFolder": "${localWorkspaceFolder}", + "workspaceMount": "source=${localWorkspaceFolder},target=${localWorkspaceFolder},type=bind", + + + "mounts": [ + "source=/var/run/docker.sock,target=/var/run/docker.sock,type=bind", + "source=${localEnv:HOME}${localEnv:USERPROFILE}/.ssh,target=/home/developer/.ssh,type=bind,consistency=cached" + ], + + "remoteEnv": { + "PYTHONPATH": "${localWorkspaceFolder}", + "TERM": "xterm-256color" + }, + + "customizations": { + "vscode": { + "settings": { + "terminal.integrated.defaultProfile.linux": "zsh", + "terminal.integrated.profiles.linux": { + "zsh": { + "path": "/bin/zsh" + } + }, + "python.defaultInterpreterPath": "${localWorkspaceFolder}/.hatch/bin/python", + "editor.rulers": [80], + "files.exclude": { + "**/__pycache__": true, + "**/.pytest_cache": true + }, + "search.exclude": { + "**/.hatch": true, + } + }, + "extensions": [ + "ms-python.vscode-pylance", + "ms-python.python", + "ms-python.debugpy", + "ms-pyright.pyright", + "charliermarsh.ruff", + "tamasfe.even-better-toml", + "GitHub.copilot", + "streetsidesoftware.code-spell-checker", + "VisualStudioExptTeam.vscodeintellicode", + "VisualStudioExptTeam.intellicode-api-usage-examples", + "mechatroner.rainbow-csv", + "redhat.vscode-yaml", + "eamodio.gitlens", + "github.vscode-github-actions" + ] + } + }, + + "postCreateCommand": "hatch env create default && git config --global core.editor nano", + + "containerEnv": { + "SHELL": "/bin/zsh" + }, + + "waitFor": "postCreateCommand", + "shutdownAction": "stopContainer" +} \ No newline at end of file diff --git a/.editorconfig b/.editorconfig deleted file mode 100644 index f37120d1..00000000 --- a/.editorconfig +++ /dev/null @@ -1,18 +0,0 @@ -# http://editorconfig.org - -root = true - -[*] -indent_style = space -indent_size = 4 -trim_trailing_whitespace = true -insert_final_newline = true -charset = utf-8 -end_of_line = lf - -[*.bat] -indent_style = tab -end_of_line = crlf - -[LICENSE] -insert_final_newline = false diff --git a/.github/scripts/matrix_generator.py b/.github/scripts/matrix_generator.py new file mode 100644 index 00000000..6cd128d7 --- /dev/null +++ b/.github/scripts/matrix_generator.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 +import json +import sys +from pathlib import Path +import tomli + +def read_toml_config(file_path: str) -> dict: + """Read and parse TOML configuration file.""" + with open(file_path, 'rb') as f: + return tomli.load(f) + +def generate_github_matrix(config: dict) -> dict: + """Generate GitHub Actions matrix configuration from Hatch config.""" + test_config = config['tool']['hatch']['envs']['test'] + matrix_configs = test_config['matrix'] + + combinations = [] + + # First matrix: all Python versions with no target + for python_version in matrix_configs[0]['python']: + combinations.append({ + "python-version": python_version, + "target": None + }) + + # Second matrix: specific Python versions with targets + for python_version in matrix_configs[1]['python']: + for target in matrix_configs[1]['targets']: + combinations.append({ + "python-version": python_version, + "target": target + }) + + return {"include": combinations} + +def main(): + if len(sys.argv) != 2: + print("Usage: python matrix_generator.py ") + sys.exit(1) + + toml_path = Path(sys.argv[1]) + if not toml_path.exists(): + print(f"Error: File {toml_path} not found") + sys.exit(1) + + try: + config = read_toml_config(str(toml_path)) + matrix = generate_github_matrix(config) + print(json.dumps(matrix, indent=2)) + except Exception as e: + print(f"Error processing TOML file: {e}", file=sys.stderr) + sys.exit(1) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index fd12bdd8..c5601f9a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -17,10 +17,7 @@ jobs: - name: Safety Version run: | - pip install packaging - package_version=$(cat safety/VERSION) - echo $package_version - echo "SAFETY_VERSION=$package_version" >> $GITHUB_ENV + echo "SAFETY_VERSION=$(python -c 'import tomli; print(tomli.load(open("pyproject.toml", "rb"))["project"]["version"])')" >> $GITHUB_ENV - name: Extract Major and Minor Version run: | diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..182fc18f --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,71 @@ +name: CI + +on: + workflow_call: + push: + branches: + - main + - chore/* + - ci/* + pull_request: + types: + - opened + - synchronize + schedule: + - cron: "0 0 * * 1" + +jobs: + generate-matrix: + runs-on: ubuntu-latest + outputs: + matrix: ${{ steps.set-matrix.outputs.matrix }} + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.10" + + - name: Generate matrix + id: set-matrix + run: | + matrix=$(python .github/scripts/matrix_generator.py pyproject.toml) + echo "matrix=$matrix" >> $GITHUB_OUTPUT + + test: + needs: generate-matrix + runs-on: ubuntu-latest + strategy: + matrix: ${{fromJson(needs.generate-matrix.outputs.matrix)}} + fail-fast: false + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Install Hatch + run: | + python -m pip install --upgrade pip + pip install hatch + + - name: Create temporary config + run: | + cp pyproject.toml temp_ci.toml + sed -i 's/type = "container"/type = "virtual"/' temp_ci.toml + + - name: Run tests + run: | + if [ "${{ matrix.target }}" = "null" ]; then + # For regular Python version tests + HATCH_CONFIG=temp_ci.toml hatch run test.py${{ matrix.python-version }}:test + else + # For tests with specific targets + HATCH_CONFIG=temp_ci.toml hatch run test.py${{ matrix.python-version }}-${{ matrix.target }}:test + fi + diff --git a/.gitignore b/.gitignore index 176131f2..75ef1a6f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,8 @@ # Other -.vscode/ .direnv/ .envrc +uv.lock +.hatch/ # Byte-compiled / optimized / DLL files __pycache__/ diff --git a/.safety-project.ini b/.safety-project.ini new file mode 100644 index 00000000..00f29606 --- /dev/null +++ b/.safety-project.ini @@ -0,0 +1,5 @@ +[project] +id = safety +url = /projects/aa1f1929-42d3-4fb7-8e6c-f9e187a832a2/findings +name = safety + diff --git a/.vscode/launch.json b/.vscode/launch.json index b716936c..36e03050 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -2,241 +2,115 @@ "version": "0.2.0", "configurations": [ { - "name": "Safety Auth Login", + "name": "[CLI]: Safety CLI", "type": "debugpy", "request": "launch", "module": "safety", - "args": [ - "auth" - ], - "console": "integratedTerminal" - }, - { - "name": "Safety Auth Login --headless", - "type": "debugpy", - "request": "launch", - "module": "safety", - "args": [ - "auth","login","--headless" - ], - "console": "integratedTerminal" - }, - { - "name": "Safety Auth Logout", - "type": "debugpy", - "request": "launch", - "module": "safety", - "args": [ - "auth", - "logout" - ], - "console": "integratedTerminal" - }, - { - "name": "Safety Scan", - "type": "debugpy", - "request": "launch", - "module": "safety", - "args": [ - "scan" - ], - "console": "integratedTerminal" - }, - { - "name": "Safety Scan --use-server-matching", - "type": "debugpy", - "request": "launch", - "module": "safety", - "args": [ - "scan", "--use-server-matching" - ], - "console": "integratedTerminal" - }, - { - "name": "Safety Scan API Key", - "type": "debugpy", - "request": "launch", - "module": "safety", - "args": [ - "--key","ADD-YOUR-API-KEY", "scan" - ], - "console": "integratedTerminal" - }, - { - "name": "Safety License", - "type": "debugpy", - "request": "launch", - "module": "safety", - "args": [ - "license" - ], + "args": "${input:mainCommand}", + // This uses the default environment which is a virtual environment + // created by Hatch + "python": "${workspaceFolder}/.hatch/bin/python", "console": "integratedTerminal" }, { - "name": "Safety Scan --detailed-output", + "name": "[TESTS]: Run Selected", "type": "debugpy", "request": "launch", - "module": "safety", + "module": "hatch", "args": [ - "scan", - "--detailed-output" - ], + "-e", + "test.${input:environment}", + "run", + "test", + "${input:testPath}" + ], "console": "integratedTerminal" }, { - "name": "Safety Scan --debug", + "name": "[TESTS]: Run Current Test File", "type": "debugpy", "request": "launch", - "module": "safety", + "module": "hatch", "args": [ - "--debug", - "scan" + "-e", + "test.${input:environment}", + "run", + "test", + "${file}" ], "console": "integratedTerminal" - }, + }, + ], + "inputs": [ { - "name": "Safety Scan --disable-optional-telemetry", - "type": "debugpy", - "request": "launch", - "module": "safety", - "args": [ - "--disable-optional-telemetry", - "scan" + "id": "testPath", + "type": "pickString", + "description": "Choose the test path (selecting 'tests' will run all tests)", + // Note: This is a manual list and works for now. + "options": [ + "tests", + "tests/alerts", + "tests/auth", + "tests/scan", + "tests/formatters", + "tests/test_cli.py", + "tests/test_models.py", + "tests/test_output_utils.py", + "tests/test_safety.py", + "tests/test_util.py", + "tests/test_policy_file.py", + "tests/test_db.py" ], - "console": "integratedTerminal" - }, - { - "name": "Safety Scan --output json", - "type": "debugpy", - "request": "launch", - "module": "safety", - "args": [ + "default": "tests/" + }, + { + "id": "environment", + "type": "pickString", + "description": "Select environment", + // Note: This is a manual list and works for now. + "options": [ + "py3.8", + "py3.9", + "py3.10", + "py3.11", + "py3.12", + "py3.13", + "py3.8-pydantic-latest", + "py3.8-typer-latest", + "py3.13-pydantic-latest", + "py3.13-typer-latest", + ], + "default": "py3.8" + }, + { + "id": "mainCommand", + "type": "pickString", + "description": "Select Safety command with options", + "options": [ + // Auth commands + "auth", + "auth login", + "auth login --headless", + "auth logout", + + // Scan commands "scan", - "--output", - "json", - "--output-file", - "test.json" - ], - "console": "integratedTerminal" - }, - { - "name": "Safety Check", - "type": "debugpy", - "request": "launch", - "module": "safety", - "args": [ - "check" - ], - "console": "integratedTerminal" - }, - { - "name": "Safety Check --debug", - "type": "debugpy", - "request": "launch", - "module": "safety", - "args": [ + "--key ADD-YOUR-API-KEY scan", + "scan --use-server-matching", + "scan --detailed-output", + "--debug scan", + "--disable-optional-telemetry scan", + "scan --output json --output-file json", + + // Check commands "check", - "--debug" - ], - "console": "integratedTerminal" - }, - { - "name": "Safety --help", - "type": "debugpy", - "request": "launch", - "module": "safety", - "args": [ + "--debug check", + + // Other commands + "license", "--help" ], - "console": "integratedTerminal" - }, - { - "name": "Run All Tests", - "type": "debugpy", - "request": "launch", - "module": "pytest", - "args": [], - "console": "integratedTerminal" - }, - { - "name": "Run test_render.py", - "type": "debugpy", - "request": "launch", - "module": "pytest", - "args": [ - "./tests/scan/test_render.py" - ], - "console": "integratedTerminal" - }, - { - "name": "Run test_cli.py", - "type": "debugpy", - "request": "launch", - "module": "pytest", - "args": [ - "tests/test_cli.py" - ], - "console": "integratedTerminal" - }, - { - "name": "Run test_db.py", - "type": "debugpy", - "request": "launch", - "module": "pytest", - "args": [ - "tests/test_db.py" - ], - "console": "integratedTerminal" - }, - { - "name": "Run test_output_utils.py", - "type": "debugpy", - "request": "launch", - "module": "pytest", - "args": [ - "tests/test_output_utils.py" - ], - "console": "integratedTerminal" - }, - { - "name": "Run test_policy_file.py", - "type": "debugpy", - "request": "launch", - "module": "pytest", - "args": [ - "tests/test_policy_file.py" - ], - "console": "integratedTerminal" - }, - { - "name": "Run test_util.py", - "type": "debugpy", - "request": "launch", - "module": "pytest", - "args": [ - "tests/test_util.py" - ], - "console": "integratedTerminal" - }, - { - "name": "Run test_safety.py", - "type": "debugpy", - "request": "launch", - "module": "pytest", - "args": [ - "tests/test_safety.py" - ], - "console": "integratedTerminal" - }, - { - "name": "Run test_models.py", - "type": "debugpy", - "request": "launch", - "module": "pytest", - "args": [ - "tests/test_models.py" - ], - "console": "integratedTerminal" - } + "default": "scan" + } ] } \ No newline at end of file diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 796a276c..00000000 --- a/LICENSE +++ /dev/null @@ -1,11 +0,0 @@ - -MIT License - -Copyright (c) 2016, safetycli.com - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - diff --git a/LICENSES/MIT.txt b/LICENSES/MIT.txt new file mode 100644 index 00000000..c41b1765 --- /dev/null +++ b/LICENSES/MIT.txt @@ -0,0 +1,18 @@ +MIT License + +Copyright (c) 2016 Safety CLI Cybersecurity Inc + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +associated documentation files (the "Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the +following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial +portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT +LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO +EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/LICENSES.md b/LICENSES/NOTICE.md similarity index 100% rename from LICENSES.md rename to LICENSES/NOTICE.md diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index 518319c7..00000000 --- a/MANIFEST.in +++ /dev/null @@ -1,13 +0,0 @@ - -include CHANGELOG.md -include LICENSE -include README.md -include README.old -include safety/VERSION -include safety/safety-policy-template.yml -include safety/alerts/templates/* - -recursive-include safety/templates * -recursive-include tests * -recursive-exclude * __pycache__ -recursive-exclude * *.py[co] diff --git a/pyproject.toml b/pyproject.toml index b0f07653..13a861d8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,240 @@ [build-system] -requires = ["setuptools>=42"] -build-backend = "setuptools.build_meta" +requires = ["hatchling"] +build-backend = "hatchling.build" + +[project] +name = "safety" +version = "3.2.14" +description = "Scan dependencies for known vulnerabilities and licenses." +keywords = ["safety", "vulnerabilities", "dependencies", "licenses", "scan"] +readme = "README.md" +authors = [ + {name = "Safety", email = "cli@safetycli.com"}, +] +classifiers = [ + "Development Status :: 5 - Production/Stable", + "License :: OSI Approved :: MIT License", + "Topic :: Security", + "Natural Language :: English", + "Intended Audience :: Developers", + "Intended Audience :: Information Technology", + "Intended Audience :: System Administrators", + "Intended Audience :: Telecommunications Industry", + "Operating System :: POSIX :: Linux", + "Operating System :: MacOS", + "Operating System :: Microsoft :: Windows", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", +] +requires-python = ">=3.8" +dependencies = [ + "Authlib>=1.2.0", + "Click>=8.0.2", # Just to set a minimum version, Typer works with >=8.0.0 + "dparse>=0.6.4", + "filelock~=3.16.1", + "jinja2>=3.1.0", + "marshmallow>=3.15.0", # TODO: To be removed + "packaging>=21.0", + "psutil~=6.1.0", + "pydantic>=2.6.0,<2.10.0", + "requests", # TODO: To be replaced by httpx or aiohttp + # "rich", + "ruamel.yaml>=0.17.21", + "safety-schemas==0.0.10", + # "setuptools>=65.5.1", + "typer>=0.12.1", + "typing-extensions>=4.7.1", +] +license = "MIT" +license-files = ["LICENSES/*"] + + +[project.urls] +homepage = "https://safetycli.com" +source = "https://github.com/pyupio/safety" +issues = "https://github.com/pyupio/safety/issues" +changelog = "https://github.com/pyupio/safety/blob/main/CHANGELOG.md" +documentation = "https://docs.safetycli.com/safety-docs" + +[project.optional-dependencies] +github = [ + "pygithub>=1.43.3" +] +gitlab = [ + "python-gitlab>=1.3.0" +] +spdx = [ + "spdx-tools>=0.8.2" +] + +[project.scripts] +safety = "safety.cli:cli" + + +# Build specific configurations + +[tool.hatch.build.targets.sdist] +only-include = [ + "CHANGELOG.md", + "README.md", + "SECURITY.md", + "LICENSES/*", + "safety", + "tests", + "pyproject.toml" +] + +[tool.hatch.build.targets.wheel] +packages = [ + "safety" +] +include-package-data = true +zip-safe = false + +# Tool specific configurations + +## Hatch environments +[tool.hatch.envs.default] +installer = "uv" +type = "virtual" +# This should always be set to the minimum supported Python version. +# To test with a different version, modify this value, refresh +# the environment, and run it via the VSCode launch configuration +# (it will pick up the new interpreter). +# +# To reset the default environment, run: +# hatch env remove +# hatch env create +python = "3.8" +path = ".hatch" + +dependencies = [ + "commitizen" +] + +# Common scripts used across environments +[tool.hatch.envs.default.scripts] +bump = "cz bump --check-consistency --yes && cz changelog --merge-prerelease" +beta-bump = "cz bump -pr beta --check-consistency --yes && cz changelog --merge-prerelease" +dev-bump = "cz bump --devrelease {args} --check-consistency --yes" +local-bump = "cz bump {args} --changelog --files-only --yes" + + +[tool.hatch.envs.test] +installer = "uv" +type = "container" + +dependencies = [ + "coverage[toml]>=6.0", + "pytest==8.3.4", + "commitizen" +] + +[tool.hatch.envs.test.scripts] +test = "pytest {args:tests}" +test-cov = 'coverage run -m pytest -m "" {args:tests}' +cov-report = [ + "coverage combine", + "coverage report --show-missing", +] +cov = [ + "test-cov", + "cov-report", +] + + +[tool.hatch.envs.test.overrides] +matrix.targets.dependencies = [ + { value = "pydantic~=2.9.0", if = ["pydantic-latest"] }, + + { value = "typer~=0.13", if = ["typer-latest"] }, + { value = "Click~=8.1", if = ["typer-latest"] } +] + +# Note: scripts/matrix_generator.py is used to generate the matrix for +# the GitHub CI workflow, the script is very naive and a switch in the +# order of the matrixes can cause the script to fail. +# This was pragmatic to avoid having to maintain the matrix in two places. +# +# Make sure to update the script if you structural changes to the matrixes. +# Examples: +# * Add/Remove python versions is okay. +# * Add/Remove targets is okay. +# * Reorder matrixes is not okay. +# +[[tool.hatch.envs.test.matrix]] +# Full Python version testing +python = ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"] + +# Critical dependency testing only on boundary versions +[[tool.hatch.envs.test.matrix]] +python = ["3.8", "3.13"] +targets = ["pydantic-latest", "typer-latest"] + + +[tool.hatch.envs.lint] +detached = true +dependencies = [ + "ruff", +] +[tool.hatch.envs.lint.scripts] +typing = "pyright" +style = [ + "ruff check {args:.}", + "ruff format --check --diff {args:.}", +] +fmt = [ + "ruff format {args:.}", + "ruff --fix {args:.}", + "style", +] +all = [ + "style", + "typing", +] + +[tool.pyright] +include = [ + "safety/scan/" +] +pythonVersion = "3.8" +pythonPlatform = "All" +reportMissingImports = "error" +reportMissingTypeStubs = false + +[tool.pytest.ini_options] +addopts = "--strict-markers" +markers = [ + "basic: requires no extras", +] + +[tool.coverage.run] +source_pkgs = ["safety"] +branch = true +parallel = true +omit = [ +] + +[tool.coverage.paths] +source = ["safety", "*/safety"] + +[tool.coverage.report] +exclude_lines = [ +] +[tool.commitizen] +name = "cz_conventional_commits" +tag_format = "$version" +version_scheme = "pep440" +version_provider = "pep621" +update_changelog_on_bump = false +annotated_tag = true +changelog_incremental = false + + +[tool.flake8] +exclude = ["docs"] diff --git a/safety/VERSION b/safety/VERSION deleted file mode 100644 index 23c635a1..00000000 --- a/safety/VERSION +++ /dev/null @@ -1 +0,0 @@ -3.2.14 diff --git a/safety/__init__.py b/safety/__init__.py index 6d3d19d3..73fcd701 100644 --- a/safety/__init__.py +++ b/safety/__init__.py @@ -1,11 +1,4 @@ # -*- coding: utf-8 -*- __author__ = """safetycli.com""" -__email__ = 'support@safetycli.com' - -import os - -ROOT = os.path.dirname(os.path.abspath(__file__)) - -with open(os.path.join(ROOT, 'VERSION')) as version_file: - VERSION = version_file.read().strip() +__email__ = 'cli@safetycli.com' diff --git a/safety/alerts/__init__.py b/safety/alerts/__init__.py index 0304f182..28972b3c 100644 --- a/safety/alerts/__init__.py +++ b/safety/alerts/__init__.py @@ -6,14 +6,17 @@ from dataclasses import dataclass -from safety.cli_util import SafetyCLILegacyGroup - from . import github from safety.util import SafetyPolicyFile from safety.scan.constants import CLI_ALERT_COMMAND_HELP LOG = logging.getLogger(__name__) + +def get_safety_cli_legacy_group(): + from safety.cli_util import SafetyCLILegacyGroup + return SafetyCLILegacyGroup + @dataclass class Alert: """ @@ -30,7 +33,7 @@ class Alert: policy: Any = None requirements_files: Any = None -@click.group(cls=SafetyCLILegacyGroup, help=CLI_ALERT_COMMAND_HELP, deprecated=True, utility_command=True) +@click.group(cls=get_safety_cli_legacy_group(), help=CLI_ALERT_COMMAND_HELP, deprecated=True, utility_command=True) @click.option('--check-report', help='JSON output of Safety Check to work with.', type=click.File('r'), default=sys.stdin, required=True) @click.option("--key", envvar="SAFETY_API_KEY", help="API Key for safetycli.com's vulnerability database. Can be set as SAFETY_API_KEY " diff --git a/safety/util.py b/safety/util.py index 420eb13a..83c30527 100644 --- a/safety/util.py +++ b/safety/util.py @@ -237,8 +237,8 @@ def get_safety_version() -> str: Returns: str: The Safety version. """ - from safety import VERSION - return VERSION + from importlib.metadata import version + return version("safety") def get_primary_announcement(announcements: List[Dict[str, Any]]) -> Optional[Dict[str, Any]]: diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 72c36621..00000000 --- a/setup.cfg +++ /dev/null @@ -1,68 +0,0 @@ -[metadata] -name = safety -version = file: safety/VERSION -description = Checks installed dependencies for known vulnerabilities and licenses. -long_description = file: README.md, CHANGELOG.md, LICENSE -long_description_content_type = text/markdown -author = safetycli.com -author_email = support@safetycli.com -url = https://github.com/pyupio/safety -project_urls = - Bug Tracker = https://github.com/pyupio/safety/issues - Source = https://github.com/pyupio/safety/ - Documentation = https://docs.pyup.io/docs/getting-started-with-safety-cli -keywords = safety, vulnerabilities, dependencies, licenses, check -license = MIT license -classifiers = - Development Status :: 5 - Production/Stable - Intended Audience :: Developers - License :: OSI Approved :: MIT License - Natural Language :: English - Operating System :: POSIX :: Linux - Operating System :: MacOS - Operating System :: Microsoft :: Windows - Programming Language :: Python :: 3 :: Only - Programming Language :: Python :: 3.6 - Programming Language :: Python :: 3.7 - Programming Language :: Python :: 3.8 - Programming Language :: Python :: 3.9 - Programming Language :: Python :: 3.10 - Programming Language :: Python :: 3.11 - -[options] -zip_safe = False -include_package_data = True -packages = safety, safety.formatters,safety.formatters.schemas, safety.alerts, safety.auth, safety.scan, safety.scan.finder, safety.scan.ecosystems, safety.scan.ecosystems.python, safety.alerts.templates, safety.templates, safety.scan.fun_mode -python_requires = >=3.8 -package_dir = - safety = safety -install_requires = - setuptools>=65.5.1 - Click>=8.0.2 - urllib3>=1.26.5 - requests - packaging>=21.0 - dparse>=0.6.4 - ruamel.yaml>=0.17.21 - jinja2>=3.1.0 - marshmallow>=3.15.0 - Authlib>=1.2.0 - rich - typer>=0.12.1 - pydantic>=2.6.0,<2.10.0 - safety_schemas==0.0.10 - typing-extensions>=4.7.1 - filelock~=3.16.1 - psutil~=6.1.0 - -[options.entry_points] -console_scripts = - safety = safety.cli:cli - -[options.extras_require] -github = pygithub>=1.43.3 -gitlab = python-gitlab>=1.3.0 -spdx = spdx-tools>=0.8.2 - -[flake8] -exclude = docs diff --git a/test_requirements.txt b/test_requirements.txt deleted file mode 100644 index 6757cff8..00000000 --- a/test_requirements.txt +++ /dev/null @@ -1,23 +0,0 @@ -pytest==7.4.4 -pytest-cov==4.1.0 -setuptools>=65.5.1; python_version>="3.7" -setuptools; python_version=="3.6" -Click>=8.0.2 -urllib3>=1.26.5 -requests -packaging>=21.0,<=23.0 -dparse>=0.6.4 -ruamel.yaml>=0.17.21 -dataclasses==0.8; python_version=="3.6" -jinja2; python_version=="3.6" -jinja2>=3.1.0; python_version>="3.7" -marshmallow; python_version=="3.6" -marshmallow>=3.15.0; python_version>="3.7" -Authlib>=1.2.0 -rich -typer>=0.12.1 -pydantic>=2.6.0,<2.10.0 -safety_schemas==0.0.10 -typing-extensions>=4.7.1 -filelock~=3.16.1 -psutil~=6.1.0 diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 00000000..11747a4b --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,51 @@ +from importlib.metadata import distributions +import tomli +from packaging.requirements import Requirement +from packaging.utils import canonicalize_name + +def get_project_dependencies(): + try: + with open("pyproject.toml", "rb") as f: + pyproject = tomli.load(f) + deps = pyproject.get("project", {}).get("dependencies", []) + return {canonicalize_name(Requirement(dep).name): dep + for dep in deps if isinstance(dep, str)} + except Exception as e: + print(f"Error reading dependencies: {e}") + return {} + +def pytest_configure(config): + main_deps_specs = get_project_dependencies() + all_dists = {canonicalize_name(dist.metadata['Name']): + (dist.metadata['Name'], dist.version) + for dist in distributions()} + + # Main dependencies table + print(f"\n[{len(main_deps_specs)}] Main Dependencies:") + print("-" * 60) + print("%-20s %-25s %-15s" % ("Package", "Specification", "Installed")) + print("-" * 60) + + for pkg_norm, spec in sorted(main_deps_specs.items()): + if pkg_norm in all_dists: + name, version = all_dists[pkg_norm] + print("%-20s %-25s %-15s" % (name, spec, version)) + + other_pkgs = [f"{name} {ver}" + for pkg_norm, (name, ver) in sorted(all_dists.items()) + if pkg_norm not in main_deps_specs] + + # Other dependencies in wrapped format + print(f"\n[{len(other_pkgs)}] Other Dependencies:") + print("-" * 80) + + # Print other dependencies with wrapping + line = "" + for pkg in other_pkgs: + if len(line) + len(pkg) + 2 > 78: + print(line.rstrip(", ")) + line = pkg + ", " + else: + line += pkg + ", " + if line: + print(line.rstrip(", ")) \ No newline at end of file diff --git a/tox.ini b/tox.ini deleted file mode 100644 index b2550420..00000000 --- a/tox.ini +++ /dev/null @@ -1,13 +0,0 @@ -[tox] -envlist = py{37,38,39,310,311,313}-packaging{21,22,23}-click{8.1.7} - -isolated_build = true - -[testenv] -deps = - pytest-cov==4.1.0 - pytest==7.4.4 - -commands = - pytest -rP tests/ --cov=safety/ --cov-report=html -