Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve output layout #14

Merged
merged 13 commits into from
Mar 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 57 additions & 21 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -1,28 +1,64 @@
name: Python Tests
name: dependency-check

on: [pull_request]
on:
pull_request:
branches:
- main

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

permissions:
contents: read # to fetch code (actions/checkout)

jobs:
test:
name: Run Tests
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v2

- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: 3.x

- name: Install Dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements-test.txt

- name: Run pytest
run: |
# Run all tests except the ones that are marked as --deselect
# This is because they can only be run locally till I find a way around that
pytest --deselect tests/test_main --deselect tests/test_repository
#----------------------------------------------
# check-out repo and set-up python
#----------------------------------------------
- name: Check out repository
uses: actions/checkout@v4
- name: Set up python
id: setup-python
uses: actions/setup-python@v5
with:
python-version: 3.9
#----------------------------------------------
# ----- install & configure poetry -----
#----------------------------------------------
- name: Install Poetry
uses: snok/install-poetry@v1
with:
virtualenvs-create: true
virtualenvs-in-project: true
#----------------------------------------------
# load cached venv if cache exists
#----------------------------------------------
- name: Load cached venv
id: cached-poetry-dependencies
uses: actions/cache@v3
with:
path: .venv
key: venv-${{ runner.os }}-${{ steps.setup-python.outputs.python-version }}-${{ hashFiles('**/poetry.lock') }}
#----------------------------------------------
# install dependencies if cache does not exist
#----------------------------------------------
- name: Install dependencies
if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true'
run: poetry install --no-interaction --no-root
#----------------------------------------------
# install your root project, if required
#----------------------------------------------
- name: Install library
run: poetry install --no-interaction
#----------------------------------------------
# run test suite
#----------------------------------------------
- name: Run tests
run: |
source .venv/bin/activate
pytest tests/ --cov=./
24 changes: 18 additions & 6 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,13 +1,25 @@
default_language_version:
python: python3

repos:
- repo: https://github.com/pycqa/isort
rev: 5.12.0
hooks:
- id: isort
- repo: https://github.com/psf/black
rev: 23.7.0
rev: 24.2.0
hooks:
- id: black
language_version: python3
args: ["--target-version", "py38"]
- repo: https://github.com/adamchainz/blacken-docs
rev: 1.16.0
hooks:
- id: blacken-docs
additional_dependencies: [black==23.9.1]
- repo: https://github.com/pycqa/isort
# isort config is in setup.cfg
rev: 5.13.2
hooks:
- id: isort
- repo: https://github.com/pycqa/flake8
rev: 6.1.0
# flake8 config is in setup.cfg
rev: 7.0.0
hooks:
- id: flake8
2 changes: 0 additions & 2 deletions Makefile

This file was deleted.

731 changes: 389 additions & 342 deletions poetry.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ python = "^3.9"
requests = "^2.31.0"
click = "^8.1.3"
tomli = "^2.0.1"
rich = "^13.7.1"


[tool.poetry.group.dev.dependencies]
Expand Down
34 changes: 0 additions & 34 deletions requirements-test.txt

This file was deleted.

149 changes: 86 additions & 63 deletions src/main.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import click
from rich import box
from rich.console import Console
from rich.table import Table
from src.managers.package import Client, Package
from src.managers.repository import RepositoryManager
from src.managers.runner import DockerManager
from src.parsers.frozen import FrozenParser
from src.parsers.toml import TomlParser

console = Console()


@click.command()
@click.option(
Expand All @@ -31,99 +36,117 @@
default="./",
)
def start(repo_url, branch_name, docker_file_name, docker_file_location=None):
console.print("\n")
table = Table(title="Repository Information", box=box.MARKDOWN, show_lines=True)
table.add_column("Repository URL", style="bright_white")
table.add_column("Branch Name", style="bright_white")
table.add_column("Dockerfile", style="bright_white")
table.add_column("Dockerfile Location", style="bright_white")
table.add_row(repo_url, branch_name, docker_file_name, docker_file_location)
console.print(table)

# clone the repository
if not docker_file_location == "./":
df = f"{docker_file_location}/{docker_file_name}"
repo_manager = RepositoryManager(repo_url, df)
else:
repo_manager = RepositoryManager(repo_url, docker_file_name)

click.echo(f"Cloning repository {repo_url} ...")
repo_manager.clone()
click.secho(f"Cloned repository to {repo_manager.repo_dir}")
client = Client("https://pypi.org/pypi")

repo_manager.clone()
# switch to an alternative branch if specified
# if branch_name != 'main':
repo_manager.branch(branch_name)
click.secho(f"Checked out branch {branch_name}")

# get the docker image from the Dockerfile
docker_image = repo_manager.docker_image
click.secho(f"Found Docker image {docker_image}")

# get the poetry version from the Dockerfile
poetry_version = repo_manager.poetry_version
click.secho(f"Found Poetry version {poetry_version}")
poetry_latest_version = client.get("poetry").json()["info"]["version"]

console.print("\n")
table = Table(title="Docker Information", box=box.MARKDOWN, show_lines=True)
table.add_column("Docker Image", style="bright_white")
table.add_column("Poetry Version", style="bright_white")
table.add_column("Latest Poetry Version", style="bright_white")
table.add_row(docker_image, poetry_version, poetry_latest_version)
console.print(table)

