Skip to content

Commit

Permalink
[PROJECT] Adds type annotations to code base and lints it against Mypy
Browse files Browse the repository at this point in the history
  • Loading branch information
Samuel FORESTIER committed Nov 28, 2020
1 parent 2de12a1 commit 63425e1
Show file tree
Hide file tree
Showing 24 changed files with 100 additions and 65 deletions.
5 changes: 4 additions & 1 deletion .github/workflows/integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ jobs:
- name: Install required dependencies
run: |
python -m pip install --upgrade pip
pip install pylint stickytape pyinstaller pex
pip install pylint mypy stickytape pyinstaller pex
- name: Install module regularly
run: pip install .
Expand All @@ -37,6 +37,9 @@ jobs:
- name: Lint source code against Pylint
run: pylint archey/

- name: Lint source code against Mypy
run: mypy archey/

- name: Run our test suite
run: |
python setup.py -q test
Expand Down
6 changes: 4 additions & 2 deletions archey/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,13 @@
from enum import Enum
from concurrent.futures import ThreadPoolExecutor
from contextlib import ExitStack
from typing import Optional

from archey._version import __version__
from archey.output import Output
from archey.configuration import Configuration
from archey.distributions import Distributions
from archey.entry import Entry
from archey.processes import Processes
from archey.screenshot import take_screenshot
from archey.entries.user import User as e_User
Expand Down Expand Up @@ -68,7 +70,7 @@ class Entries(Enum):
WAN_IP = e_WanIP


def args_parsing():
def args_parsing() -> argparse.Namespace:
"""Simple wrapper to `argparse`"""
parser = argparse.ArgumentParser(prog='archey')
parser.add_argument(
Expand Down Expand Up @@ -139,7 +141,7 @@ def main():
)

