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

add type hints #109

Merged
merged 9 commits into from
Oct 1, 2023
Merged
Show file tree
Hide file tree
Changes from 2 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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,4 @@ coverage.xml
*,cover

.tox
venv
5 changes: 3 additions & 2 deletions scooby/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@
import argparse
import importlib
import sys
from typing import Any, Dict, Optional

import scooby
from scooby.report import Report


def main(args=None):
def main(args: Optional[list[str]] = None):
"""Parse command line inputs of CLI interface."""
# If not explicitly called, catch arguments
if args is None:
Expand Down Expand Up @@ -51,7 +52,7 @@ def main(args=None):
act(vars(parser.parse_args(args)))


def act(args_dict):
def act(args_dict: Dict[str, Any]) -> None:
"""Act upon CLI inputs."""
# Quick exit if only scooby version.
if args_dict.pop('version'):
Expand Down
29 changes: 13 additions & 16 deletions scooby/knowledge.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import os
import sys
import sysconfig
from typing import Callable, Dict, Literal, Tuple

# Define unusual version locations
VERSION_ATTRIBUTES = {
Expand All @@ -21,20 +22,20 @@
}


def get_pyqt5_version():
def get_pyqt5_version() -> str:
"""Return the PyQt5 version."""
from PyQt5.Qt import PYQT_VERSION_STR

return PYQT_VERSION_STR


