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

Improved Info #477

Merged
merged 11 commits into from
May 16, 2024
12 changes: 10 additions & 2 deletions .github/workflows/push-pr_workflow.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,20 @@ jobs:
if: github.event_name == 'pull_request'

steps:
- uses: actions/checkout@v1
- name: Checkout code
uses: actions/checkout@v2
with:
fetch-depth: 0 # Checkout the whole history, in case the target is way far behind

- name: Check that CHANGELOG has been updated
run: |
# If this step fails, this means you haven't updated the CHANGELOG.md file with notes on your contribution.
git diff --name-only ${{ github.event.pull_request.base.sha }} ${{ github.sha }} | grep '^CHANGELOG.md$' && echo "Thanks for helping keep our CHANGELOG up-to-date!"
if git diff --name-only ${{ github.event.pull_request.base.sha }} ${{ github.sha }} | grep -q '^CHANGELOG.md$'; then
echo "Thanks for helping keep our CHANGELOG up-to-date!"
else
echo "Please update the CHANGELOG.md file with notes on your contribution."
exit 1
fi

Lint:
runs-on: ubuntu-latest
Expand Down
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Pytest-mock as a dependency for the test suite (necessary for using mocks and fixtures in the same test)

### Changed
- `merlin info` is cleaner and gives python package info
- merlin version now prints with every banner message

### Fixed
- Bugfix for output of `merlin example openfoam_wf_singularity`
- A bug with the CHANGELOG detection test when the target branch isn't in the ci runner history


## [1.12.1]
Expand Down
4 changes: 4 additions & 0 deletions merlin/ascii_art.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@

# pylint: skip-file

from merlin import VERSION


"""
Holds ascii art strings.
"""
Expand Down Expand Up @@ -127,6 +130,7 @@ def _make_banner():
for hat_line, name_line in zip(hat_lines, name_lines):
banner = banner + hat_line + name_line + "\n"

banner = banner + f" v. {VERSION}\n"
return banner


Expand Down
22 changes: 10 additions & 12 deletions merlin/display.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,9 @@
Manages formatting for displaying information to the console.
"""
import logging
import os
import pprint
import shutil
import subprocess
import time
import traceback
from datetime import datetime
Expand All @@ -46,6 +46,7 @@

from merlin.ascii_art import banner_small
from merlin.study.status_renderers import status_renderer_factory
from merlin.utils import get_package_versions


LOG = logging.getLogger("merlin")
Expand Down Expand Up @@ -212,9 +213,9 @@ def display_multiple_configs(files, configs):
# Might use args here in the future so we'll disable the pylint warning for now
def print_info(args): # pylint: disable=W0613
"""
Provide version and location information about python and pip to
facilitate user troubleshooting. 'merlin info' is a CLI tool only for
developer versions of Merlin.
Provide version and location information about python and packages to
facilitate user troubleshooting. Also provides info about server connections
and configurations.

:param `args`: parsed CLI arguments
"""
Expand All @@ -225,14 +226,11 @@ def print_info(args): # pylint: disable=W0613
print("Python Configuration")
print("-" * 25)
print("")
info_calls = ["which python3", "python3 --version", "which pip3", "pip3 --version"]
info_str = ""
for cmd in info_calls:
info_str += 'echo " $ ' + cmd + '" && ' + cmd + "\n"
info_str += "echo \n"
info_str += r"echo \"echo \$PYTHONPATH\" && echo $PYTHONPATH"
_ = subprocess.run(info_str, shell=True)
print("")
package_list = ["pip", "merlin", "maestrowf", "celery", "kombu", "amqp", "redis"]
package_versions = get_package_versions(package_list)
print(package_versions)
pythonpath = os.environ.get("PYTHONPATH")
print(f"$PYTHONPATH: {pythonpath}")


def display_status_task_by_task(status_obj: "DetailedStatus", test_mode: bool = False): # noqa: F821
Expand Down
26 changes: 26 additions & 0 deletions merlin/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,18 @@
import re
import socket
import subprocess
import sys
from contextlib import contextmanager
from copy import deepcopy
from datetime import datetime, timedelta
from types import SimpleNamespace
from typing import Callable, List, Optional, Union

import numpy as np
import pkg_resources
import psutil
import yaml
from tabulate import tabulate


try:
Expand Down Expand Up @@ -744,3 +747,26 @@ def ws_time_to_dt(ws_time: str) -> datetime:
minute = int(ws_time[11:13])
second = int(ws_time[13:])
return datetime(year, month, day, hour=hour, minute=minute, second=second)


def get_package_versions(package_list: List[str]) -> str:
"""
Return a table of the versions and locations of installed packages, including python.
If the package is not installed says "Not installed"

:param `package_list`: A list of packages.
:returns: A string that's a formatted table.
"""
table = []
for package in package_list:
try:
distribution = pkg_resources.get_distribution(package)
version = distribution.version
location = distribution.location
table.append([package, version, location])
except pkg_resources.DistributionNotFound:
table.append([package, "Not installed", "N/A"])

table.insert(0, ["python", sys.version.split()[0], sys.executable])
table_str = tabulate(table, headers=["Package", "Version", "Location"], tablefmt="simple")
return f"Python Packages\n\n{table_str}\n"
56 changes: 56 additions & 0 deletions tests/unit/utils/test_get_package_version.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import sys
from unittest.mock import patch

import pytest
from tabulate import tabulate

from merlin.utils import get_package_versions


fake_package_list = [
("python", sys.version.split()[0], sys.executable),
("merlin", "1.2.3", "/path/to/merlin"),
("celery", "4.5.1", "/path/to/celery"),
("kombu", "4.6.11", "/path/to/kombu"),
("redis", "3.5.3", "/path/to/redis"),
("amqp", "2.6.1", "/path/to/amqp"),
]


@pytest.fixture
def mock_get_distribution():
"""Mock call to get python distribution"""
with patch("pkg_resources.get_distribution") as mock_get_distribution:
mock_get_distribution.side_effect = [mock_distribution(*package) for package in fake_package_list[1:]]
yield mock_get_distribution


class mock_distribution:
"""A mock python distribution"""

def __init__(self, package, version, location):
self.key = package
self.version = version
self.location = location


def test_get_package_versions(mock_get_distribution):
"""Test ability to get versions and format as correct table"""
package_list = ["merlin", "celery", "kombu", "redis", "amqp"]
fake_table = tabulate(fake_package_list, headers=["Package", "Version", "Location"], tablefmt="simple")
expected_result = f"Python Packages\n\n{fake_table}\n"
result = get_package_versions(package_list)
assert result == expected_result


def test_bad_package():
"""Test that it only gets the things we have in our real environment."""
bogus_packages = ["garbage_package_1", "junk_package_2"]
result = get_package_versions(bogus_packages)
expected_data = [fake_package_list[0]] # python
for package in bogus_packages:
expected_data.append([package, "Not installed", "N/A"])

expected_table = tabulate(expected_data, headers=["Package", "Version", "Location"], tablefmt="simple")
expected_result = f"Python Packages\n\n{expected_table}\n"
assert result == expected_result
Loading