## /utils/system/hardware.py
from typing import Any
from ..interface import ReadOnlyMeta
from .properties import SystemProperties
__all__ = [
'HardwareProperties',
]
[документация]
class HardwareProperties(metaclass=ReadOnlyMeta):
"""
Неизменяемая структура рантайм-сведений об аппаратном обеспечении (Железе).
Полностью кроссплатформенна, работает через Си-вызовы и парсинг системных утилит.
"""
__slots__ = ()
@staticmethod
def cpu_model() -> str:
"""Возвращает коммерческое наименование процессора."""
sys_name = SystemProperties.os
try:
if sys_name == "windows":
import subprocess
out = subprocess.check_output("wmic cpu get name", shell=True, text=True)
return out.splitlines()[2].strip()
elif sys_name == "darwin":
import subprocess
return subprocess.check_output(["sysctl", "-n", "machdep.cpu.brand_string"], text=True).strip()
else: # Linux
with open("/proc/cpuinfo", "r") as f:
for line in f:
if "model name" in line:
return line.split(":")[1].strip()
except Exception: pass
return "Unknown Processor"
@staticmethod
def cpu_cores() -> int:
"""Возвращает общее количество логических ядер."""
try:
import os
return os.cpu_count() or 1
except Exception:
return 1
@staticmethod
def ram_total_gb() -> int:
"""Возвращает общий объем оперативной памяти в гигабайтах (округленный)."""
sys_name = SystemProperties.os
try:
if sys_name == "windows":
import subprocess
out = subprocess.check_output("wmic computersystem get totalphysicalmemory", shell=True, text=True)
bytes_ram = int(out.splitlines()[2].strip())
return round(bytes_ram / (1024 ** 3))
elif sys_name == "darwin":
import subprocess
bytes_ram = int(subprocess.check_output(["sysctl", "-n", "hw.memsize"], text=True).strip())
return round(bytes_ram / (1024 ** 3))
else: # Linux
with open("/proc/meminfo", "r") as f:
for line in f:
if "MemTotal" in line:
kb_ram = int(re.search(r"\d+", line).group()) # type: ignore
return round(kb_ram / (1024 ** 2))
except Exception: pass
return 8
@staticmethod
def gpu_model() -> str:
"""Возвращает наименование активной дискретной или встроенной видеокарты."""
sys_name = SystemProperties.os
try:
if sys_name == "windows":
import subprocess
out = subprocess.check_output("wmic path win32_VideoController get name", shell=True, text=True)
return out.splitlines()[2].strip()
elif sys_name == "darwin":
import subprocess
out = subprocess.check_output("system_profiler SPDisplaysDataType | grep 'Chipset Model'", shell=True, text=True)
return out.split(":")[1].strip()
else: # Linux
import subprocess
import re
out = subprocess.check_output("lspci | grep -E 'VGA|3D'", shell=True, text=True)
match = re.search(r"controller:\s+(.+)", out, re.IGNORECASE)
return match.group(1).strip() if match else out.strip()
except Exception: pass
return "Integrated Graphics Controller"
@staticmethod
def motherboard() -> str:
"""Возвращает модель материнской платы (или платы системного контура)."""
sys_name = SystemProperties.os
try:
if sys_name == "windows":
import subprocess
out_m = subprocess.check_output("wmic baseboard get product", shell=True, text=True)
out_v = subprocess.check_output("wmic baseboard get manufacturer", shell=True, text=True)
return f"{out_v.splitlines()[2].strip()} {out_m.splitlines()[2].strip()}"
elif sys_name == "darwin":
return "Apple Motherboard (Logic Board)"
else: # Linux
with open("/sys/class/dmi/id/board_name", "r") as f:
m = f.read().strip()
with open("/sys/class/dmi/id/board_vendor", "r") as f:
v = f.read().strip()
return f"{v} {m}"
except Exception: pass
return "Baseboard Mainboard"
@staticmethod
def storage_drives() -> list[dict[str, Any]]:
"""
Возвращает структурированный список всех физических дисков в системе
с указанием их типа (SSD/HDD), буквы/имени и объема в ГБ.
"""
sys_name = SystemProperties.os
drives: list[dict[str, Any]] = []
try:
if sys_name == "windows":
import subprocess
import json
# Ультимативный PowerShell скрипт. Он берет каждую букву диска, находит ее физический
# индекс Win32_DiskDrive и запрашиваетMediaType из хранилища. ЕслиMediaType скрыт (Unspecified),
# он проверяет флаг вращения или паспортные характеристики устройства.
ps_script = (
"Get-CimInstance Win32_LogicalDisk | Where-Object { $_.DriveType -eq 3 } | ForEach-Object { "
"$letter = $_.DeviceID; "
"$sizeGb = [Math]::Round($_.Size / 1Gb); "
"$partition = Get-CimAssociator -Instance $_ -AssocClassName Win32_LogicalDiskToPartition; "
"$drive = Get-CimAssociator -Instance $partition -AssocClassName Win32_DiskDriveToDiskPartition; "
"$phys = Get-CimInstance -Namespace root/Microsoft/Windows/Storage -ClassName MSFT_PhysicalDisk | "
"Where-Object { $_.DeviceId -eq $drive.Index }; "
"$type = 'HDD'; "
"if ($phys) { "
" if ($phys.MediaType -eq 4) { $type = 'SSD' } "
" elif ($phys.MediaType -eq 3) { $type = 'HDD' } "
" else { "
" if ($phys.CannotPoolReason -eq 0 -and $drive.Caption -match 'SSD') { $type = 'SSD' } "
" } "
"} else { "
" if ($drive.Caption -match 'SSD' -or $drive.Model -match 'SSD') { $type = 'SSD' } "
"}; "
"[PSCustomObject]@{ device = $letter; size_gb = $sizeGb; type = $type } "
"} | ConvertTo-Json"
)
cmd = f'powershell -NoProfile -ExecutionPolicy Bypass -Command "{ps_script}"'
out = subprocess.check_output(cmd, shell=True, text=True, stderr=subprocess.DEVNULL)
if out.strip():
raw_json = json.loads(out)
items = raw_json if isinstance(raw_json, list) else [raw_data] if 'raw_data' in locals() else [raw_json]
for item in items:
if not isinstance(item, dict):
continue
drives.append({
"device": str(item.get("device", "")),
"size_gb": int(item.get("size_gb", 0)),
"type": str(item.get("type", "HDD")).upper()
})
elif sys_name == "darwin":
import subprocess
import re
# system_profiler SPStorageDataType возвращает структурированный текст обо всех дисках
cmd = "system_profiler SPStorageDataType"
out = subprocess.check_output(cmd, shell=True, text=True, stderr=subprocess.DEVNULL)
# Разбиваем вывод на блоки отдельных дисков
disk_blocks = out.split("\n\n")
for block in disk_blocks:
if "Mount Point:" in block:
# Извлекаем точку монтирования (например, "/")
mount_match = re.search(r"Mount Point:\s+(.+)", block)
size_match = re.search(r"Capacity:\s+(\d+[\.,]\d+)\s*(GB|TB)", block)
medium_match = re.search(r"Medium Type:\s+(.+)", block)
if mount_match and size_match:
device = mount_match.group(1).strip()
# Парсим объем диска
raw_size = float(size_match.group(1).replace(",", "."))
size_gb = round(raw_size * 1024) if "TB" in size_match.group(2) else round(raw_size)
# Вычисляем физический тип (Mac возвращает Solid State или Rotational)
phys_type = "SSD"
if medium_match:
medium_str = medium_match.group(1).lower()
if "rotational" in medium_str or "hdd" in medium_str:
phys_type = "HDD"
drives.append({
"device": device,
"size_gb": size_gb,
"type": phys_type
})
else: # Linux
import subprocess
import re
# lsblk -d (только физические устройства) -o (колонки Имя, Размер, Вращение)
out = subprocess.check_output(["lsblk", "-d", "-o", "NAME,SIZE,ROTA"], text=True, stderr=subprocess.DEVNULL)
for line in out.splitlines()[1:]:
parts = line.split()
if len(parts) >= 3:
name, size_str, rota = parts[0], parts[1], parts[2]
# Парсим строковый размер lsblk (например, "465,8G" или "1T")
size_gb = 500
size_match = re.search(r"(\d+[\.,]?\d*)([GMT])", size_str)
if size_match:
val = float(size_match.group(1).replace(",", "."))
unit = size_match.group(2)
size_gb = round(val * 1024) if unit == "T" else round(val)
# Если ROTA == 1 -> диск вращается (HDD), если 0 -> твердотельный (SSD)
phys_type = "HDD" if rota == "1" else "SSD"
drives.append({
"device": f"/dev/{name}",
"size_gb": size_gb,
"type": phys_type
})
except Exception: pass
return drives if drives else [{"device": "C:", "size_gb": 512, "type": "SSD"}]