libs.zazac.link

simplesuit.sys v0.2.1

Infos système : batterie, CPU, RAM, disque, réseau, son

Inclus dans simplesuit : installe simplesuit pour l'avoir.

import simplesuit.sys · PyPI : simplesuit · Python 3.9+ · dépend de psutil

Guide

Les infos de ta machine en une ligne chacune : batterie, processeur, mémoire, disque, réseau, son, système… Toujours dans un format prêt à afficher ("6.6 Go", "7h43"). Et une saisie de mot de passe avec des *.

Tout d'un coup#

python
from simplesuit import sys as ss

for nom, valeur in ss.summary().items():
    print(f"{nom:<10} {valeur}")
résultat
user       izac
hostname   MacBook-de-Izac
os         macOS 27.0.1
python     3.9.6
cpu        12.4% (8 cores, arm64)
ram        63.2% (6.6 Go / 16.0 Go)
disk       33.4% (306.8 Go free / 460.4 Go)
battery    66% (plugged)
time_left  -
uptime     26h12
processes  412
local_ip   192.168.1.42
online     True
volume     31%

Les fonctions, par thème#

ThèmeFonctionsExemple
Batteriebattery(), plugged(), time_left(), time_left_text()66, True, 27780, "7h43"
Processeurcpu(), cpu_cores(), cpu_type()12.4, 8, "arm64"
Mémoireram(), ram_used(), ram_total()63.2, "6.6 Go", "16.0 Go"
Disquedisk(), disk_free(), disk_total()33.4, "306.8 Go", "460.4 Go"
Réseauonline(), local_ip(), public_ip(), wifi_connected(), wifi(), net_sent(), net_received()True, "192.168.1.42"
Systèmehostname(), user(), os_name(), os_version(), python_version(), python_path(), uptime(), uptime_text(), processes(), home()"macOS", "26h12"
Son (macOS)volume(), muted()31, False
Textehuman_size(octets), human_time(secondes)"1.5 Ko", "7h43"

Les pourcentages sont des nombres (pour comparer : if ss.ram() > 90:), les tailles et les durées sont des textes (pour afficher).

None veut dire « pas disponible »

Pas de batterie (ordi fixe), volume inconnu (pas sur Mac), hors ligne pour public_ip()… La fonction renvoie None au lieu de planter. Pense à ss.battery() or 0 avant un calcul.

Demander un mot de passe#

ss.password(texte) lit un mot de passe en affichant un * par lettre tapée (au lieu de rien du tout avec getpass). Le retour arrière efface, Ctrl+U efface tout.

python
from simplesuit import sys as ss

try:
    mdp = ss.password("password > ")
except (KeyboardInterrupt, EOFError):   # Ctrl+C ou Ctrl+D
    exit("\nannulé")
résultat
password > ********
  • mask="•" pour changer le caractère : ss.password("PIN : ", mask="•").
  • Si l'entrée n'est pas un clavier (echo 1234 | python3 main.py), il lit simplement la ligne.

Le mot de passe reste en mémoire en clair

password() cache la saisie à l'écran, c'est tout. Ne l'affiche pas, ne l'écris pas dans un fichier : envoie-le (chiffré, avec capy par exemple) et stocke seulement son hash côté serveur.

Astuces et pièges#

cpu() prend 0,3 seconde

Pour mesurer l'utilisation du processeur, il faut comparer deux instants : cpu() attend 0,3 s. Dans une boucle qui se rafraîchit, c'est très bien ; dans un programme qui doit répondre vite, appelle-le une seule fois.

public_ip() demande internet

Elle interroge un site extérieur : un peu lente, et None hors ligne. C'est pour ça qu'elle n'est pas dans summary().

Le nom du wifi sur macOS récent

Depuis macOS 14, Apple cache le nom du réseau aux programmes pour protéger la vie privée : ss.wifi() renvoie souvent None. ss.wifi_connected() marche toujours.

Un tableau de bord qui se rafraîchit

Efface l'écran avec print("\033[2J\033[H", end=""), affiche tes infos, attends une seconde, recommence. Voir l'exemple « Mini tableau de bord ».

Pourquoi psutil ?

C'est la bibliothèque de référence pour lire CPU, RAM, disque et batterie sur Mac, Linux et Windows. Elle s'installe toute seule avec simplesuit, et seul simplesuit.sys l'utilise.

Exemples

Résumé du système

Toutes les infos en une ligne de code.

resume.py
from simplesuit import sys as ss

for nom, valeur in ss.summary().items():
    print(f"{nom:>10} : {valeur}")

Jauge de batterie en couleur

sys donne les chiffres, colors et icons les mettent en forme.

