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

Use scooby for Versions #792

Merged
merged 13 commits into from
Feb 20, 2020
239 changes: 33 additions & 206 deletions SimPEG/Utils/printinfo.py
Original file line number Diff line number Diff line change
@@ -1,60 +1,11 @@
from __future__ import print_function

# Mandatory modules
import sys
import time
import numpy
import scipy
import textwrap
import platform
import multiprocessing

# Required packages
import SimPEG
import cython
import properties
import vectormath
import discretize
import pymatsolver

# Optional modules
try:
import IPython
from IPython.display import HTML, Pretty
except ImportError:
IPython = False
try:
import matplotlib
except ImportError:
matplotlib = False
try:
import ipywidgets
except ImportError:
ipywidgets = False
try:
import numexpr
except ImportError:
numexpr = False
try:
import mkl
except ImportError:
mkl = False

# Get mkl info from numexpr or mkl, if available
if mkl:
mklinfo = mkl.get_version_string()
elif numexpr:
mklinfo = numexpr.get_vml_version()
else:
mklinfo = False


class Versions:
import scooby

class Versions(scooby.Report):
"""Print date, time, and version information.

Print date, time, and package version information in any environment
(Jupyter notebook, IPython console, Python console, QT console), either as
html-table (notebook) or as plain text (anywhere).
Use scooby to print date, time, and package version information in any
environment (Jupyter notebook, IPython console, Python console, QT
console), either as html-table (notebook) or as plain text (anywhere).

Always shown are the OS, number of CPU(s), ``numpy``, ``scipy``,
``SimPEG``, ``cython``, ``properties``, ``vectormath``, ``discretize``,
Expand All @@ -64,171 +15,47 @@ class Versions:
``matplotlib``, and ``ipywidgets``. It also shows MKL information, if
available.

All modules provided in ``add_pckg`` are also shown. They have to be
imported before ``Versions`` is called.

This script is an adapted version of ``empymod.printversions``,
(https://empymod.github.io) which itself was heavily inspired by

- ipynbtools.py from qutip https://github.com/qutip
- watermark.py from https://github.com/rasbt/watermark
All modules provided in ``add_pckg`` are also shown.


**Parameters**

Parameters
----------
add_pckg : packages, optional
Package or list of packages to add to output information (must be
imported beforehand).

ncol : int, optional
Number of package-columns in html table; only has effect if
``mode='HTML'`` or ``mode='html'``. Defaults to 3.
Number of package-columns in html table (no effect in text-version);
Defaults to 3.

text_width : int, optional
The text width for non-HTML display modes

sort : bool, optional
Sort the packages when the report is shown

**Examples**

Examples
--------

>>> import pytest
>>> import dateutil
>>> from SimPEG import Versions
>>> Versions() # Default values
>>> Versions(pytest) # Provide additional package
>>> Versions() # Default values
>>> Versions(pytest) # Provide additional package
>>> Versions([pytest, dateutil], ncol=5) # Define nr of columns

"""

def __init__(self, add_pckg=None, ncol=4):
"""Initiate and add packages and number of columns to self."""
self.add_pckg = self._get_packages(add_pckg)
self.ncol = int(ncol)

def __repr__(self):
r"""Plain-text version information."""

# Width for text-version
n = 54
text = u'\n' + ''.join(n*['-']) + '\n'

# Date and time info as title
text += time.strftime(' %a %b %d %H:%M:%S %Y %Z\n\n')

# OS and CPUs
text += '{:>15}'.format(platform.system())+' : OS\n'
text += '{:>15}'.format(multiprocessing.cpu_count())+' : CPU(s)\n'

# Loop over packages
for pckg in self.add_pckg:
text += '{:>15} : {}\n'.format(pckg.__version__, pckg.__name__)

# sys.version
text += '\n'
for txt in textwrap.wrap(sys.version, n-4):
text += ' '+txt+'\n'

# mkl version
if mklinfo:
text += '\n'
for txt in textwrap.wrap(mklinfo, n-4):
text += ' '+txt+'\n'

# Finish
text += ''.join(n*['-'])
return text

def _repr_html_(self):
"""HTML-rendered version information."""

# Define html-styles
border = "border: 2px solid #fff;'"

def colspan(html, txt, ncol, nrow):
"""Print txt in a row spanning whole table."""
html += " <tr>\n"
html += " <td style='text-align: center; "
if nrow == 0:
html += "font-weight: bold; font-size: 1.2em; "
elif nrow % 2 == 0:
html += "background-color: #ddd;"
html += border + " colspan='"
html += str(2*ncol)+"'>%s</td>\n" % txt
html += " </tr>\n"
return html

def cols(html, version, name, ncol, i):
"""Print package information in two cells."""

# Check if we have to start a new row
if i > 0 and i % ncol == 0:
html += " </tr>\n"
html += " <tr>\n"

html += " <td style='text-align: right; background-color: #ccc;"
html += " " + border + ">%s</td>\n" % version

html += " <td style='text-align: left; "
html += border + ">%s</td>\n" % name

return html, i+1

# Start html-table
html = "<table style='border: 3px solid #ddd;'>\n"