VERSION_METHODS = {
VERSION_METHODS: Dict[str, Callable[[], str]] = {
'PyQt5': get_pyqt5_version,
}


# Check the environments
def in_ipython():
def in_ipython() -> bool:
"""Check if we are in a IPython environment.

Returns
Expand All @@ -49,7 +50,7 @@ def in_ipython():
return False


def in_ipykernel():
def in_ipykernel() -> bool:
"""Check if in a ipykernel (most likely Jupyter) environment.

Warning
Expand All @@ -67,27 +68,23 @@ def in_ipykernel():
ipykernel = False
if in_ipython():
try:
ipykernel = type(get_ipython()).__module__.startswith('ipykernel.')
ipykernel: bool = type(get_ipython()).__module__.startswith('ipykernel.')
except NameError:
pass
return ipykernel


def get_standard_lib_modules():
def get_standard_lib_modules() -> set[str]:
"""Return a set of the names of all modules in the standard library."""
site_path = sysconfig.get_path('stdlib')
if getattr(sys, 'frozen', False): # within pyinstaller
lib_path = os.path.join(site_path, '..')
if os.path.isdir(lib_path):
names = os.listdir(lib_path)
else:
names = []
names: list[str] = []

stdlib_pkgs = []
for name in names:
if name.endswith(".py"):
stdlib_pkgs.append(name[:-3])
stdlib_pkgs = set(stdlib_pkgs)
stdlib_pkgs = {name[:-3] for name in names if name.endswith(".py")}

else:
names = os.listdir(site_path)
Expand Down Expand Up @@ -116,7 +113,7 @@ def get_standard_lib_modules():
return stdlib_pkgs


def version_tuple(v):
def version_tuple(v: str) -> Tuple[int, ...]:
"""Convert a version string to a tuple containing ints.

Non-numeric version strings will be converted to 0. For example:
Expand All @@ -135,7 +132,7 @@ def version_tuple(v):
if len(split_v) > 3:
raise ValueError('Version strings containing more than three parts ' 'cannot be parsed')

vals = []
vals: list[int] = []
for item in split_v:
if item.isnumeric():
vals.append(int(item))
Expand All @@ -145,7 +142,7 @@ def version_tuple(v):
return tuple(vals)


def meets_version(version, meets):
def meets_version(version: str, meets: str) -> bool:
"""Check if a version string meets a minimum version.

This is a simplified way to compare version strings. For a more robust
Expand Down Expand Up @@ -190,7 +187,7 @@ def meets_version(version, meets):
return True


def get_filesystem_type():
def get_filesystem_type() -> str | Literal[False]:
Gryfenfer97 marked this conversation as resolved.
Show resolved Hide resolved
"""Get the type of the file system at the path of the scooby package."""
try:
import psutil # lazy-load see PR#85
Expand Down
Empty file added scooby/py.typed
Empty file.
79 changes: 42 additions & 37 deletions scooby/report.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import sys
import time
from types import ModuleType
from typing import Any, Dict, Literal, Optional, Tuple, cast

from .knowledge import (
VERSION_ATTRIBUTES,
Expand All @@ -22,8 +23,12 @@
class PlatformInfo:
"""Internal helper class to access details about the computer platform."""

def __init__(self):
self._mkl_info: Optional[str] # for typing purpose
self._filesystem: str | Literal[False]

@property
def system(self):
def system(self) -> str:
"""Return the system/OS name.

E.g. ``'Linux'``, ``'Windows'``, or ``'Java'``. An empty string is
Expand All @@ -32,25 +37,25 @@ def system(self):
return platform().system()

@property
def platform(self):
def platform(self) -> str:
"""Return the platform."""
return platform().platform()

@property
def machine(self):
def machine(self) -> str:
"""Return the machine type, e.g. 'i386'.

An empty string is returned if the value cannot be determined.
"""
return platform().machine()

@property
def architecture(self):
def architecture(self) -> str:
"""Return the bit architecture used for the executable."""
return platform().architecture()[0]

@property
def cpu_count(self):
def cpu_count(self) -> int:
"""Return the number of CPUs in the system."""
if not hasattr(self, '_cpu_count'):
import multiprocessing # lazy-load see PR#85
Expand All @@ -59,7 +64,7 @@ def cpu_count(self):
return self._cpu_count

@property
def total_ram(self):
def total_ram(self) -> str:
"""Return total RAM info.

If not available, returns 'unknown'.
Expand All @@ -77,7 +82,7 @@ def total_ram(self):
return self._total_ram

@property
def mkl_info(self):
def mkl_info(self) -> Optional[str]:
"""Return MKL info.

If not available, returns 'unknown'.
Expand All @@ -98,21 +103,21 @@ def mkl_info(self):

# Get mkl info from numexpr or mkl, if available
if mkl:
self._mkl_info = mkl.get_version_string()
self._mkl_info = cast(str, mkl.get_version_string())
elif numexpr:
self._mkl_info = numexpr.get_vml_version()
self._mkl_info = cast(str, numexpr.get_vml_version())
else:
self._mkl_info = None

return self._mkl_info

@property
def date(self):
def date(self) -> str:
"""Return the date formatted as a string."""
return time.strftime('%a %b %d %H:%M:%S %Y %Z')

@property
def filesystem(self):
def filesystem(self) -> str | Literal[False]:
"""Get the type of the file system at the path of the scooby package."""
if not hasattr(self, '_filesystem'):
self._filesystem = get_filesystem_type()
Expand All @@ -122,25 +127,25 @@ def filesystem(self):
class PythonInfo:
"""Internal helper class to access Python info and package versions."""

def __init__(self, additional, core, optional, sort):
def __init__(self, additional: Optional[list[str | ModuleType]], core: Optional[list[str | ModuleType]], optional: Optional[list[str | ModuleType]], sort: bool):
"""Initialize python info."""
self._packages = {} # Holds name of packages and their version
self._packages: Dict[str, Any] = {} # Holds name of packages and their version
self._sort = sort

# Add packages in the following order:
self._add_packages(additional) # Provided by the user
self._add_packages(core) # Provided by a module dev
self._add_packages(optional, optional=True) # Optional packages

def _add_packages(self, packages, optional=False):
def _add_packages(self, packages: Optional[list[str | ModuleType]], optional: bool = False):
"""Add all packages to list; optional ones only if available."""
# Ensure arguments are a list
if isinstance(packages, (str, ModuleType)):
pckgs = [
packages,
]
elif packages is None or len(packages) < 1:
pckgs = list()
pckgs: list[str | ModuleType] = list()
else:
pckgs = list(packages)

Expand All @@ -151,12 +156,12 @@ def _add_packages(self, packages, optional=False):
self._packages[name] = version

@property
def sys_version(self):
def sys_version(self) -> str:
"""Return the system version."""
return sys.version

@property
def python_environment(self):
def python_environment(self) -> Literal['Jupyter', 'IPython', 'Python']:
"""Return the python environment."""
if in_ipykernel():
return 'Jupyter'
Expand All @@ -165,15 +170,15 @@ def python_environment(self):
return 'Python'

@property
def packages(self):
def packages(self) -> dict[str, Any]:
"""Return versions of all packages.

Includes available and unavailable/unknown.

"""
pckg_dict = dict(self._packages)
if self._sort:
packages = {}
packages: Dict[str, Any] = {}
for name in sorted(pckg_dict.keys(), key=lambda x: x.lower()):
packages[name] = pckg_dict[name]
pckg_dict = packages
Expand Down Expand Up @@ -220,15 +225,15 @@ class Report(PlatformInfo, PythonInfo):

def __init__(
self,
additional=None,
core=None,
optional=None,
ncol=4,
text_width=80,
sort=False,
extra_meta=None,
max_width=None,
):
additional: Optional[list[str | ModuleType]] = None,
core: Optional[list[str | ModuleType]] = None,
optional: Optional[list[str | ModuleType]] = None,
ncol: int = 4,
text_width: int = 80,
sort: bool = False,
extra_meta: Optional[Tuple[str, str]] = None,
max_width: Optional[int] = None,
) -> None:
"""Initialize report."""
# Set default optional packages to investigate
if optional is None:
Expand All @@ -251,7 +256,7 @@ def __init__(
extra_meta = []
self._extra_meta = extra_meta

def __repr__(self):
def __repr__(self) -> str:
"""Return Plain-text version information."""
import textwrap # lazy-load see PR#85

Expand Down Expand Up @@ -303,12 +308,12 @@ def __repr__(self):

return text

def _repr_html_(self):
def _repr_html_(self) -> str:
"""Return HTML-rendered version information."""
# Define html-styles
border = "border: 1px solid;'"

def colspan(html, txt, ncol, nrow):
def colspan(html: str, txt: str, ncol: int, nrow: int) -> str:
r"""Print txt in a row spanning whole table."""
html += " <tr>\n"
html += " <td style='"
Expand All @@ -323,7 +328,7 @@ def colspan(html, txt, ncol, nrow):
html += " </tr>\n"
return html

def cols(html, version, name, ncol, i):
def cols(html: str, version: str, name: str, ncol: int, i: int) -> tuple[str, int]:
r"""Print package information in two cells."""
# Check if we have to start a new row
if i > 0 and i % ncol == 0:
Expand Down Expand Up @@ -385,9 +390,9 @@ def cols(html, version, name, ncol, i):

return html

def to_dict(self):
def to_dict(self) -> dict[str, str]:
"""Return report as dict for storage."""
out = {}
out: Dict[str, str] = {}

# Date and time info
out['Date'] = self.date
Expand Down Expand Up @@ -419,7 +424,7 @@ def to_dict(self):
return out


def pkg_resources_version_fallback(name):
def pkg_resources_version_fallback(name: str) -> Optional[str]:
"""Use package-resources to get the distribution version."""
try:
from pkg_resources import DistributionNotFound, get_distribution
Expand All @@ -433,7 +438,7 @@ def pkg_resources_version_fallback(name):


# This functionaliy might also be of interest on its own.
def get_version(module):
def get_version(module: str | ModuleType) -> Tuple[str, Optional[str]]:
"""Get the version of ``module`` by passing the package or it's name.

Parameters
Expand Down Expand Up @@ -506,7 +511,7 @@ def get_version(module):
return name, VERSION_NOT_FOUND


def platform():
def platform() -> ModuleType:
"""Return platform as lazy load; see PR#85."""
import platform

Expand Down
Loading