jauge_batterie.py
from simplesuit import colors as sc
from simplesuit import icons as si
from simplesuit import sys as ss

niveau = ss.battery()
if niveau is None:
    print("Pas de batterie sur cet ordinateur")
else:
    couleur = "lime" if niveau > 50 else "orange" if niveau > 20 else "red"
    barre = "█" * (niveau // 5) + "░" * (20 - niveau // 5)
    reste = ss.time_left_text() or "-"
    print(si.battery(niveau, ss.plugged()), sc.color(barre, couleur), f"{niveau} %  (reste {reste})")

Réseau et machine

Connexion, adresses IP, mémoire, disque et temps depuis le démarrage.

reseau_et_machine.py
from simplesuit import sys as ss

print("Connecté à internet :", ss.online())
print("IP locale :", ss.local_ip())
print("IP publique :", ss.public_ip())          # Demande internet (≈ 1 s)
print("Données reçues :", ss.net_received())

print(f"Processeur : {ss.cpu()} % ({ss.cpu_cores()} cœurs, {ss.cpu_type()})")
print(f"Mémoire : {ss.ram_used()} / {ss.ram_total()}")
print(f"Disque : {ss.disk_free()} libres sur {ss.disk_total()}")
print("Allumé depuis :", ss.uptime_text())

Demander un mot de passe avec des *

password() affiche une * par lettre tapée. Retour arrière efface, Ctrl+U efface tout, Ctrl+C ou Ctrl+D annulent (KeyboardInterrupt / EOFError).

mot_de_passe.py
from simplesuit import colors as sc
from simplesuit import sys as ss

try:
    mdp = ss.password("password > ")
    confirmation = ss.password("encore une fois > ", mask="•")
except (KeyboardInterrupt, EOFError):
    exit("\nannulé")

if mdp != confirmation:
    exit(sc.red("Les deux mots de passe sont différents"))
print(sc.green(f"OK, {len(mdp)} caractères"))  # On n'affiche jamais le mot de passe lui-même

Mini tableau de bord

Toutes les secondes : on efface l'écran, puis on affiche les infos avec des icônes et des couleurs. Ctrl+C pour quitter.

tableau_de_bord.py
import time

from simplesuit import colors as sc
from simplesuit import icons as si
from simplesuit import sys as ss


# Une barre de progression colorée selon le niveau (vert, orange, rouge)
def barre(pourcentage, largeur=20):
    plein = round(pourcentage / 100 * largeur)
    couleur = "mint" if pourcentage < 60 else "orange" if pourcentage < 85 else "tomato"
    return sc.color("█" * plein, couleur) + sc.color("░" * (largeur - plein), "dimgray")


try:
    while True:
        cpu, ram, disque = ss.cpu(), ss.ram(), ss.disk()  # cpu() attend 0,3 s : c'est notre pause
        print("\033[2J\033[H", end="")                   # Effacer l'écran, curseur en haut
        print(sc.gradient(f" {ss.hostname()} ", "cyan", "magenta", styles="bold"), "\n")
        print(f"{si.cpu}  CPU    {barre(cpu)} {cpu:5.1f} %")
        print(f"{si.memory}  RAM    {barre(ram)} {ram:5.1f} %  {ss.ram_used()}")
        print(f"{si.disk}  Disque {barre(disque)} {disque:5.1f} %  {ss.disk_free()} libres")
        if ss.battery() is not None:
            print(f"{si.battery(ss.battery(), bool(ss.plugged()))}  Batterie {ss.battery()} %")
        print(sc.color(f"\nAllumé depuis {ss.uptime_text()} · Ctrl+C pour quitter", "gray"))
        time.sleep(0.7)
except KeyboardInterrupt:
    print()

Son propre ls, avec des icônes

file_icon() choisit l'icône selon le type : dossier, .py, .json, .md, image, musique… Les dossiers en bleu et en premier, les tailles alignées à droite.

mon_ls.py
import os

from simplesuit import colors as sc
from simplesuit import icons as si
from simplesuit import sys as ss

noms = sorted(os.listdir("."), key=lambda n: (not os.path.isdir(n), n.lower()))

for nom in noms:
    if nom.startswith("."):
        continue
    if os.path.isdir(nom):
        print(sc.color(f"{si.file_icon(nom)}  {nom}/", "cornflowerblue", styles="bold"))
    else:
        taille = ss.human_size(os.path.getsize(nom))
        print(f"{si.file_icon(nom)}  {nom:<30} {sc.color(f'{taille:>9}', 'gray')}")

Référence

Fonctions

human_size(octets)

Transformer un nombre d'octets en texte lisible

Ex1536 -> "1.5 Ko" / 8589934592 -> "8.0 Go"

Voir le code
python
def human_size(octets):
    for unit in ["o", "Ko", "Mo", "Go", "To"]:
        if octets < 1024:
            return f"{octets:.1f} {unit}"
        octets /= 1024
    return f"{octets:.1f} Po"

human_time(seconds)

Transformer des secondes en texte lisible

Ex27780 -> "7h43" / 300 -> "5min"

Voir le code
python
def human_time(seconds):
    hours, rest = divmod(int(seconds), 3600)
    minutes = rest // 60
    if hours:
        return f"{hours}h{minutes:02d}"
    return f"{minutes}min"

battery()

Pourcentage de batterie (ex : 64), None si pas de batterie

Voir le code
python
def battery():
    bat = psutil.sensors_battery()
    return bat.percent if bat else None

plugged()

Chargeur branché ? (True / False), None si pas de batterie

Voir le code
python
def plugged():
    bat = psutil.sensors_battery()
    return bat.power_plugged if bat else None

time_left()

Temps restant en secondes, None si inconnu ou si branché

Voir le code
python
def time_left():
    bat = psutil.sensors_battery()
    # secsleft est négatif quand le temps est inconnu ou illimité
    if bat is None or bat.secsleft < 0:
        return None
    return bat.secsleft

time_left_text()

Temps restant en texte (ex : "7h43"), None si inconnu

Voir le code
python
def time_left_text():
    seconds = time_left()
    return human_time(seconds) if seconds is not None else None

cpu()

Utilisation du processeur en % (ex : 12.5)

Mesure pendant 0.3 seconde : la 1ère mesure instantanée de psutil vaut toujours 0

Voir le code
python
def cpu():
    return psutil.cpu_percent(interval=0.3)

cpu_cores()

Nombre de cœurs (ex : 8)

Voir le code
python
def cpu_cores():
    return psutil.cpu_count()

cpu_type()

Type de processeur (ex : "arm64" pour Apple Silicon, "x86_64" pour Intel)

Voir le code
python
def cpu_type():
    return platform.machine()

ram()

RAM utilisée en % (ex : 63.2)

Voir le code
python
def ram():
    return psutil.virtual_memory().percent

ram_used()

RAM utilisée en texte (ex : "6.6 Go")

On calcule total - disponible : c'est ce qui correspond au pourcentage de ram()

Voir le code
python
def ram_used():
    mem = psutil.virtual_memory()
    return human_size(mem.total - mem.available)

ram_total()

RAM totale en texte (ex : "16.0 Go")

Voir le code
python
def ram_total():
    return human_size(psutil.virtual_memory().total)

disk(path='/')

Disque utilisé en % (ex : 22.1)

On calcule à partir de l'espace libre : sur Mac, le disque est découpé en plusieurs

volumes qui partagent la même place, et le % de psutil ne compte que le volume "/"

Voir le code
python
def disk(path="/"):
    usage = psutil.disk_usage(path)
    return round((usage.total - usage.free) / usage.total * 100, 1)

disk_free(path='/')

Espace libre en texte (ex : "120.4 Go")

Voir le code
python
def disk_free(path="/"):
    return human_size(psutil.disk_usage(path).free)

disk_total(path='/')

Taille totale du disque en texte (ex : "460.4 Go")

Voir le code
python
def disk_total(path="/"):
    return human_size(psutil.disk_usage(path).total)

online()

Connecté à internet ? (True / False)

Voir le code
python
def online():
    try:
        socket.create_connection(("1.1.1.1", 53), timeout=2).close()
        return True
    except OSError:
        return False

local_ip()

Adresse IP locale, sur ton réseau (ex : "192.168.1.42")

Voir le code
python
def local_ip():
    try:
        # Astuce : on "vise" une adresse externe sans rien envoyer, juste pour savoir quelle IP on utilise
        with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
            s.connect(("1.1.1.1", 80))
            return s.getsockname()[0]
    except OSError:
        return None

public_ip()

Adresse IP publique, vue depuis internet (None si hors ligne)

Voir le code
python
def public_ip():
    try:
        with urllib.request.urlopen("https://api.ipify.org", timeout=3) as response:
            return response.read().decode()
    except OSError:
        return None

wifi_connected()

Wifi activé et connecté ? (True / False)

Voir le code
python
def wifi_connected():
    return _run(["ipconfig", "getifaddr", "en0"]) != ""

wifi()

Nom du réseau wifi (ex : "Livebox-1234")

⚠️ Depuis macOS 14, Apple cache ce nom pour la vie privée -> renvoie souvent None

Voir le code
python
def wifi():
    for line in _run(["ipconfig", "getsummary", "en0"]).splitlines():
        line = line.strip()
        if line.startswith("SSID :"):
            name = line.split(":", 1)[1].strip()
            return None if name == "<redacted>" else name
    return None

net_sent()

Données envoyées depuis le démarrage, en texte (ex : "902.5 Mo")

Voir le code
python
def net_sent():
    return human_size(psutil.net_io_counters().bytes_sent)

net_received()

Données reçues depuis le démarrage, en texte (ex : "12.3 Go")

Voir le code
python
def net_received():
    return human_size(psutil.net_io_counters().bytes_recv)

hostname()

Nom de l'ordinateur (ex : "MacBook-de-Izac")

Voir le code
python
def hostname():
    return platform.node().removesuffix(".local")

user()

Nom de l'utilisateur (ex : "izac")

Voir le code
python
def user():
    return getpass.getuser()

os_name()

Système d'exploitation (ex : "macOS")

Voir le code
python
def os_name():
    names = {"Darwin": "macOS", "Windows": "Windows", "Linux": "Linux"}
    return names.get(platform.system(), platform.system())

os_version()

Version du système (ex : "27.0.1")

Voir le code
python
def os_version():
    if platform.system() == "Darwin":
        return platform.mac_ver()[0]
    return platform.release()

python_version()

Version de Python (ex : "3.9.6")

Voir le code
python
def python_version():
    return platform.python_version()

python_path()

Chemin du Python utilisé

Voir le code
python
def python_path():
    return sys.executable

uptime()

Temps depuis le démarrage du Mac, en secondes

Voir le code
python
def uptime():
    return time.time() - psutil.boot_time()

uptime_text()

Temps depuis le démarrage, en texte (ex : "26h12")

Voir le code
python
def uptime_text():
    return human_time(uptime())

processes()

Nombre de programmes en cours

Voir le code
python
def processes():
    return len(psutil.pids())

home()

Dossier perso (ex : "/Users/izac")

Voir le code
python
def home():
    return os.path.expanduser("~")

volume()

Volume du son en % (ex : 31), None si inconnu

Voir le code
python
def volume():
    value = _run(["osascript", "-e", "output volume of (get volume settings)"])
    return int(value) if value.isdigit() else None

muted()

Son coupé ? (True / False)

Voir le code
python
def muted():
    return _run(["osascript", "-e", "output muted of (get volume settings)"]) == "true"

password(prompt='password > ', mask='*')

Demander un mot de passe : chaque lettre tapée s'affiche en "*" (au lieu de rien avec getpass)

Retour arrière efface, Ctrl+C -> KeyboardInterrupt, Ctrl+D (champ vide) -> EOFError

Exmdp = password("password > ") / password("PIN : ", mask="•")

Voir le code
python
def password(prompt="password > ", mask="*"):
    if not sys.stdin.isatty():  # Entrée redirigée (ex : echo 1234 | python3 main.py)
        return input(prompt)
    print(prompt, end="", flush=True)
    chars = []
    try:
        for char in _keys():
            if char in ("\r", "\n"):        # Entrée
                break
            if char == "\x03":               # Ctrl+C
                raise KeyboardInterrupt
            if char == "\x04" and not chars: # Ctrl+D sur un champ vide
                raise EOFError
            if char in ("\x7f", "\b"):       # Retour arrière
                if chars:
                    chars.pop()
                    print("\b \b", end="", flush=True)
            elif char == "\x15":             # Ctrl+U : tout effacer
                print("\b \b" * len(chars), end="", flush=True)
                chars.clear()
            elif char.isprintable():          # Ignore Tab, Échap...
                chars.append(char)
                print(mask, end="", flush=True)
    finally:
        print()  # Ici le terminal est déjà remis normal -> vrai retour à la ligne
    return "".join(chars)

summary()

Toutes les infos dans un dict (pratique pour une commande "sysinfo")

public_ip n'est pas inclus car il demande internet et prend du temps

Voir le code
python
def summary():
    bat, vol = battery(), volume()  # Une seule mesure chacun (au lieu de 2-3 appels)
    return {
        "user": user(),
        "hostname": hostname(),
        "os": f"{os_name()} {os_version()}",
        "python": python_version(),
        "cpu": f"{cpu()}% ({cpu_cores()} cores, {cpu_type()})",
        "ram": f"{ram()}% ({ram_used()} / {ram_total()})",
        "disk": f"{disk()}% ({disk_free()} free / {disk_total()})",
        "battery": f"{bat}%" + (" (plugged)" if plugged() else "") if bat is not None else "none",
        "time_left": time_left_text() or "-",
        "uptime": uptime_text(),
        "processes": processes(),
        "local_ip": local_ip() or "-",
        "online": online(),
        "volume": f"{vol}%" + (" (muted)" if muted() else "") if vol is not None else "-",
    }