# Date and time info as title
html = colspan(html, time.strftime('%a %b %d %H:%M:%S %Y %Z'),
self.ncol, 0)

# OS and CPUs
html += " <tr>\n"
html, i = cols(html, platform.system(), 'OS', self.ncol, 0)
html, i = cols(html, multiprocessing.cpu_count(), 'CPU(s)',
self.ncol, i)

# Loop over packages
for pckg in self.add_pckg:
html, i = cols(html, pckg.__version__, pckg.__name__, self.ncol, i)
# Fill up the row
while i % self.ncol != 0:
html += " <td style= " + border + "></td>\n"
html += " <td style= " + border + "></td>\n"
i += 1
# Finish row
html += " </tr>\n"

# sys.version
html = colspan(html, sys.version, self.ncol, 1)

# mkl version
if mklinfo:
html = colspan(html, mklinfo, self.ncol, 2)

# Finish table
html += "</table>"

return html

@staticmethod
def _get_packages(add_pckg):
"""Create list of packages."""

# Mandatory packages
pckgs = [numpy, scipy, SimPEG, cython, properties, vectormath,
discretize, pymatsolver]

# Optional packages
for module in [IPython, ipywidgets, matplotlib]:
if module:
pckgs += [module]

# Cast and add add_pckg
if add_pckg is not None:

# Cast add_pckg
if isinstance(add_pckg, tuple):
add_pckg = list(add_pckg)

if not isinstance(add_pckg, list):
add_pckg = [add_pckg, ]

# Add add_pckg
pckgs += add_pckg

return pckgs
def __init__(self, add_pckg=None, ncol=3, text_width=80, sort=False):
"""Initiate a scooby.Report instance."""

# Mandatory packages.
core = ['SimPEG', 'discretize', 'pymatsolver', 'vectormath',
'properties', 'numpy', 'scipy', 'cython']

# Optional packages.
optional = ['IPython', 'matplotlib', 'ipywidgets']

super().__init__(additional=add_pckg, core=core, optional=optional,
ncol=ncol, text_width=text_width, sort=sort)
1 change: 1 addition & 0 deletions requirements_dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ wheel
pytest
jupyter
sphinxcontrib-napoleon
scooby
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
'vectormath>=0.2.0',
'discretize>=0.4.0',
'geoana>=0.0.4'
'scooby>=0.3.0',
Copy link
Member

@lheagy lheagy Jul 2, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think of making this a soft dependency? Since it isn't strictly necessary for running simulations or inversions with SimPEG, I would be inclined to make it a soft rather than a hard dependency

],
author="Rowan Cockett",
author_email="rowanc1@gmail.com",
Expand Down
77 changes: 13 additions & 64 deletions tests/utils/test_printinfo.py
Original file line number Diff line number Diff line change
@@ -1,75 +1,24 @@
import pip
import scooby
import unittest
import sys
import io

# Optional imports
try:
import IPython
except ImportError:
IPython = False

from SimPEG import Versions


class TestVersion(unittest.TestCase):

def catch_version_stdout(self, *args, **kwargs):

# Check the default
stdout = sys.stdout
sys.stdout = io.StringIO()

# Print text version
print(Versions(*args, **kwargs).__repr__())

# catch the output
out1 = sys.stdout.getvalue()
sys.stdout = stdout

# Check the default
stdout = sys.stdout
sys.stdout = io.StringIO()

# Print html version
print(Versions(*args, **kwargs)._repr_html_())

# catch the output
out2 = sys.stdout.getvalue()
sys.stdout = stdout

return out1, out2

def test_version_defaults(self):

# Check the default
out1_text, out1_html = self.catch_version_stdout(pip)

# Check one of the standard packages
assert 'numpy' in out1_text
assert 'numpy' in out1_html

# Providing a package as a tuple
out2_text, out2_html = self.catch_version_stdout(add_pckg=(pip,))

# Check the provided package, with number
assert pip.__version__ + ' : pip' in out2_text
assert pip.__version__ in out2_html
assert ">pip</td>" in out2_html

# Providing a package as a list
out3_text, out3_html = self.catch_version_stdout(add_pckg=[pip])

assert 'numpy' in out3_text
assert 'td style=' in out3_html

# Check row of provided package, with number
teststr = "<td style='text-align: right; background-color: #ccc; "
teststr += "border: 2px solid #fff;'>"
teststr += pip.__version__
teststr += "</td>\n <td style='"
teststr += "text-align: left; border: 2px solid #fff;'>pip</td>"
assert teststr in out3_html
# Reporting is now done by the external package scooby.
# We just ensure the shown packages do not change (core and optional).
out1 = Versions()
out2 = scooby.Report(
core=['SimPEG', 'discretize', 'pymatsolver', 'vectormath',
'properties', 'numpy', 'scipy', 'cython'],
optional=['IPython', 'matplotlib', 'ipywidgets'],
ncol=4)

# Ensure they're the same; exclude initial time to avoid errors due
# to second-change.
assert out1.__repr__()[115:] == out2.__repr__()[115:]

if __name__ == '__main__':
unittest.main()