# We will map this function onto our enabled entries to instantiate them.
def _entry_instantiator(entry):
def _entry_instantiator(entry: dict) -> Optional[Entry]:
# Based on **required** `type` field, instantiate the corresponding `Entry` object.
try:
return Entries[entry.pop('type')].value(
Expand Down
11 changes: 7 additions & 4 deletions archey/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@

import json

from typing import Dict, List

from archey._version import __version__
from archey.entry import Entry


class API:
Expand All @@ -13,17 +16,17 @@ class API:
At the moment, only JSON has been implemented.
Feel free to contribute to add other formats as needed.
"""
def __init__(self, entries):
def __init__(self, entries: List[Entry]):
self.entries = entries

def json_serialization(self, indent=None):
def json_serialization(self, indent: int = 0) -> str:
"""
JSON serialization of entries.
Set `indent` to the number of wanted output indentation tabs (2-space long).
Note: For Python < 3.6, the keys order is not guaranteed.
"""
document = {
document: Dict[str, Dict[str, object]] = {
'data': {},
'meta': {
'version': self._version_to_semver_segments(__version__),
Expand All @@ -40,7 +43,7 @@ def json_serialization(self, indent=None):
)

@staticmethod
def _version_to_semver_segments(version):
def _version_to_semver_segments(version: str) -> tuple:
"""Transforms string `version` to a tuple containing SemVer segments"""
return tuple(
map(
Expand Down
6 changes: 3 additions & 3 deletions archey/colors.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ def __str__(self):
)

@staticmethod
def escape_code_from_attrs(display_attrs):
def escape_code_from_attrs(display_attrs: str) -> str:
"""
Build and return an ANSI/ECMA-48 escape code string from passed display attributes.
"""
Expand All @@ -54,13 +54,13 @@ def escape_code_from_attrs(display_attrs):
return '\x1b[{}m'.format(display_attrs)

@staticmethod
def get_level_color(value, yellow_bpt, red_bpt):
def get_level_color(value: float, yellow_bpt: float, red_bpt: float) -> 'Colors':
"""Returns the best level color according to `value` compared to `{yellow,red}_bpt`"""
level_colors = (Colors.GREEN_NORMAL, Colors.YELLOW_NORMAL, Colors.RED_NORMAL)

return level_colors[bisect((yellow_bpt, red_bpt), value)]

@staticmethod
def remove_colors(string):
def remove_colors(string: str) -> str:
"""Simple DRY method to remove any ANSI/ECMA-48 color escape code from passed `string`"""
return ANSI_ECMA_REGEXP.sub('', string)
11 changes: 6 additions & 5 deletions archey/configuration.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
"""Archey configuration module"""

from copy import deepcopy

import json
import os
import sys
import json
from copy import deepcopy

from archey.constants import DEFAULT_CONFIG
from archey.singleton import Singleton
Expand Down Expand Up @@ -32,13 +33,13 @@ def __init__(self, config_path=None):
self._load_configuration(os.path.expanduser('~/.config/archey4/'))
self._load_configuration(os.getcwd())

def get(self, key, default=None):
def get(self, key: str, default=None):
"""
A binding method to imitate the `dict.get()` behavior.
"""
return self._config.get(key, default)

def _load_configuration(self, path):
def _load_configuration(self, path: str):
"""
A method handling configuration loading from a JSON file.
It will try to load any `config.json` present under `path`.
Expand Down Expand Up @@ -70,7 +71,7 @@ def _load_configuration(self, path):
self._close_and_restore_sys_stderr()

@classmethod
def update_recursive(cls, old_dict, new_dict):
def update_recursive(cls, old_dict: dict, new_dict: dict):
"""
A method for recursively merging dictionaries as `dict.update()` is not able to do this.
Original snippet taken from here : <https://gist.github.com/angstwad/bf22d1822c38a92ec0a9>
Expand Down
11 changes: 6 additions & 5 deletions archey/distributions.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

from enum import Enum
from subprocess import check_output
from typing import List, Optional

import distro

Expand Down Expand Up @@ -46,12 +47,12 @@ class Distributions(Enum):


@staticmethod
def get_distribution_identifiers():
def get_distribution_identifiers() -> List[str]:
"""Simple getter returning current supported distributions identifiers"""
return [d.value for d in Distributions.__members__.values()]

@staticmethod
def run_detection():
def run_detection() -> 'Distributions':
"""Entry point of Archey distribution detection logic"""
distribution = Distributions._detection_logic()

Expand Down Expand Up @@ -86,7 +87,7 @@ def run_detection():
return distribution

@staticmethod
def _detection_logic():
def _detection_logic() -> Optional['Distributions']:
"""Main distribution detection logic, relying on `distro`, handling _common_ cases"""
# Are we running on Windows ?
if sys.platform in ('win32', 'cygwin'):
Expand Down Expand Up @@ -115,12 +116,12 @@ def _detection_logic():
return None

@staticmethod
def get_distro_name(pretty=True):
def get_distro_name(pretty: bool = True) -> Optional[str]:
"""Simple wrapper to `distro` to return the current distribution _pretty_ name"""
return distro.name(pretty=pretty) or None

@staticmethod
def get_ansi_color():
def get_ansi_color() -> Optional[str]:
"""
Simple wrapper to `distro` to return the distribution preferred ANSI color.
See <https://www.freedesktop.org/software/systemd/man/os-release.html#ANSI_COLOR=>.
Expand Down
7 changes: 4 additions & 3 deletions archey/entries/cpu.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import re

from subprocess import check_output
from typing import Dict, List

from archey.entry import Entry

Expand Down Expand Up @@ -48,7 +49,7 @@ def __init__(self, *args, **kwargs):


@classmethod
def _parse_proc_cpuinfo(cls):
def _parse_proc_cpuinfo(cls) -> List[Dict[str, int]]:
"""Read `/proc/cpuinfo` and search for CPU model names occurrences"""
try:
with open('/proc/cpuinfo') as f_cpu_info:
Expand All @@ -59,7 +60,7 @@ def _parse_proc_cpuinfo(cls):
model_names = cls._MODEL_NAME_REGEXP.findall(cpu_info)
physical_ids = cls._PHYSICAL_ID_REGEXP.findall(cpu_info)

cpus_list = []
cpus_list: List[Dict[str, int]] = []

# Manually de-duplicates CPUs count.
for model_name, physical_id in zip(model_names, physical_ids):
Expand All @@ -77,7 +78,7 @@ def _parse_proc_cpuinfo(cls):
return cpus_list

@classmethod
def _parse_lscpu_output(cls):
def _parse_lscpu_output(cls) -> List[Dict[str, int]]:
"""Same operation but from `lscpu` output"""
cpu_info = check_output(
['lscpu'],
Expand Down
16 changes: 9 additions & 7 deletions archey/entries/disk.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
"""Disk usage detection class"""

import re
from subprocess import DEVNULL, PIPE, run

from csv import reader as csv_reader
from subprocess import DEVNULL, PIPE, run
from typing import Dict, List

from archey.colors import Colors
from archey.entry import Entry
Expand All @@ -16,15 +18,15 @@ def __init__(self, *args, **kwargs):
# Populate an output from `df`
self._disk_dict = self._get_df_output_dict()

config_filesystems = self.options.get('show_filesystems', ['local'])
config_filesystems: List[str] = self.options.get('show_filesystems', ['local'])
# See `Disk._get_df_output_dict` for the format we use in `self.value`.
if config_filesystems == ['local']:
self.value = self._get_local_filesystems()
else:
self.value = self._get_specified_filesystems(config_filesystems)


def _get_local_filesystems(self):
def _get_local_filesystems(self) -> Dict[str, dict]:
"""
Extracts local (i.e. /dev/xxx) filesystems for any *NIX from `self._disk_dict`,
returning a copy with those filesystems only.
Expand All @@ -41,7 +43,7 @@ def _get_local_filesystems(self):
device_path_regexp = re.compile(r'^\/dev\/(?:(?!loop|[rs]?vnd|lofi|dm).)+$')

# Build the dictionary
local_disk_dict = {}
local_disk_dict: Dict[str, dict] = {}
for mount_point, disk_data in self._disk_dict.items():
if (
device_path_regexp.match(disk_data['device_path'])
Expand All @@ -56,7 +58,7 @@ def _get_local_filesystems(self):
return local_disk_dict


def _get_specified_filesystems(self, specified_filesystems):
def _get_specified_filesystems(self, specified_filesystems: List[str]) -> Dict[str, dict]:
"""
Extracts the specified filesystems (if found) from `self._disk_dict`,
returning a copy with those filesystems only, preserving specified mount point names.
Expand Down Expand Up @@ -88,7 +90,7 @@ def _get_specified_filesystems(self, specified_filesystems):


@staticmethod
def _get_df_output_dict():
def _get_df_output_dict() -> Dict[str, dict]:
"""
Runs `df -P -k` and returns disks in a dict formatted as:
{
Expand Down Expand Up @@ -136,7 +138,7 @@ def _get_df_output_dict():


@staticmethod
def _blocks_to_human_readable(blocks, suffix='B'):
def _blocks_to_human_readable(blocks: float, suffix: str = 'B') -> str:
"""
Returns human-readable format of `blocks` supplied in kibibytes (1024 bytes).
Taken (and modified) from: <https://stackoverflow.com/a/1094933/13343912>
Expand Down
5 changes: 3 additions & 2 deletions archey/entries/distro.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Distribution and architecture detection class"""

from subprocess import check_output
from typing import Optional

from archey.distributions import Distributions
from archey.entry import Entry
Expand All @@ -21,15 +22,15 @@ def __init__(self, *args, **kwargs):
}

@staticmethod
def _fetch_architecture():
def _fetch_architecture() -> str:
"""Simple wrapper to `uname -m` returning the current system architecture"""
return check_output(
['uname', '-m'],
universal_newlines=True
).rstrip()

@staticmethod
def _fetch_android_release():
def _fetch_android_release() -> Optional[str]:
"""Simple method to fetch current release on Android systems"""
try:
release = check_output(
Expand Down
3 changes: 2 additions & 1 deletion archey/entries/gpu.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from subprocess import CalledProcessError, check_output
from itertools import islice
from typing import Iterator

from archey.entry import Entry

Expand All @@ -20,7 +21,7 @@ def __init__(self, *args, **kwargs):
self.value = list(islice(self._gpu_generator(), max_count))

@staticmethod
def _gpu_generator():
def _gpu_generator() -> Iterator[str]:
"""Based on `lspci` output, return a generator for video controllers names"""
try:
lspci_output = check_output(
Expand Down
3 changes: 2 additions & 1 deletion archey/entries/hostname.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Host name detection class"""

from subprocess import check_output
from typing import Optional

from archey.entry import Entry

Expand All @@ -18,7 +19,7 @@ def __init__(self, *args, **kwargs):
).rstrip()

@staticmethod
def _read_etc_hostname():
def _read_etc_hostname() -> Optional[str]:
try:
with open('/etc/hostname') as f_hostname:
return f_hostname.read().rstrip()
Expand Down
3 changes: 2 additions & 1 deletion archey/entries/lan_ip.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import sys

from itertools import islice
from typing import Iterator

try:
import netifaces
Expand Down Expand Up @@ -43,7 +44,7 @@ def __init__(self, *args, **kwargs):
)

@staticmethod
def _lan_ip_addresses_generator(addr_families):
def _lan_ip_addresses_generator(addr_families) -> Iterator[str]:
"""Generator yielding local IP address according to passed address families"""
# Loop through all available network interfaces.
for if_name in netifaces.interfaces():
Expand Down
Loading

0 comments on commit 63425e1

Please sign in to comment.