# run the docker image
docker = DockerManager(docker_image, poetry_version, repo_manager.repo_dir)
click.echo("Running the docker image. This may take some time ...")
console.print("Running the docker image. This may take some time ...", style="yellow1")
docker.run(docker.run_cmd, docker.run_args)
click.secho("Generated requirements-frozen.txt")

# process the requirements-frozen.txt file as a FrozenParser object
# it's used to lookup the package name and version installed in the docker image
frozen = FrozenParser()
click.echo("Processing frozen requirements ...")
frozen.parse_requirements()
frozen_dependencies = frozen.requirements

# process the pyproject.toml file as a TomlParser object
# it's used to lookup the package name and version specified in the pyproject.toml file
toml = TomlParser(repo_manager.toml)
click.echo("Processing pyproject.toml ...")
dependencies = sorted(toml.dependencies().keys())
dev_dependencies = sorted(toml.dev_dependencies().keys())

report_production_dependencies = []
report_dev_dependencies = []
messages = []

client = Client("https://pypi.org/pypi")

click.echo("\n")
click.secho("RED: Manual check should be carried out", fg="bright_red")
click.secho("YELLOW: The latest available version is not installed", fg="bright_yellow")
click.secho("GREEN: Using the latest version available is installed", fg="bright_green")
production_packages = []
development_packages = []

# production dependencies
click.echo("\n")
click.secho("Production dependencies ...", **{"underline": True, "fg": "bright_white"})
console.print("\n")
table = Table(title="Production Dependencies", box=box.MARKDOWN, show_lines=True)
table.add_column("Package", style="bright_white")
table.add_column("Installed Version", style="bright_white")
table.add_column("Latest Version", style="bright_white")
table.add_column("Status", style="bright_white")

for dependency in dependencies:
c = client.get(dependency)
if isinstance(c, int):
# deals with cases such as package names with [extras] in them
messages.append(f"{dependency}")
continue

package = Package(c.json())
latest_version = package.latest_version
frozen_version = frozen_dependencies.get(dependency.lower())

if frozen_version is None:
# deals with cases such as package names that don't exist such as "python"
messages.append(f"{dependency}")
continue
production_packages.append(package)

if frozen_version != latest_version:
if "git+https://" not in frozen_version:
report_production_dependencies.append(
(f"{dependency} {frozen_version} -> {latest_version}", "bright_yellow")
)
for package in production_packages:
name = package.name
latest_version = package.latest_version
frozen_version = frozen_dependencies.get(name.lower())
if frozen_version and frozen_version != latest_version:
if "git+https://" in frozen_version:
status = "Check"
style = "red1"
else:
report_production_dependencies.append(
(f"{dependency} {frozen_version} -> {latest_version}", "bright_red")
)
status = "Outdated"
style = "yellow1"
elif not frozen_version:
status = "Check"
style = "cyan1"
frozen_version = "Unable to determine version"
else:
report_production_dependencies.append((f"{dependency} == {frozen_version}", "bright_green"))
status = "OK"
style = "green3"

if "git+https://" in frozen_version:
frozen_version = frozen_version.replace("git+https://", "")
frozen_version = f"{frozen_version.split('@')[0]} TAG {frozen_version.split('@')[1]}"

table.add_row(name, frozen_version, latest_version, status, style=style)

if report_production_dependencies:
for item in report_production_dependencies:
click.secho(item[0], fg=item[1])
console.print(table)

# development dependencies
console.print("\n")
table = Table(title="Development Dependencies", box=box.MARKDOWN, show_lines=True)
table.add_column("Package", style="bright_white")
table.add_column("Installed Version", style="bright_white")
table.add_column("Latest Version", style="bright_white")
table.add_column("Status", style="bright_white")

# development dependencies
click.echo("\n")
click.secho("Development dependencies ...", **{"underline": True, "fg": "bright_white"})
for dependency in dev_dependencies:
c = client.get(dependency)
if isinstance(c, int):
Expand All @@ -132,31 +155,31 @@ def start(repo_url, branch_name, docker_file_name, docker_file_location=None):
continue

package = Package(c.json())
latest_version = package.latest_version
frozen_version = frozen_dependencies.get(dependency.lower())

if frozen_version is None:
# deals with cases such as package names that don't exist such as "python"
messages.append(f"{dependency}")
continue
development_packages.append(package)

if frozen_version != latest_version:
if "git+https://" not in frozen_version:
report_dev_dependencies.append((f"{dependency} {frozen_version} -> {latest_version}", "bright_yellow"))
for package in development_packages:
name = package.name
latest_version = package.latest_version
frozen_version = frozen_dependencies.get(name.lower())
if frozen_version and frozen_version != latest_version:
if "git+https://" in frozen_version:
status = "Check"
style = "bright_red"
else:
report_dev_dependencies.append((f"{dependency} {frozen_version} -> {latest_version}", "bright_red"))
status = "Outdated"
style = "bright_yellow"
elif not frozen_version:
status = "Check"
style = "magenta"
frozen_version = "Unable to determine version"
else:
report_dev_dependencies.append((f"{dependency} == {frozen_version}", "bright_green"))
status = "OK"
style = "bright_green"

if report_dev_dependencies:
for item in report_dev_dependencies:
click.secho(item[0], fg=item[1])
table.add_row(name, frozen_version, latest_version, status, style=style)

if len(messages) > 0:
click.secho("\n")
click.secho("Manual check required", **{"underline": True, "fg": "bright_white"})
for message in messages:
click.secho(f"{message}", fg="bright_red")
console.print(table)

# cleanup
frozen.clean_up_frozen()
Expand Down
Loading
Loading