pySMART

Copyright (C) 2014 Marc Herndon

pySMART is a simple Python wrapper for the smartctl component of smartmontools. It works under Linux and Windows, as long as smartctl is on the system path. Running with administrative (root) privilege is strongly recommended, as smartctl cannot accurately detect all device types or parse all SMART information without full permissions.

With only a device's name (ie: /dev/sda, pd0), the API will create a Device object, populated with all relevant information about that device. The documented API can then be used to query this object for information, initiate device self-tests, and perform other functions.

Usage

The most common way to use pySMART is to create a logical representation of the physical storage device that you would like to work with, as shown:

#!bash
>>> from pySMART import Device
>>> sda = Device('/dev/sda')
>>> sda
<SATA device on /dev/sda mod:WDC WD5000AAKS-60Z1A0 sn:WD-WCAWFxxxxxxx>

Device class members can be accessed directly, and a number of helper methods are provided to retrieve information in bulk. Some examples are shown below:

#!bash
>>> sda.assessment  # Query the SMART self-assessment
'PASS'
>>> sda.attributes[9]  # Query a single SMART attribute
<SMART Attribute 'Power_On_Hours' 068/000 raw:23644>
>>> sda.all_attributes()  # Print the entire SMART attribute table
ID# ATTRIBUTE_NAME          CUR WST THR TYPE     UPDATED WHEN_FAIL    RAW
  1 Raw_Read_Error_Rate     200 200 051 Pre-fail Always  -           0
  3 Spin_Up_Time            141 140 021 Pre-fail Always  -           3908
  4 Start_Stop_Count        098 098 000 Old_age  Always  -           2690
  5 Reallocated_Sector_Ct   200 200 140 Pre-fail Always  -           0
    ... # Edited for brevity
199 UDMA_CRC_Error_Count    200 200 000 Old_age  Always  -           0
200 Multi_Zone_Error_Rate   200 200 000 Old_age  Offline -           0
>>> sda.tests[0]  # Query the most recent self-test result
<SMART Self-test [Short offline|Completed without error] hrs:23734 lba:->
>>> sda.all_selftests()  # Print the entire self-test log
ID Test_Description Status                        Left Hours  1st_Error@lba
 1 Short offline    Completed without error       00%  23734  -
 2 Short offline    Completed without error       00%  23734  -
   ... # Edited for brevity
 7 Short offline    Completed without error       00%  23726  -
 8 Short offline    Completed without error       00%  1      -

Alternatively, the package provides a DeviceList class. When instantiated, this will auto-detect all local storage devices and create a list containing one Device object for each detected storage device.

#!bash
>>> from pySMART import DeviceList
>>> devlist = DeviceList()
>>> devlist
<DeviceList contents:
<SAT device on /dev/sdb mod:WDC WD20EADS-00R6B0 sn:WD-WCAVYxxxxxxx>
<SAT device on /dev/sdc mod:WDC WD20EADS-00S2B0 sn:WD-WCAVYxxxxxxx>
<CSMI device on /dev/csmi0,0 mod:WDC WD5000AAKS-60Z1A0 sn:WD-WCAWFxxxxxxx>
>
>>> devlist.devices[0].attributes[5]  # Access Device data as above
<SMART Attribute 'Reallocated_Sector_Ct' 173/140 raw:214>

In the above cases if a new DeviceList is empty or a specific Device reports an "UNKNOWN INTERFACE", you are likely running without administrative privileges. On POSIX systems, you can request smartctl is run as a superuser by setting the sudo attribute of the global SMARTCTL object to True. Note this may cause you to be prompted for a password.

#!bash
>>> from pySMART import DeviceList
>>> from pySMART import Device
>>> sda = Device('/dev/sda')
>>> sda
<UNKNOWN INTERFACE device on /dev/sda mod:None sn:None>
>>> devlist = DeviceList()
>>> devlist
<DeviceList contents:
>
>>> from pySMART import SMARTCTL
>>> SMARTCTL.sudo = True
>>> sda = Device('/dev/sda')
>>> sda
[sudo] password for user:
<SAT device on /dev/sda mod:ST10000DM0004-1ZC101 sn:ZA20VNPT>
>>> devlist = DeviceList()
>>> devlist
<DeviceList contents:
<NVME device on /dev/nvme0 mod:Sabrent Rocket 4.0 1TB sn:03850709185D88300410>
<NVME device on /dev/nvme1 mod:Samsung SSD 970 EVO Plus 2TB sn:S59CNM0RB05028D>
<NVME device on /dev/nvme2 mod:Samsung SSD 970 EVO Plus 2TB sn:S59CNM0RB05113H>
<SAT device on /dev/sda mod:ST10000DM0004-1ZC101 sn:ZA20VNPT>
<SAT device on /dev/sdb mod:ST10000DM0004-1ZC101 sn:ZA22W366>
<SAT device on /dev/sdc mod:ST10000DM0004-1ZC101 sn:ZA22SPLG>
<SAT device on /dev/sdd mod:ST10000DM0004-1ZC101 sn:ZA2215HL>
>

In general, it is recommended to run the base script with enough privileges to execute smartctl, but this is not possible in all cases, so this workaround is provided as a convenience. However, note that using sudo inside other non-terminal projects may cause dev-bugs/issues.

Using the pySMART wrapper, Python applications be be rapidly developed to take advantage of the powerful features of smartmontools.

Acknowledgements

I would like to thank the entire team behind smartmontools for creating and maintaining such a fantastic product.

In particular I want to thank Christian Franke, who maintains the Windows port of the software. For several years I have written Windows batch files that rely on smartctl.exe to automate evaluation and testing of large pools of storage devices under Windows. Without his work, my job would have been significantly more miserable. :)

Having recently migrated my development from Batch to Python for Linux portability, I thought a simple wrapper for smartctl would save time in the development of future automated test tools.

  1# Copyright (C) 2014 Marc Herndon
  2#
  3# This program is free software; you can redistribute it and/or
  4# modify it under the terms of the GNU General Public License,
  5# version 2, as published by the Free Software Foundation.
  6#
  7# This program is distributed in the hope that it will be useful,
  8# but WITHOUT ANY WARRANTY; without even the implied warranty of
  9# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 10# GNU General Public License for more details.
 11#
 12# You should have received a copy of the GNU General Public License
 13# along with this program; if not, write to the Free Software
 14# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 15# MA  02110-1301, USA.
 16#
 17################################################################
 18"""
 19Copyright (C) 2014 Marc Herndon
 20
 21pySMART is a simple Python wrapper for the `smartctl` component of
 22`smartmontools`. It works under Linux and Windows, as long as smartctl is on
 23the system path. Running with administrative (root) privilege is strongly
 24recommended, as smartctl cannot accurately detect all device types or parse
 25all SMART information without full permissions.
 26
 27With only a device's name (ie: /dev/sda, pd0), the API will create a
 28`Device` object, populated with all relevant information about
 29that device. The documented API can then be used to query this object for
 30information, initiate device self-tests, and perform other functions.
 31
 32Usage
 33-----
 34The most common way to use pySMART is to create a logical representation of the
 35physical storage device that you would like to work with, as shown:
 36
 37    #!bash
 38    >>> from pySMART import Device
 39    >>> sda = Device('/dev/sda')
 40    >>> sda
 41    <SATA device on /dev/sda mod:WDC WD5000AAKS-60Z1A0 sn:WD-WCAWFxxxxxxx>
 42
 43`Device` class members can be accessed directly, and a number of helper methods
 44are provided to retrieve information in bulk.  Some examples are shown below:
 45
 46    #!bash
 47    >>> sda.assessment  # Query the SMART self-assessment
 48    'PASS'
 49    >>> sda.attributes[9]  # Query a single SMART attribute
 50    <SMART Attribute 'Power_On_Hours' 068/000 raw:23644>
 51    >>> sda.all_attributes()  # Print the entire SMART attribute table
 52    ID# ATTRIBUTE_NAME          CUR WST THR TYPE     UPDATED WHEN_FAIL    RAW
 53      1 Raw_Read_Error_Rate     200 200 051 Pre-fail Always  -           0
 54      3 Spin_Up_Time            141 140 021 Pre-fail Always  -           3908
 55      4 Start_Stop_Count        098 098 000 Old_age  Always  -           2690
 56      5 Reallocated_Sector_Ct   200 200 140 Pre-fail Always  -           0
 57        ... # Edited for brevity
 58    199 UDMA_CRC_Error_Count    200 200 000 Old_age  Always  -           0
 59    200 Multi_Zone_Error_Rate   200 200 000 Old_age  Offline -           0
 60    >>> sda.tests[0]  # Query the most recent self-test result
 61    <SMART Self-test [Short offline|Completed without error] hrs:23734 lba:->
 62    >>> sda.all_selftests()  # Print the entire self-test log
 63    ID Test_Description Status                        Left Hours  1st_Error@lba
 64     1 Short offline    Completed without error       00%  23734  -
 65     2 Short offline    Completed without error       00%  23734  -
 66       ... # Edited for brevity
 67     7 Short offline    Completed without error       00%  23726  -
 68     8 Short offline    Completed without error       00%  1      -
 69
 70Alternatively, the package provides a `DeviceList` class. When instantiated,
 71this will auto-detect all local storage devices and create a list containing
 72one `Device` object for each detected storage device.
 73
 74    #!bash
 75    >>> from pySMART import DeviceList
 76    >>> devlist = DeviceList()
 77    >>> devlist
 78    <DeviceList contents:
 79    <SAT device on /dev/sdb mod:WDC WD20EADS-00R6B0 sn:WD-WCAVYxxxxxxx>
 80    <SAT device on /dev/sdc mod:WDC WD20EADS-00S2B0 sn:WD-WCAVYxxxxxxx>
 81    <CSMI device on /dev/csmi0,0 mod:WDC WD5000AAKS-60Z1A0 sn:WD-WCAWFxxxxxxx>
 82    >
 83    >>> devlist.devices[0].attributes[5]  # Access Device data as above
 84    <SMART Attribute 'Reallocated_Sector_Ct' 173/140 raw:214>
 85
 86In the above cases if a new DeviceList is empty or a specific Device reports an
 87"UNKNOWN INTERFACE", you are likely running without administrative privileges.
 88On POSIX systems, you can request smartctl is run as a superuser by setting the
 89sudo attribute of the global SMARTCTL object to True. Note this may cause you
 90to be prompted for a password.
 91
 92    #!bash
 93    >>> from pySMART import DeviceList
 94    >>> from pySMART import Device
 95    >>> sda = Device('/dev/sda')
 96    >>> sda
 97    <UNKNOWN INTERFACE device on /dev/sda mod:None sn:None>
 98    >>> devlist = DeviceList()
 99    >>> devlist
100    <DeviceList contents:
101    >
102    >>> from pySMART import SMARTCTL
103    >>> SMARTCTL.sudo = True
104    >>> sda = Device('/dev/sda')
105    >>> sda
106    [sudo] password for user:
107    <SAT device on /dev/sda mod:ST10000DM0004-1ZC101 sn:ZA20VNPT>
108    >>> devlist = DeviceList()
109    >>> devlist
110    <DeviceList contents:
111    <NVME device on /dev/nvme0 mod:Sabrent Rocket 4.0 1TB sn:03850709185D88300410>
112    <NVME device on /dev/nvme1 mod:Samsung SSD 970 EVO Plus 2TB sn:S59CNM0RB05028D>
113    <NVME device on /dev/nvme2 mod:Samsung SSD 970 EVO Plus 2TB sn:S59CNM0RB05113H>
114    <SAT device on /dev/sda mod:ST10000DM0004-1ZC101 sn:ZA20VNPT>
115    <SAT device on /dev/sdb mod:ST10000DM0004-1ZC101 sn:ZA22W366>
116    <SAT device on /dev/sdc mod:ST10000DM0004-1ZC101 sn:ZA22SPLG>
117    <SAT device on /dev/sdd mod:ST10000DM0004-1ZC101 sn:ZA2215HL>
118    >
119
120In general, it is recommended to run the base script with enough privileges to
121execute smartctl, but this is not possible in all cases, so this workaround is
122provided as a convenience. However, note that using sudo inside other
123non-terminal projects may cause dev-bugs/issues.
124
125
126Using the pySMART wrapper, Python applications be be rapidly developed to take
127advantage of the powerful features of smartmontools.
128
129Acknowledgements
130----------------
131I would like to thank the entire team behind smartmontools for creating and
132maintaining such a fantastic product.
133
134In particular I want to thank Christian Franke, who maintains the Windows port
135of the software.  For several years I have written Windows batch files that
136rely on smartctl.exe to automate evaluation and testing of large pools of
137storage devices under Windows.  Without his work, my job would have been
138significantly more miserable. :)
139
140Having recently migrated my development from Batch to Python for Linux
141portability, I thought a simple wrapper for smartctl would save time in the
142development of future automated test tools.
143"""
144# autopep8: off
145from .testentry import TestEntry
146from .interface.ata.attribute import Attribute
147from . import utils
148utils.configure_trace_logging()
149from .smartctl import SMARTCTL
150from .device_list import DeviceList
151from .device import Device, smart_health_assement
152from .version import __version__,__version_tuple__
153# autopep8: on
154
155
156__all__ = [
157    '__version__', '__version_tuple__',
158    'TestEntry', 'Attribute', 'utils', 'SMARTCTL', 'DeviceList', 'Device',
159    'smart_health_assement'
160]
__version__ = '1.4.2.dev2'
__version_tuple__ = (1, 4, 2, 'dev2')
class TestEntry:
 27class TestEntry(object):
 28    """
 29    Contains all of the information associated with a single SMART Self-test
 30    log entry. This data is intended to exactly mirror that obtained through
 31    smartctl.
 32    """
 33
 34    def __init__(self, format, num: Optional[int], test_type, status, hours, lba,
 35                 remain=None,
 36                 segment=None,
 37                 sense=None,
 38                 asc=None,
 39                 ascq=None,
 40                 nsid=None,
 41                 sct=None,
 42                 code=None):
 43
 44        self._format = format
 45        """
 46        **(str):** Indicates whether this entry was taken from an 'ata' or
 47        'scsi' self-test log. Used to display the content properly.
 48        """
 49        self.num: Optional[int] = num
 50        """
 51        **(int):** Entry's position in the log from 1 (most recent) to 21
 52        (least recent).  ATA logs save the last 21 entries while SCSI logs
 53        only save the last 20.
 54        """
 55        self.type = test_type
 56        """
 57        **(str):** Type of test run.  Generally short, long (extended), or
 58        conveyance, plus offline (background) or captive (foreground).
 59        """
 60        self.status = status
 61        """
 62        **(str):** Self-test's status message, for example 'Completed without
 63        error' or 'Completed: read failure'.
 64        """
 65        self.hours = hours
 66        """
 67        **(str):** The device's power-on hours at the time the self-test
 68        was initiated.
 69        """
 70        self.LBA = lba
 71        """
 72        **(str):** Indicates the first LBA at which an error was encountered
 73        during this self-test. Presented as a decimal value for ATA/SATA
 74        devices and in hexadecimal notation for SAS/SCSI devices.
 75        """
 76        self.remain = remain
 77        """
 78        **(str):** Percentage value indicating how much of the self-test is
 79        left to perform. '00%' indicates a complete test, while any other
 80        value could indicate a test in progress or one that failed prior to
 81        completion. Only reported by ATA devices.
 82        """
 83        self.segment = segment
 84        """
 85        **(str):** A manufacturer-specific self-test segment number reported
 86        by SCSI devices on self-test failure. Set to '-' otherwise.
 87        """
 88        self.sense = sense
 89        """
 90        **(str):** SCSI sense key reported on self-test failure. Set to '-'
 91        otherwise.
 92        """
 93        self.ASC = asc
 94        """
 95        **(str):** SCSI 'Additonal Sense Code' reported on self-test failure.
 96        Set to '-' otherwise.
 97        """
 98        self.ASCQ = ascq
 99        """
100        **(str):** SCSI 'Additonal Sense Code Quaifier' reported on self-test
101        failure. Set to '-' otherwise.
102        """
103        self.nsid = nsid
104        """
105        **(str):** NVMe 'Name Space Identifier' reported on self-test failure.
106        Set to '-' if no namespace is defined.
107        """
108        self.sct = sct
109        """
110        **(str):** NVMe 'Status Code Type' reported on self-test failure.
111        Set to '-' if undefined.
112        """
113        self.code = code
114        """
115        **(str):** NVMe 'Status Code' reported on self-test failure.
116        Set to '-' if undefined.
117        """
118
119    def __getstate__(self):
120        return {
121            'num': self.num,
122            'type': self.type,
123            'status': self.status,
124            'hours': self.hours,
125            'lba': self.LBA,
126            'remain': self.remain,
127            'segment': self.segment,
128            'sense': self.sense,
129            'asc': self.ASC,
130            'ascq': self.ASCQ,
131            'nsid': self.nsid,
132            'sct': self.sct,
133            'code': self.code
134        }
135
136    def __repr__(self):
137        """Define a basic representation of the class object."""
138        return "<SMART Self-test [%s|%s] hrs:%s LBA:%s>" % (
139            self.type, self.status, self.hours, self.LBA)
140
141    def __str__(self):
142        """
143        Define a formatted string representation of the object's content.
144        Looks nearly identical to the output of smartctl, without overflowing
145        80-character lines.
146        """
147        if self._format == 'ata':
148            return "{0:>2} {1:17}{2:30}{3:5}{4:7}{5:17}".format(
149                self.num, self.type, self.status, self.remain, self.hours,
150                self.LBA)
151        elif self._format == 'scsi':
152            # 'Segment' could not be fit on the 80-char line. It's of limited
153            # utility anyway due to it's manufacturer-proprietary nature...
154            return ("{0:>2} {1:17}{2:23}{3:7}{4:14}[{5:4}{6:5}{7:4}]".format(
155                self.num,
156                self.type,
157                self.status,
158                self.hours,
159                self.LBA,
160                self.sense,
161                self.ASC,
162                self.ASCQ
163            ))
164        elif self._format == 'nvme':
165            ## NVME FORMAT ##
166            # Example smartctl output
167            # Self-test Log (NVMe Log 0x06)
168            # Self-test status: Extended self-test in progress (28% completed)
169            # Num  Test_Description  Status                       Power_on_Hours  Failing_LBA  NSID Seg SCT Code
170            #  0   Extended          Completed without error                3441            -     -   -   -    -
171            return ("{0:^4} {1:<18}{2:<29}{3:>14}{4:>13}{5:>6}{6:>4}{7:>4}{8:>5}".format(
172                self.num,
173                self.type,
174                self.status,
175                self.hours,
176                self.LBA if self.LBA is not None else '-',
177                self.nsid if self.LBA is not None else '-',
178                self.segment if self.segment is not None else '-',
179                self.sct if self.LBA is not None else '-',
180                self.code if self.LBA is not None else '-'
181            ))
182        else:
183            return "Unknown test format: %s" % self._format

Contains all of the information associated with a single SMART Self-test log entry. This data is intended to exactly mirror that obtained through smartctl.

TestEntry( format, num: Optional[int], test_type, status, hours, lba, remain=None, segment=None, sense=None, asc=None, ascq=None, nsid=None, sct=None, code=None)
 34    def __init__(self, format, num: Optional[int], test_type, status, hours, lba,
 35                 remain=None,
 36                 segment=None,
 37                 sense=None,
 38                 asc=None,
 39                 ascq=None,
 40                 nsid=None,
 41                 sct=None,
 42                 code=None):
 43
 44        self._format = format
 45        """
 46        **(str):** Indicates whether this entry was taken from an 'ata' or
 47        'scsi' self-test log. Used to display the content properly.
 48        """
 49        self.num: Optional[int] = num
 50        """
 51        **(int):** Entry's position in the log from 1 (most recent) to 21
 52        (least recent).  ATA logs save the last 21 entries while SCSI logs
 53        only save the last 20.
 54        """
 55        self.type = test_type
 56        """
 57        **(str):** Type of test run.  Generally short, long (extended), or
 58        conveyance, plus offline (background) or captive (foreground).
 59        """
 60        self.status = status
 61        """
 62        **(str):** Self-test's status message, for example 'Completed without
 63        error' or 'Completed: read failure'.
 64        """
 65        self.hours = hours
 66        """
 67        **(str):** The device's power-on hours at the time the self-test
 68        was initiated.
 69        """
 70        self.LBA = lba
 71        """
 72        **(str):** Indicates the first LBA at which an error was encountered
 73        during this self-test. Presented as a decimal value for ATA/SATA
 74        devices and in hexadecimal notation for SAS/SCSI devices.
 75        """
 76        self.remain = remain
 77        """
 78        **(str):** Percentage value indicating how much of the self-test is
 79        left to perform. '00%' indicates a complete test, while any other
 80        value could indicate a test in progress or one that failed prior to
 81        completion. Only reported by ATA devices.
 82        """
 83        self.segment = segment
 84        """
 85        **(str):** A manufacturer-specific self-test segment number reported
 86        by SCSI devices on self-test failure. Set to '-' otherwise.
 87        """
 88        self.sense = sense
 89        """
 90        **(str):** SCSI sense key reported on self-test failure. Set to '-'
 91        otherwise.
 92        """
 93        self.ASC = asc
 94        """
 95        **(str):** SCSI 'Additonal Sense Code' reported on self-test failure.
 96        Set to '-' otherwise.
 97        """
 98        self.ASCQ = ascq
 99        """
100        **(str):** SCSI 'Additonal Sense Code Quaifier' reported on self-test
101        failure. Set to '-' otherwise.
102        """
103        self.nsid = nsid
104        """
105        **(str):** NVMe 'Name Space Identifier' reported on self-test failure.
106        Set to '-' if no namespace is defined.
107        """
108        self.sct = sct
109        """
110        **(str):** NVMe 'Status Code Type' reported on self-test failure.
111        Set to '-' if undefined.
112        """
113        self.code = code
114        """
115        **(str):** NVMe 'Status Code' reported on self-test failure.
116        Set to '-' if undefined.
117        """
num: Optional[int]

(int): Entry's position in the log from 1 (most recent) to 21 (least recent). ATA logs save the last 21 entries while SCSI logs only save the last 20.

type

(str): Type of test run. Generally short, long (extended), or conveyance, plus offline (background) or captive (foreground).

status

(str): Self-test's status message, for example 'Completed without error' or 'Completed: read failure'.

hours

(str): The device's power-on hours at the time the self-test was initiated.

LBA

(str): Indicates the first LBA at which an error was encountered during this self-test. Presented as a decimal value for ATA/SATA devices and in hexadecimal notation for SAS/SCSI devices.

remain

(str): Percentage value indicating how much of the self-test is left to perform. '00%' indicates a complete test, while any other value could indicate a test in progress or one that failed prior to completion. Only reported by ATA devices.

segment

(str): A manufacturer-specific self-test segment number reported by SCSI devices on self-test failure. Set to '-' otherwise.

sense

(str): SCSI sense key reported on self-test failure. Set to '-' otherwise.

ASC

(str): SCSI 'Additonal Sense Code' reported on self-test failure. Set to '-' otherwise.

ASCQ

(str): SCSI 'Additonal Sense Code Quaifier' reported on self-test failure. Set to '-' otherwise.

nsid

(str): NVMe 'Name Space Identifier' reported on self-test failure. Set to '-' if no namespace is defined.

sct

(str): NVMe 'Status Code Type' reported on self-test failure. Set to '-' if undefined.

code

(str): NVMe 'Status Code' reported on self-test failure. Set to '-' if undefined.

class Attribute:
 29class Attribute(object):
 30    """
 31    Contains all of the information associated with a single SMART attribute
 32    in a `Device`'s SMART table. This data is intended to exactly mirror that
 33    obtained through smartctl.
 34    """
 35
 36    def __init__(self, num: int, name, flags: int, value, worst, thresh, attr_type, updated, when_failed, raw):
 37        self.num: int = num
 38        """**(int):** Attribute's ID as a decimal value (1-255)."""
 39        self.name: str = name
 40        """
 41        **(str):** Attribute's name, as reported by smartmontools' drive.db.
 42        """
 43        self.flags: int = flags
 44        """**(int):** Attribute flags as a bit value (ie: 0x0032)."""
 45        self._value: str = value
 46        """**(str):** Attribute's current normalized value."""
 47        self._worst: str = worst
 48        """**(str):** Worst recorded normalized value for this attribute."""
 49        self._thresh: str = thresh
 50        """**(str):** Attribute's failure threshold."""
 51        self.type: str = attr_type
 52        """**(str):** Attribute's type, generally 'pre-fail' or 'old-age'."""
 53        self.updated: str = updated
 54        """
 55        **(str):** When is this attribute updated? Generally 'Always' or
 56        'Offline'
 57        """
 58        self.when_failed: str = when_failed
 59        """
 60        **(str):** When did this attribute cross below
 61        `pySMART.attribute.Attribute.thresh`? Reads '-' when not failed.
 62        Generally either 'FAILING_NOW' or 'In_the_Past' otherwise.
 63        """
 64        self.raw = raw
 65        """**(str):** Attribute's current raw (non-normalized) value."""
 66
 67    @property
 68    def value_str(self) -> str:
 69        """Gets the attribute value
 70
 71        Returns:
 72            str: The attribute value in string format
 73        """
 74        return self._value
 75
 76    @property
 77    def value_int(self) -> int:
 78        """Gets the attribute value
 79
 80        Returns:
 81            int: The attribute value in integer format.
 82        """
 83        return int(self._value)
 84
 85    @property
 86    def value(self) -> str:
 87        """Gets the attribue value
 88
 89        Returns:
 90            str: The attribute value in string format
 91        """
 92        return self.value_str
 93
 94    @property
 95    def worst(self) -> int:
 96        """Gets the worst value
 97
 98        Returns:
 99            int: The attribute worst field in integer format
100        """
101        return int(self._worst)
102
103    @property
104    def thresh(self) -> Optional[int]:
105        """Gets the threshold value
106
107        Returns:
108            int: The attribute threshold field in integer format
109        """
110        return None if self._thresh == '---' else int(self._thresh)
111
112    @property
113    def raw_int(self) -> Optional[int]:
114        """Gets the raw value converted to int
115        NOTE: Some values may not be correctly converted!
116
117        Returns:
118            int: The attribute raw-value field in integer format.
119            None: In case the raw string failed to be parsed
120        """
121        try:
122            return int(re.search(r'\d+', self.raw).group())
123        except:
124            return None
125
126    def __repr__(self):
127        """Define a basic representation of the class object."""
128        return "<SMART Attribute %r %s/%s raw:%s>" % (
129            self.name, self.value, self.thresh, self.raw)
130
131    def __str__(self):
132        """
133        Define a formatted string representation of the object's content.
134        In the interest of not overflowing 80-character lines this does not
135        print the value of `pySMART.attribute.Attribute.flags_hex`.
136        """
137        return "{0:>3} {1:23}{2:>4}{3:>4}{4:>4} {5:9}{6:8}{7:12}{8}".format(
138            self.num,
139            self.name,
140            self.value,
141            self.worst,
142            self.thresh,
143            self.type,
144            self.updated,
145            self.when_failed,
146            self.raw
147        )
148
149    def __getstate__(self):
150        return {
151            'name': self.name,
152            'num': self.num,
153            'flags': self.flags,
154            'raw': self.raw,
155            'value': self.value,
156            'worst': self.worst,
157            'thresh': self.thresh,
158            'type': self.type,
159            'updated': self.updated,
160            'when_failed': self.when_failed,
161            # Raw values
162            '_value': self._value,
163            '_worst': self._worst,
164            '_thresh': self._thresh,
165            'raw_int': self.raw_int,
166
167        }

Contains all of the information associated with a single SMART attribute in a Device's SMART table. This data is intended to exactly mirror that obtained through smartctl.

Attribute( num: int, name, flags: int, value, worst, thresh, attr_type, updated, when_failed, raw)
36    def __init__(self, num: int, name, flags: int, value, worst, thresh, attr_type, updated, when_failed, raw):
37        self.num: int = num
38        """**(int):** Attribute's ID as a decimal value (1-255)."""
39        self.name: str = name
40        """
41        **(str):** Attribute's name, as reported by smartmontools' drive.db.
42        """
43        self.flags: int = flags
44        """**(int):** Attribute flags as a bit value (ie: 0x0032)."""
45        self._value: str = value
46        """**(str):** Attribute's current normalized value."""
47        self._worst: str = worst
48        """**(str):** Worst recorded normalized value for this attribute."""
49        self._thresh: str = thresh
50        """**(str):** Attribute's failure threshold."""
51        self.type: str = attr_type
52        """**(str):** Attribute's type, generally 'pre-fail' or 'old-age'."""
53        self.updated: str = updated
54        """
55        **(str):** When is this attribute updated? Generally 'Always' or
56        'Offline'
57        """
58        self.when_failed: str = when_failed
59        """
60        **(str):** When did this attribute cross below
61        `pySMART.attribute.Attribute.thresh`? Reads '-' when not failed.
62        Generally either 'FAILING_NOW' or 'In_the_Past' otherwise.
63        """
64        self.raw = raw
65        """**(str):** Attribute's current raw (non-normalized) value."""
num: int

(int): Attribute's ID as a decimal value (1-255).

name: str

(str): Attribute's name, as reported by smartmontools' drive.db.

flags: int

(int): Attribute flags as a bit value (ie: 0x0032).

type: str

(str): Attribute's type, generally 'pre-fail' or 'old-age'.

updated: str

(str): When is this attribute updated? Generally 'Always' or 'Offline'

when_failed: str

(str): When did this attribute cross below pySMART.attribute.Attribute.thresh? Reads '-' when not failed. Generally either 'FAILING_NOW' or 'In_the_Past' otherwise.

raw

(str): Attribute's current raw (non-normalized) value.

value_str: str
67    @property
68    def value_str(self) -> str:
69        """Gets the attribute value
70
71        Returns:
72            str: The attribute value in string format
73        """
74        return self._value

Gets the attribute value

Returns: str: The attribute value in string format

value_int: int
76    @property
77    def value_int(self) -> int:
78        """Gets the attribute value
79
80        Returns:
81            int: The attribute value in integer format.
82        """
83        return int(self._value)

Gets the attribute value

Returns: int: The attribute value in integer format.

value: str
85    @property
86    def value(self) -> str:
87        """Gets the attribue value
88
89        Returns:
90            str: The attribute value in string format
91        """
92        return self.value_str

Gets the attribue value

Returns: str: The attribute value in string format

worst: int
 94    @property
 95    def worst(self) -> int:
 96        """Gets the worst value
 97
 98        Returns:
 99            int: The attribute worst field in integer format
100        """
101        return int(self._worst)

Gets the worst value

Returns: int: The attribute worst field in integer format

thresh: Optional[int]
103    @property
104    def thresh(self) -> Optional[int]:
105        """Gets the threshold value
106
107        Returns:
108            int: The attribute threshold field in integer format
109        """
110        return None if self._thresh == '---' else int(self._thresh)

Gets the threshold value

Returns: int: The attribute threshold field in integer format

raw_int: Optional[int]
112    @property
113    def raw_int(self) -> Optional[int]:
114        """Gets the raw value converted to int
115        NOTE: Some values may not be correctly converted!
116
117        Returns:
118            int: The attribute raw-value field in integer format.
119            None: In case the raw string failed to be parsed
120        """
121        try:
122            return int(re.search(r'\d+', self.raw).group())
123        except:
124            return None

Gets the raw value converted to int NOTE: Some values may not be correctly converted!

Returns: int: The attribute raw-value field in integer format. None: In case the raw string failed to be parsed

SMARTCTL = <pySMART.smartctl.Smartctl object>
class DeviceList:
 37class DeviceList(object):
 38    """
 39    Represents a list of all the storage devices connected to this computer.
 40    """
 41
 42    def __init__(self, init: bool = True, smartctl=SMARTCTL, catch_errors: bool = False):
 43        """Instantiates and optionally initializes the `DeviceList`.
 44
 45        Args:
 46            init (bool, optional): By default, `pySMART.device_list.DeviceList.devices`
 47                is populated with `Device` objects during instantiation. Setting init
 48                to False will skip initialization and create an empty
 49                `pySMART.device_list.DeviceList` object instead. Defaults to True.
 50            smartctl ([type], optional): This stablish the smartctl wrapper.
 51                Defaults the global `SMARTCTL` object and should be only
 52                overwritten on tests.
 53            catch_errors (bool, optional): If True, individual device-parsing errors will be caught
 54        """
 55
 56        self.devices: List[Device] = []
 57        """
 58        **(list of `Device`):** Contains all storage devices detected during
 59        instantiation, as `Device` objects.
 60        """
 61        self.smartctl: Smartctl = smartctl
 62        """The smartctl wrapper
 63        """
 64        if init:
 65            self.initialize(catch_errors)
 66
 67    def __repr__(self):
 68        """Define a basic representation of the class object."""
 69        rep = "<DeviceList contents:\n"
 70        for device in self.devices:
 71            rep += str(device) + '\n'
 72        return rep + '>'
 73        # return "<DeviceList contents:%r>" % (self.devices)
 74
 75    def _cleanup(self):
 76        """
 77        Removes duplicate ATA devices that correspond to an existing CSMI
 78        device. Also removes any device with no capacity value, as this
 79        indicates removable storage, ie: CD/DVD-ROM, ZIP, etc.
 80        """
 81        # We can't operate directly on the list while we're iterating
 82        # over it, so we collect indeces to delete and remove them later
 83        to_delete = []
 84        # Enumerate the list to get tuples containing indeces and values
 85        for index, device in enumerate(self.devices):
 86            # Allow well-known devices
 87            if device.interface in ['nvme']:
 88                continue
 89            
 90            # Check for duplicate ATA devices with CSMI devices
 91            if device.interface == 'csmi':
 92                for otherindex, otherdevice in enumerate(self.devices):
 93                    if (otherdevice.interface == 'ata' or
 94                            otherdevice.interface == 'sata'):
 95                        if device.serial == otherdevice.serial:
 96                            to_delete.append(otherindex)
 97                            device._sd_name = otherdevice.name
 98            if device.capacity is None and index not in to_delete:
 99                to_delete.append(index)
100        # Recreate the self.devices list without the marked indeces
101        self.devices[:] = [v for i, v in enumerate(self.devices)
102                           if i not in to_delete]
103
104    def initialize(self, catch_errors: bool = False):
105        """
106        Scans system busses for attached devices and add them to the
107        `DeviceList` as `Device` objects.
108        If device list is already populated, it will be cleared first.
109
110        Args:
111            catch_errors (bool, optional): If True, individual device-parsing errors will be caught
112        """
113
114        # Clear the list if it's already populated
115        if len(self.devices):
116            self.devices = []
117
118        # Scan for devices
119        for line in self.smartctl.scan():
120            if not ('failed:' in line or line == ''):
121                groups = re.compile(
122                    r'^(\S+)\s+-d\s+(\S+)').match(line).groups()
123                name = groups[0]
124                interface = groups[1]
125
126                try:
127                    # Add the device to the list
128                    self.devices.append(
129                        Device(name, interface=interface, smartctl=self.smartctl))
130
131                except Exception as e:
132                    if catch_errors:
133                        # Print the exception
134                        import logging
135
136                        logging.exception(f"Error parsing device {name}")
137
138                    else:
139                        # Reraise the exception
140                        raise e
141
142        # Remove duplicates and unwanted devices (optical, etc.) from the list
143        self._cleanup()
144        # Sort the list alphabetically by device name
145        self.devices.sort(key=lambda device: device.name)
146
147    def __getitem__(self, index: int) -> Device:
148        """Returns an element from self.devices
149
150        Args:
151            index (int): An index of self.devices
152
153        Returns:
154            Device: Returns a Device that is located on the asked index
155        """
156        return self.devices[index]

Represents a list of all the storage devices connected to this computer.

DeviceList( init: bool = True, smartctl=<pySMART.smartctl.Smartctl object>, catch_errors: bool = False)
42    def __init__(self, init: bool = True, smartctl=SMARTCTL, catch_errors: bool = False):
43        """Instantiates and optionally initializes the `DeviceList`.
44
45        Args:
46            init (bool, optional): By default, `pySMART.device_list.DeviceList.devices`
47                is populated with `Device` objects during instantiation. Setting init
48                to False will skip initialization and create an empty
49                `pySMART.device_list.DeviceList` object instead. Defaults to True.
50            smartctl ([type], optional): This stablish the smartctl wrapper.
51                Defaults the global `SMARTCTL` object and should be only
52                overwritten on tests.
53            catch_errors (bool, optional): If True, individual device-parsing errors will be caught
54        """
55
56        self.devices: List[Device] = []
57        """
58        **(list of `Device`):** Contains all storage devices detected during
59        instantiation, as `Device` objects.
60        """
61        self.smartctl: Smartctl = smartctl
62        """The smartctl wrapper
63        """
64        if init:
65            self.initialize(catch_errors)

Instantiates and optionally initializes the DeviceList.

Args: init (bool, optional): By default, pySMART.device_list.DeviceList.devices is populated with Device objects during instantiation. Setting init to False will skip initialization and create an empty pySMART.device_list.DeviceList object instead. Defaults to True. smartctl ([type], optional): This stablish the smartctl wrapper. Defaults the global SMARTCTL object and should be only overwritten on tests. catch_errors (bool, optional): If True, individual device-parsing errors will be caught

devices: List[Device]

(list of Device): Contains all storage devices detected during instantiation, as Device objects.

smartctl: pySMART.smartctl.Smartctl

The smartctl wrapper

def initialize(self, catch_errors: bool = False):
104    def initialize(self, catch_errors: bool = False):
105        """
106        Scans system busses for attached devices and add them to the
107        `DeviceList` as `Device` objects.
108        If device list is already populated, it will be cleared first.
109
110        Args:
111            catch_errors (bool, optional): If True, individual device-parsing errors will be caught
112        """
113
114        # Clear the list if it's already populated
115        if len(self.devices):
116            self.devices = []
117
118        # Scan for devices
119        for line in self.smartctl.scan():
120            if not ('failed:' in line or line == ''):
121                groups = re.compile(
122                    r'^(\S+)\s+-d\s+(\S+)').match(line).groups()
123                name = groups[0]
124                interface = groups[1]
125
126                try:
127                    # Add the device to the list
128                    self.devices.append(
129                        Device(name, interface=interface, smartctl=self.smartctl))
130
131                except Exception as e:
132                    if catch_errors:
133                        # Print the exception
134                        import logging
135
136                        logging.exception(f"Error parsing device {name}")
137
138                    else:
139                        # Reraise the exception
140                        raise e
141
142        # Remove duplicates and unwanted devices (optical, etc.) from the list
143        self._cleanup()
144        # Sort the list alphabetically by device name
145        self.devices.sort(key=lambda device: device.name)

Scans system busses for attached devices and add them to the DeviceList as Device objects. If device list is already populated, it will be cleared first.

Args: catch_errors (bool, optional): If True, individual device-parsing errors will be caught

class Device:
  81class Device(object):
  82    """
  83    Represents any device attached to an internal storage interface, such as a
  84    hard drive or DVD-ROM, and detected by smartmontools. Includes eSATA
  85    (considered SATA) but excludes other external devices (USB, Firewire).
  86    """
  87
  88    def __init__(self, name: str, interface: Optional[str] = None, abridged: bool = False, smart_options: Union[str, List[str], None] = None, smartctl: Smartctl = SMARTCTL):
  89        """Instantiates and initializes the `pySMART.device.Device`."""
  90        if not (
  91                interface is None or
  92                smartctl_isvalid_type(interface.lower())
  93        ):
  94            raise ValueError(
  95                'Unknown interface: {0} specified for {1}'.format(interface, name))
  96        self.abridged = abridged or interface == 'UNKNOWN INTERFACE'
  97        if smart_options is not None:
  98            if isinstance(smart_options,  str):
  99                smart_options = smart_options.split(' ')
 100            smartctl.add_options(smart_options)
 101        self.smartctl = smartctl
 102        """
 103        """
 104        self.name: str = name.replace('/dev/', '').replace('nvd', 'nvme')
 105        """
 106        **(str):** Device's hardware ID, without the '/dev/' prefix.
 107        (ie: sda (Linux), pd0 (Windows))
 108        """
 109        self.family: Optional[str] = None
 110        """**(str):** Device's family (if any)."""
 111        self.model: Optional[str] = None
 112        """**(str):** Device's model number (if any)."""
 113        self.serial: Optional[str] = None
 114        """**(str):** Device's serial number (if any)."""
 115        self._vendor: Optional[str] = None
 116        """**(str):** Device's vendor (if any)."""
 117        self._interface: Optional[str] = None if interface == 'UNKNOWN INTERFACE' else interface
 118        """
 119        **(str):** Device's interface type. Must be one of:
 120            * **ATA** - Advanced Technology Attachment
 121            * **SATA** - Serial ATA
 122            * **SCSI** - Small Computer Systems Interface
 123            * **SAS** - Serial Attached SCSI
 124            * **SAT** - SCSI-to-ATA Translation (SATA device plugged into a
 125            SAS port)
 126            * **CSMI** - Common Storage Management Interface (Intel ICH /
 127            Matrix RAID)
 128        Generally this should not be specified to allow auto-detection to
 129        occur. Otherwise, this value overrides the auto-detected type and could
 130        produce unexpected or no data.
 131        """
 132        self._capacity: Optional[int] = None
 133        """**(str):** Device's user capacity as reported directly by smartctl (RAW)."""
 134        self._capacity_human: Optional[str] = None
 135        """**(str):** Device's user capacity (human readable) as reported directly by smartctl (RAW)."""
 136        self.firmware: Optional[str] = None
 137        """**(str):** Device's firmware version."""
 138        self.smart_capable: bool = self._interface == 'nvme'
 139        """
 140        **(bool):** True if the device has SMART Support Available.
 141        False otherwise. This is useful for VMs amongst other things.
 142        """
 143        self.smart_enabled: bool = self._interface == 'nvme'
 144        """
 145        **(bool):** True if the device supports SMART (or SCSI equivalent) and
 146        has the feature set enabled. False otherwise.
 147        """
 148        self.assessment: Optional[str] = None
 149        """
 150        **(str):** SMART health self-assessment as reported by the device.
 151        """
 152        self.messages: List[str] = []
 153        """
 154        **(list of str):** Contains any SMART warnings or other error messages
 155        reported by the device (ie: ascq codes).
 156        """
 157        self.is_ssd: bool = self._interface == 'nvme'
 158        """
 159        **(bool):** True if this device is a Solid State Drive.
 160        False otherwise.
 161        """
 162        self.rotation_rate: Optional[int] = None
 163        """
 164        **(int):** The Roatation Rate of the Drive if it is not a SSD.
 165        The Metric is RPM.
 166        """
 167        self.test_capabilities = {
 168            'offline': False,  # SMART execute Offline immediate (ATA only)
 169            'short': 'nvme' not in self.name,  # SMART short Self-test
 170            'long': 'nvme' not in self.name,  # SMART long Self-test
 171            'conveyance': False,  # SMART Conveyance Self-Test (ATA only)
 172            'selective': False,  # SMART Selective Self-Test (ATA only)
 173        }
 174        # Note have not included 'offline' test for scsi as it runs in the foregorund
 175        # mode. While this may be beneficial to us in someways it is against the
 176        # general layout and pattern that the other tests issued using pySMART are
 177        # followed hence not doing it currently
 178        """
 179        **(dict): ** This dictionary contains key == 'Test Name' and
 180        value == 'True/False' of self-tests that this device is capable of.
 181        """
 182        # Note: The above are just default values and can/will be changed
 183        # upon update() when the attributes and type of the disk is actually
 184        # determined.
 185        self.test_polling_time = {
 186            'short': 10,
 187            'long': 1000,
 188            'conveyance': 20,
 189        }
 190        """
 191        **(dict): ** This dictionary contains key == 'Test Name' and
 192        value == int of approximate times to run each test type that this
 193        device is capable of.
 194        """
 195        # Note: The above are just default values and can/will be changed
 196        # upon update() when the attributes and type of the disk is actually
 197        # determined.
 198        self.tests: List[TestEntry] = []
 199        """
 200        **(list of `TestEntry`):** Contains the complete SMART self-test log
 201        for this device, as provided by smartctl.
 202        """
 203        self._test_running = False
 204        """
 205        **(bool):** True if a self-test is currently being run.
 206        False otherwise.
 207        """
 208        self._test_ECD = None
 209        """
 210        **(str):** Estimated completion time of the running SMART selftest.
 211        Not provided by SAS/SCSI devices.
 212        """
 213        self._test_progress = None
 214        """
 215        **(int):** Estimate progress percantage of the running SMART selftest.
 216        """
 217        self._temperature: Optional[int] = None
 218        """
 219        **(int or None): Since SCSI disks do not report attributes like ATA ones
 220        we need to grep/regex the shit outta the normal "smartctl -a" output.
 221        In case the device have more than one temperature sensor the first value
 222        will be stored here too.
 223        Note: Temperatures are always in Celsius (if possible).
 224        """
 225        self.temperatures: Dict[int, int] = {}
 226        """
 227        **(dict of int): NVMe disks usually report multiple temperatures, which
 228        will be stored here if available. Keys are sensor numbers as reported in
 229        output data.
 230        Note: Temperatures are always in Celsius (if possible).
 231        """
 232        self.logical_sector_size: Optional[int] = None
 233        """
 234        **(int):** The logical sector size of the device (or LBA).
 235        """
 236        self.physical_sector_size: Optional[int] = None
 237        """
 238        **(int):** The physical sector size of the device.
 239        """
 240        self.if_attributes: Union[None,
 241                                  AtaAttributes,
 242                                  NvmeAttributes,
 243                                  SCSIAttributes] = None
 244        """
 245        **(NvmeAttributes):** This object may vary for each device interface attributes.
 246        It will store all data obtained from smartctl
 247        """
 248
 249        if self.name is None:
 250            warnings.warn(
 251                "\nDevice '{0}' does not exist! This object should be destroyed.".format(
 252                    name)
 253            )
 254            return
 255        # If no interface type was provided, scan for the device
 256        # Lets do this only for the non-abridged case
 257        # (we can work with no interface for abridged case)
 258        elif self._interface is None and not self.abridged:
 259            logger.debug(
 260                "Determining interface of disk: {0}".format(self.name))
 261            raw, returncode = self.smartctl.generic_call(
 262                ['-d', 'test', self.dev_reference])
 263
 264            if len(raw) > 0:
 265                # I do not like this parsing logic but it works for now!
 266                # just for reference _stdout.split('\n') gets us
 267                # something like
 268                # [
 269                #     ...copyright string...,
 270                #     '',
 271                #     "/dev/ada2: Device of type 'atacam' [ATA] detected",
 272                #     "/dev/ada2: Device of type 'atacam' [ATA] opened",
 273                #     ''
 274                # ]
 275                # The above example should be enough for anyone to understand the line below
 276                try:
 277                    for line in reversed(raw):
 278                        if "opened" in line:
 279                            self._interface = line.split("'")[1]
 280
 281                            if self._interface == "nvme":  # if nvme set SMART to true
 282                                self.smart_capable = True
 283                                self.smart_enabled = True
 284                                self.is_ssd = True
 285
 286                            break
 287                except:
 288                    # for whatever reason we could not get the interface type
 289                    # we should mark this as an `abbridged` case and move on
 290                    self._interface = None
 291                    self.abbridged = True
 292                # TODO: Uncomment the classify call if we ever find out that we need it
 293                # Disambiguate the generic interface to a specific type
 294                # self._classify()
 295            else:
 296                warnings.warn(
 297                    "\nDevice '{0}' does not exist! This object should be destroyed.".format(
 298                        name)
 299                )
 300                return
 301        # If a valid device was detected, populate its information
 302        # OR if in unabridged mode, then do it even without interface info
 303        if self._interface is not None or self.abridged:
 304            self.update()
 305
 306    @property
 307    def attributes(self) -> List[Optional[Attribute]]:
 308        """Returns the SMART attributes of the device.
 309        Note: This is only filled with ATA/SATA attributes. SCSI/SAS/NVMe devices will have empty lists!!
 310        @deprecated: Use `if_attributes` instead.
 311
 312        Returns:
 313            list of `Attribute`: The SMART attributes of the device.
 314        """
 315        if self.if_attributes is None or not isinstance(self.if_attributes, AtaAttributes):
 316            return [None] * 256
 317        else:
 318            return self.if_attributes.legacyAttributes
 319
 320    @property
 321    def dev_interface(self) -> Optional[str]:
 322        """Returns the internal interface type of the device.
 323           It may not be the same as the interface type as used by smartctl.
 324
 325        Returns:
 326            str: The interface type of the device. (example: ata, scsi, nvme)
 327                 None if the interface type could not be determined.
 328        """
 329        # Try to get the fine-tuned interface type
 330        fineType = self._classify()
 331
 332        # If return still contains a megaraid, just asume it's type
 333        if 'megaraid' in fineType:
 334            # If any attributes is not None and has at least non None value, then it is a sat+megaraid device
 335            if isinstance(self.if_attributes, AtaAttributes):
 336                return 'ata'
 337            else:
 338                return 'sas'
 339
 340        return fineType
 341
 342    @property
 343    def temperature(self) -> Optional[int]:
 344        """Returns the temperature of the device.
 345
 346        Returns:
 347            int: The temperature of the device in Celsius.
 348                 None if the temperature could not be determined.
 349        """
 350        if self.if_attributes is None:
 351            return self._temperature
 352        else:
 353            return self.if_attributes.temperature or self._temperature
 354
 355    @property
 356    def smartctl_interface(self) -> Optional[str]:
 357        """Returns the interface type of the device as it is used in smartctl.
 358
 359        Returns:
 360            str: The interface type of the device. (example: ata, scsi, nvme)
 361                 None if the interface type could not be determined.
 362        """
 363        return self._interface
 364
 365    @property
 366    def interface(self) -> Optional[str]:
 367        """Returns the interface type of the device as it is used in smartctl.
 368
 369        Returns:
 370            str: The interface type of the device. (example: ata, scsi, nvme)
 371                 None if the interface type could not be determined.
 372        """
 373        return self.smartctl_interface
 374
 375    @property
 376    def dev_reference(self) -> str:
 377        """The reference to the device as provided by smartctl.
 378           - On unix-like systems, this is the path to the device. (example /dev/<name>)
 379           - On MacOS, this is the name of the device. (example <name>)
 380           - On Windows, this is the drive letter of the device. (example <drive letter>)
 381
 382        Returns:
 383            str: The reference to the device as provided by smartctl.
 384        """
 385
 386        # detect if we are on MacOS
 387        if 'IOService' in self.name:
 388            return self.name
 389
 390        # otherwise asume we are on unix-like systems
 391        return os.path.join('/dev/', self.name)
 392
 393    @property
 394    def vendor(self) -> Optional[str]:
 395        """Returns the vendor of the device.
 396
 397        Returns:
 398            str: The vendor of the device.
 399        """
 400        if self._vendor:
 401            return self._vendor
 402
 403        # If family is present, try to stract from family. Skip anything but letters.
 404        elif self.family:
 405            filter = re.search(r'^[a-zA-Z]+', self.family.strip())
 406            if filter:
 407                return filter.group(0)
 408
 409        # If model is present, try to stract from model. Skip anything but letters.
 410        elif self.model:
 411            filter = re.search(r'^[a-zA-Z]+', self.model.strip())
 412            if filter:
 413                return filter.group(0)
 414
 415        # If all else fails, return None
 416        return None
 417
 418    @property
 419    def capacity(self) -> Optional[str]:
 420        """Returns the capacity in the raw smartctl format.
 421        This may be deprecated in the future and its only retained for compatibility.
 422
 423        Returns:
 424            str: The capacity in the raw smartctl format
 425        """
 426        return self._capacity_human
 427
 428    @property
 429    def diagnostics(self) -> Optional[Diagnostics]:
 430        """Gets the old/deprecated version of SCSI/SAS diagnostics atribute.
 431        """
 432        if self.if_attributes is None or not isinstance(self.if_attributes, SCSIAttributes):
 433            return None
 434
 435        else:
 436            return self.if_attributes.diagnostics
 437
 438    @property
 439    def diags(self) -> Dict[str, str]:
 440        """Gets the old/deprecated version of SCSI/SAS diags atribute.
 441        """
 442        if self.if_attributes is None or not isinstance(self.if_attributes, SCSIAttributes):
 443            # Return an empty dict if the device is not SCSI/SAS
 444            return {}
 445
 446        else:
 447            return self.if_attributes.diagnostics.get_classic_format()
 448
 449    @property
 450    def size_raw(self) -> Optional[str]:
 451        """Returns the capacity in the raw smartctl format.
 452
 453        Returns:
 454            str: The capacity in the raw smartctl format
 455        """
 456        return self._capacity_human
 457
 458    @property
 459    def size(self) -> int:
 460        """Returns the capacity in bytes
 461
 462        Returns:
 463            int: The capacity in bytes
 464        """
 465        import humanfriendly
 466
 467        if self._capacity is not None:
 468            return self._capacity
 469        elif self._capacity_human is not None:
 470            return humanfriendly.parse_size(self._capacity_human)
 471        else:
 472            return 0
 473
 474    @property
 475    def sector_size(self) -> int:
 476        """Returns the sector size of the device.
 477
 478        Returns:
 479            int: The sector size of the device in Bytes. If undefined, we'll assume 512B
 480        """
 481        if self.logical_sector_size is not None:
 482            return self.logical_sector_size
 483        elif self.physical_sector_size is not None:
 484            return self.physical_sector_size
 485        else:
 486            return 512
 487
 488    def __repr__(self):
 489        """Define a basic representation of the class object."""
 490        return "<{0} device on /dev/{1} mod:{2} sn:{3}>".format(
 491            self._interface.upper() if self._interface else 'UNKNOWN INTERFACE',
 492            self.name,
 493            self.model,
 494            self.serial
 495        )
 496
 497    def __getstate__(self, all_info=True) -> Dict:
 498        """
 499        Allows us to send a pySMART Device object over a serializable
 500        medium which uses json (or the likes of json) payloads
 501        """
 502
 503        # Deprecated entries:
 504        # - attributes
 505        # - diagnostics
 506
 507        state_dict = {
 508            'attributes': [attr.__getstate__() if attr else None for attr in self.attributes],
 509            'capacity': self._capacity_human,
 510            'diagnostics': self.diagnostics.__getstate__() if self.diagnostics else None,
 511            'firmware': self.firmware,
 512            'if_attributes': self.if_attributes.__getstate__() if self.if_attributes else None,
 513            'interface': self._interface if self._interface else 'UNKNOWN INTERFACE',
 514            'is_ssd': self.is_ssd,
 515            'messages': self.messages,
 516            'model': self.model,
 517            'name': self.name,
 518            'path': self.dev_reference,
 519            'rotation_rate': self.rotation_rate,
 520            'serial': self.serial,
 521            'smart_capable': self.smart_capable,
 522            'smart_enabled': self.smart_enabled,
 523            'smart_status': self.assessment,
 524            'temperature': self.temperature,
 525            'test_capabilities': self.test_capabilities.copy(),
 526            'tests': [t.__getstate__() for t in self.tests] if self.tests else [],
 527        }
 528        return state_dict
 529
 530    def __setstate__(self, state):
 531        state['assessment'] = state['smart_status']
 532        del state['smart_status']
 533        self.__dict__.update(state)
 534
 535    def smart_toggle(self, action: str) -> Tuple[bool, List[str]]:
 536        """
 537        A basic function to enable/disable SMART on device.
 538
 539        # Args:
 540        * **action (str):** Can be either 'on'(for enabling) or 'off'(for disabling).
 541
 542        # Returns"
 543        * **(bool):** Return True (if action succeded) else False
 544        * **(List[str]):** None if option succeded else contains the error message.
 545        """
 546        # Lets make the action verb all lower case
 547        if self._interface == 'nvme':
 548            return False, ['NVME devices do not currently support toggling SMART enabled']
 549        action_lower = action.lower()
 550        if action_lower not in ['on', 'off']:
 551            return False, ['Unsupported action {0}'.format(action)]
 552        # Now lets check if the device's smart enabled status is already that of what
 553        # the supplied action is intending it to be. If so then just return successfully
 554        if self.smart_enabled:
 555            if action_lower == 'on':
 556                return True, []
 557        else:
 558            if action_lower == 'off':
 559                return True, []
 560        if self._interface is not None:
 561            raw, returncode = self.smartctl.generic_call(
 562                ['-s', action_lower, '-d', self._interface, self.dev_reference])
 563        else:
 564            raw, returncode = self.smartctl.generic_call(
 565                ['-s', action_lower, self.dev_reference])
 566
 567        if returncode != 0:
 568            return False, raw
 569        # if everything worked out so far lets perform an update() and check the result
 570        self.update()
 571        if action_lower == 'off' and self.smart_enabled:
 572            return False, ['Failed to turn SMART off.']
 573        if action_lower == 'on' and not self.smart_enabled:
 574            return False, ['Failed to turn SMART on.']
 575        return True, []
 576
 577    def all_attributes(self, print_fn=print):
 578        """
 579        Prints the entire SMART attribute table, in a format similar to
 580        the output of smartctl.
 581        allows usage of custom print function via parameter print_fn by default uses print
 582        """
 583        header_printed = False
 584        for attr in self.attributes:
 585            if attr is not None:
 586                if not header_printed:
 587                    print_fn("{0:>3} {1:24}{2:4}{3:4}{4:4}{5:9}{6:8}{7:12}{8}"
 588                             .format('ID#', 'ATTRIBUTE_NAME', 'CUR', 'WST', 'THR', 'TYPE', 'UPDATED', 'WHEN_FAIL',
 589                                     'RAW'))
 590                    header_printed = True
 591                print_fn(attr)
 592        if not header_printed:
 593            print_fn('This device does not support SMART attributes.')
 594
 595    def all_selftests(self):
 596        """
 597        Prints the entire SMART self-test log, in a format similar to
 598        the output of smartctl.
 599        """
 600        if self.tests:
 601            all_tests = []
 602            if smartctl_type(self._interface) == 'scsi':
 603                header = "{0:3}{1:17}{2:23}{3:7}{4:14}{5:15}".format(
 604                    'ID',
 605                    'Test Description',
 606                    'Status',
 607                    'Hours',
 608                    '1st_Error@LBA',
 609                    '[SK  ASC  ASCQ]'
 610                )
 611            else:
 612                header = ("{0:3}{1:17}{2:30}{3:5}{4:7}{5:17}".format(
 613                    'ID',
 614                    'Test_Description',
 615                    'Status',
 616                    'Left',
 617                    'Hours',
 618                    '1st_Error@LBA'))
 619            all_tests.append(header)
 620            for test in self.tests:
 621                all_tests.append(str(test))
 622
 623            return all_tests
 624        else:
 625            no_tests = 'No self-tests have been logged for this device.'
 626            return no_tests
 627
 628    def _classify(self) -> str:
 629        """
 630        Disambiguates generic device types ATA and SCSI into more specific
 631        ATA, SATA, SAS, SAT and SCSI.
 632        """
 633
 634        fine_interface = self._interface or ''
 635        # SCSI devices might be SCSI, SAS or SAT
 636        # ATA device might be ATA or SATA
 637        if fine_interface in ['scsi', 'ata'] or 'megaraid' in fine_interface:
 638            if 'megaraid' in fine_interface:
 639                if not 'sat+' in fine_interface:
 640                    test = 'sat'+fine_interface
 641                else:
 642                    test = fine_interface
 643            else:
 644                test = 'sat' if fine_interface == 'scsi' else 'sata'
 645            # Look for a SATA PHY to detect SAT and SATA
 646            raw, returncode = self.smartctl.try_generic_call([
 647                '-d',
 648                smartctl_type(test),
 649                '-l',
 650                'sataphy',
 651                self.dev_reference])
 652
 653            if returncode == 0 and 'GP Log 0x11' in raw[3]:
 654                fine_interface = test
 655        # If device type is still SCSI (not changed to SAT above), then
 656        # check for a SAS PHY
 657        if fine_interface in ['scsi'] or 'megaraid' in fine_interface:
 658            raw, returncode = self.smartctl.try_generic_call([
 659                '-d',
 660                smartctl_type(fine_interface),
 661                '-l',
 662                'sasphy',
 663                self.dev_reference])
 664            if returncode == 0 and len(raw) > 4 and 'SAS SSP' in raw[4]:
 665                fine_interface = 'sas'
 666            # Some older SAS devices do not support the SAS PHY log command.
 667            # For these, see if smartmontools reports a transport protocol.
 668            else:
 669                raw = self.smartctl.all(self.dev_reference, fine_interface)
 670
 671                for line in raw:
 672                    if 'Transport protocol' in line and 'SAS' in line:
 673                        fine_interface = 'sas'
 674
 675        return fine_interface
 676
 677    def _guess_smart_type(self, line):
 678        """
 679        This function is not used in the generic wrapper, however the header
 680        is defined so that it can be monkey-patched by another application.
 681        """
 682        pass
 683
 684    def _make_smart_warnings(self):
 685        """
 686        Parses an ATA/SATA SMART table for attributes with the 'when_failed'
 687        value set. Generates an warning message for any such attributes and
 688        updates the self-assessment value if necessary.
 689        """
 690        if smartctl_type(self._interface) == 'scsi':
 691            return
 692        for attr in self.attributes:
 693            if attr is not None:
 694                if attr.when_failed == 'In_the_past':
 695                    warn_str = "{0} failed in the past with value {1}. [Threshold: {2}]".format(
 696                        attr.name, attr.worst, attr.thresh)
 697                    self.messages.append(warn_str)
 698                    if not self.assessment == 'FAIL':
 699                        self.assessment = 'WARN'
 700                elif attr.when_failed == 'FAILING_NOW':
 701                    warn_str = "{0} is failing now with value {1}. [Threshold: {2}]".format(
 702                        attr.name, attr.value, attr.thresh)
 703                    self.assessment = 'FAIL'
 704                    self.messages.append(warn_str)
 705                elif not attr.when_failed == '-':
 706                    warn_str = "{0} says it failed '{1}'. [V={2},W={3},T={4}]".format(
 707                        attr.name, attr.when_failed, attr.value, attr.worst, attr.thresh)
 708                    self.messages.append(warn_str)
 709                    if not self.assessment == 'FAIL':
 710                        self.assessment = 'WARN'
 711
 712    def __get_smart_status(self, raw_iterator:Iterator[str]):
 713        """
 714        A quick function to get the SMART status of the device.
 715        This is required prior to scsi parsing.
 716        """
 717        
 718        for line in raw_iterator:
 719            if 'SMART support' in line:
 720                # self.smart_capable = 'Available' in line
 721                # self.smart_enabled = 'Enabled' in line
 722                # Since this line repeats twice the above method is flawed
 723                # Lets try the following instead, it is a bit redundant but
 724                # more robust.
 725                if any_in(line, 'Unavailable', 'device lacks SMART capability'):
 726                    self.smart_capable = False
 727                    self.smart_enabled = False
 728                elif 'Enabled' in line:
 729                    self.smart_enabled = True
 730                elif 'Disabled' in line:
 731                    self.smart_enabled = False
 732                elif any_in(line, 'Available', 'device has SMART capability'):
 733                    self.smart_capable = True
 734                continue
 735
 736            if 'does not support SMART' in line:
 737                self.smart_capable = False
 738                self.smart_enabled = False
 739                continue
 740
 741        
 742
 743    def get_selftest_result(self, output=None):
 744        """
 745        Refreshes a device's `pySMART.device.Device.tests` attribute to obtain
 746        the latest test results. If a new test result is obtained, its content
 747        is returned.
 748
 749        # Args:
 750        * **output (str, optional):** If set to 'str', the string
 751        representation of the most recent test result will be returned, instead
 752        of a `Test_Entry` object.
 753
 754        # Returns:
 755        * **(int):** Return status code. One of the following:
 756            * 0 - Success. Object (or optionally, string rep) is attached.
 757            * 1 - Self-test in progress. Must wait for it to finish.
 758            * 2 - No new test results.
 759            * 3 - The Self-test was Aborted by host
 760        * **(`Test_Entry` or str):** Most recent `Test_Entry` object (or
 761        optionally it's string representation) if new data exists.  Status
 762        message string on failure.
 763        * **(int):** Estimate progress percantage of the running SMART selftest, if known.
 764        Otherwise 'None'.
 765        """
 766        # SCSI self-test logs hold 20 entries while ATA logs hold 21
 767        if smartctl_type(self._interface) == 'scsi':
 768            maxlog = 20
 769        else:
 770            maxlog = 21
 771        # If we looked only at the most recent test result we could be fooled
 772        # by two short tests run close together (within the same hour)
 773        # appearing identical. Comparing the length of the log adds some
 774        # confidence until it maxes, as above. Comparing the least-recent test
 775        # result greatly diminishes the chances that two sets of two tests each
 776        # were run within an hour of themselves, but with 16-17 other tests run
 777        # in between them.
 778        if self.tests:
 779            _first_entry = self.tests[0]
 780            _len = len(self.tests)
 781            _last_entry = self.tests[_len - 1]
 782        else:
 783            _len = 0
 784        self.update()
 785        # Since I have changed the update() parsing to DTRT to pickup currently
 786        # running selftests we can now purely rely on that for self._test_running
 787        # Thus check for that variable first and return if it is True with appropos message.
 788        if self._test_running is True:
 789            return 1, 'Self-test in progress. Please wait.', self._test_progress
 790        # Check whether the list got longer (ie: new entry)
 791        # If so return the newest test result
 792        # If not, because it's max size already, check for new entries
 793        if (
 794                (len(self.tests) != _len) or
 795                (
 796                    _len == maxlog and
 797                    (
 798                        _first_entry.type != self.tests[0].type or
 799                        _first_entry.hours != self.tests[0].hours or
 800                        _last_entry.type != self.tests[len(self.tests) - 1].type or
 801                        _last_entry.hours != self.tests[len(
 802                            self.tests) - 1].hours
 803                    )
 804                )
 805        ):
 806            return (
 807                0 if 'Aborted' not in self.tests[0].status else 3,
 808                str(self.tests[0]) if output == 'str' else self.tests[0],
 809                None
 810            )
 811        else:
 812            return 2, 'No new self-test results found.', None
 813
 814    def abort_selftest(self):
 815        """
 816        Aborts non-captive SMART Self Tests.   Note that this command
 817        will  abort the Offline Immediate Test routine only if your disk
 818        has the "Abort Offline collection upon new command"  capability.
 819
 820        # Args: Nothing (just aborts directly)
 821
 822        # Returns:
 823        * **(int):** The returncode of calling `smartctl -X device_path`
 824        """
 825        return self.smartctl.test_stop(smartctl_type(self._interface), self.dev_reference)
 826
 827    def run_selftest(self, test_type, ETA_type='date'):
 828        """
 829        Instructs a device to begin a SMART self-test. All tests are run in
 830        'offline' / 'background' mode, allowing normal use of the device while
 831        it is being tested.
 832
 833        # Args:
 834        * **test_type (str):** The type of test to run. Accepts the following
 835        (not case sensitive):
 836            * **short** - Brief electo-mechanical functionality check.
 837            Generally takes 2 minutes or less.
 838            * **long** - Thorough electro-mechanical functionality check,
 839            including complete recording media scan. Generally takes several
 840            hours.
 841            * **conveyance** - Brief test used to identify damage incurred in
 842            shipping. Generally takes 5 minutes or less. **This test is not
 843            supported by SAS or SCSI devices.**
 844            * **offline** - Runs SMART Immediate Offline Test. The effects of
 845            this test are visible only in that it updates the SMART Attribute
 846            values, and if errors are found they will appear in the SMART error
 847            log, visible with the '-l error' option to smartctl. **This test is
 848            not supported by SAS or SCSI devices in pySMART use cli smartctl for
 849            running 'offline' selftest (runs in foreground) on scsi devices.**
 850            * **ETA_type** - Format to return the estimated completion time/date
 851            in. Default is 'date'. One could otherwise specidy 'seconds'.
 852            Again only for ATA devices.
 853
 854        # Returns:
 855        * **(int):** Return status code.  One of the following:
 856            * 0 - Self-test initiated successfully
 857            * 1 - Previous self-test running. Must wait for it to finish.
 858            * 2 - Unknown or unsupported (by the device) test type requested.
 859            * 3 - Unspecified smartctl error. Self-test not initiated.
 860        * **(str):** Return status message.
 861        * **(str)/(float):** Estimated self-test completion time if a test is started.
 862        The optional argument of 'ETA_type' (see above) controls the return type.
 863        if 'ETA_type' == 'date' then a date string is returned else seconds(float)
 864        is returned.
 865        Note: The self-test completion time can only be obtained for ata devices.
 866        Otherwise 'None'.
 867        """
 868        # Lets call get_selftest_result() here since it does an update() and
 869        # checks for an existing selftest is running or not, this way the user
 870        # can issue a test from the cli and this can still pick that up
 871        # Also note that we do not need to obtain the results from this as the
 872        # data is already stored in the Device class object's variables
 873        self.get_selftest_result()
 874        if self._test_running:
 875            return 1, 'Self-test in progress. Please wait.', self._test_ECD
 876        test_type = test_type.lower()
 877        interface = smartctl_type(self._interface)
 878        try:
 879            if not self.test_capabilities[test_type]:
 880                return (
 881                    2,
 882                    "Device {0} does not support the '{1}' test ".format(
 883                        self.name, test_type),
 884                    None
 885                )
 886        except KeyError:
 887            return 2, "Unknown test type '{0}' requested.".format(test_type), None
 888
 889        raw, rc = self.smartctl.test_start(
 890            interface, test_type, self.dev_reference)
 891        _success = False
 892        _running = False
 893        for line in raw:
 894            if 'has begun' in line:
 895                _success = True
 896                self._test_running = True
 897            if 'aborting current test' in line:
 898                _running = True
 899                try:
 900                    self._test_progress = 100 - \
 901                        int(line.split('(')[-1].split('%')[0])
 902                except ValueError:
 903                    pass
 904
 905            if _success and 'complete after' in line:
 906                self._test_ECD = line[25:].rstrip()
 907                if ETA_type == 'seconds':
 908                    self._test_ECD = mktime(
 909                        strptime(self._test_ECD, '%a %b %d %H:%M:%S %Y')) - time()
 910                self._test_progress = 0
 911        if _success:
 912            return 0, 'Self-test started successfully', self._test_ECD
 913        else:
 914            if _running:
 915                return 1, 'Self-test already in progress. Please wait.', self._test_ECD
 916            else:
 917                return 3, 'Unspecified Error. Self-test not started.', None
 918
 919    def run_selftest_and_wait(self, test_type, output=None, polling=5, progress_handler=None):
 920        """
 921        This is essentially a wrapper around run_selftest() such that we
 922        call self.run_selftest() and wait on the running selftest till
 923        it finished before returning.
 924        The above holds true for all pySMART supported tests with the
 925        exception of the 'offline' test (ATA only) as it immediately
 926        returns, since the entire test only affects the smart error log
 927        (if any errors found) and updates the SMART attributes. Other
 928        than that it is not visibile anywhere else, so we start it and
 929        simply return.
 930        # Args:
 931        * **test_type (str):** The type of test to run. Accepts the following
 932        (not case sensitive):
 933            * **short** - Brief electo-mechanical functionality check.
 934            Generally takes 2 minutes or less.
 935            * **long** - Thorough electro-mechanical functionality check,
 936            including complete recording media scan. Generally takes several
 937            hours.
 938            * **conveyance** - Brief test used to identify damage incurred in
 939            shipping. Generally takes 5 minutes or less. **This test is not
 940            supported by SAS or SCSI devices.**
 941            * **offline** - Runs SMART Immediate Offline Test. The effects of
 942            this test are visible only in that it updates the SMART Attribute
 943            values, and if errors are found they will appear in the SMART error
 944            log, visible with the '-l error' option to smartctl. **This test is
 945            not supported by SAS or SCSI devices in pySMART use cli smartctl for
 946            running 'offline' selftest (runs in foreground) on scsi devices.**
 947        * **output (str, optional):** If set to 'str', the string
 948            representation of the most recent test result will be returned,
 949            instead of a `Test_Entry` object.
 950        * **polling (int, default=5):** The time duration to sleep for between
 951            checking for test_results and progress.
 952        * **progress_handler (function, optional):** This if provided is called
 953            with self._test_progress as the supplied argument everytime a poll to
 954            check the status of the selftest is done.
 955        # Returns:
 956        * **(int):** Return status code.  One of the following:
 957            * 0 - Self-test executed and finished successfully
 958            * 1 - Previous self-test running. Must wait for it to finish.
 959            * 2 - Unknown or illegal test type requested.
 960            * 3 - The Self-test was Aborted by host
 961            * 4 - Unspecified smartctl error. Self-test not initiated.
 962        * **(`Test_Entry` or str):** Most recent `Test_Entry` object (or
 963        optionally it's string representation) if new data exists.  Status
 964        message string on failure.
 965        """
 966        test_initiation_result = self.run_selftest(test_type)
 967        if test_initiation_result[0] != 0:
 968            return test_initiation_result[:2]
 969        if test_type == 'offline':
 970            self._test_running = False
 971        # if not then the test initiated correctly and we can start the polling.
 972        # For now default 'polling' value is 5 seconds if not specified by the user
 973
 974        # Do an initial check, for good measure.
 975        # In the probably impossible case that self._test_running is instantly False...
 976        selftest_results = self.get_selftest_result(output=output)
 977        while self._test_running:
 978            if selftest_results[0] != 1:
 979                # the selftest is run and finished lets return with the results
 980                break
 981            # Otherwise see if we are provided with the progress_handler to update progress
 982            if progress_handler is not None:
 983                progress_handler(
 984                    selftest_results[2] if selftest_results[2] is not None else 50)
 985            # Now sleep 'polling' seconds before checking the progress again
 986            sleep(polling)
 987
 988            # Check after the sleep to ensure we return the right result, and not an old one.
 989            selftest_results = self.get_selftest_result(output=output)
 990
 991        # Now if (selftes_results[0] == 2) i.e No new selftest (because the same
 992        # selftest was run twice within the last hour) but we know for a fact that
 993        # we just ran a new selftest then just return the latest entry in self.tests
 994        if selftest_results[0] == 2:
 995            selftest_return_value = 0 if 'Aborted' not in self.tests[0].status else 3
 996            return selftest_return_value, str(self.tests[0]) if output == 'str' else self.tests[0]
 997        return selftest_results[:2]
 998
 999    def update(self):
1000        """
1001        Queries for device information using smartctl and updates all
1002        class members, including the SMART attribute table and self-test log.
1003        Can be called at any time to refresh the `pySMART.device.Device`
1004        object's data content.
1005        """
1006        # set temperature back to None so that if update() is called more than once
1007        # any logic that relies on self.temperature to be None to rescan it works.it
1008        self._temperature = None
1009        # same for temperatures
1010        self.temperatures = {}
1011        if self.abridged:
1012            interface = None
1013            raw = self.smartctl.info(self.dev_reference)
1014
1015        else:
1016            interface = smartctl_type(self._interface)
1017            raw = self.smartctl.all(
1018                self.dev_reference, interface)
1019
1020        parse_self_tests = False
1021        parse_running_test = False
1022        parse_ascq = False
1023        polling_minute_type = None
1024        message = ''
1025        self.tests = []
1026        self._test_running = False
1027        self._test_progress = None
1028        # Lets skip the first couple of non-useful lines
1029        _stdout = raw[4:]
1030
1031        #######################################
1032        #           Encoding fixing           #
1033        #######################################
1034        # In some scenarios, smartctl returns some lines with a different/strange encoding
1035        # This is a workaround to fix that
1036        for i, line in enumerate(_stdout):
1037            # character ' ' (U+202F) should be removed
1038            _stdout[i] = line.replace('\u202f', '')
1039
1040        #######################################
1041        #   Dedicated interface attributes    #
1042        #######################################
1043        if AtaAttributes.has_compatible_data(iter(_stdout)):
1044            self.if_attributes = AtaAttributes(iter(_stdout))
1045
1046        elif self.dev_interface == 'nvme':
1047            self.if_attributes = NvmeAttributes(iter(_stdout))
1048
1049            # Get Tests
1050            for test in self.if_attributes.tests:
1051                self.tests.append(TestEntry('nvme', test.num, test.description, test.status, test.powerOnHours,
1052                                  test.failingLBA, nsid=test.nsid, segment=test.seg, sct=test.sct, code=test.code, remain=100-test.progress))
1053
1054            # Set running test
1055            if any(test.status == 'Running' for test in self.if_attributes.tests):
1056                self._test_running = True
1057                self._test_progress = self.if_attributes.tests[0].progress
1058            else:
1059                self._test_running = False
1060                self._test_progress = None
1061
1062        elif SCSIAttributes.has_compatible_data(iter(_stdout)):
1063            self.__get_smart_status(iter(_stdout))
1064            self.if_attributes = SCSIAttributes(iter(_stdout),
1065                                                abridged=self.abridged,
1066                                                smartEnabled=self.smart_enabled,
1067                                                sm=self.smartctl,
1068                                                dev_reference=self.dev_reference)
1069
1070            # Import (for now) the tests from if_attributes
1071            self.tests = self.if_attributes.tests
1072
1073        else:
1074            self.if_attributes = None
1075
1076        #######################################
1077        #    Global / generic  attributes     #
1078        #######################################
1079        stdout_iter = iter(_stdout)
1080        for line in stdout_iter:
1081            if line.strip() == '':  # Blank line stops sub-captures
1082                if parse_self_tests is True:
1083                    parse_self_tests = False
1084                if parse_ascq:
1085                    parse_ascq = False
1086                    self.messages.append(message)
1087            if parse_ascq:
1088                message += ' ' + line.lstrip().rstrip()
1089            if parse_self_tests:
1090                # Detect Test Format
1091
1092                ## SCSI/SAS FORMAT ##
1093                # Example smartctl output
1094                # SMART Self-test log
1095                # Num  Test              Status                 segment  LifeTime  LBA_first_err [SK ASC ASQ]
1096                #      Description                              number   (hours)
1097                # # 1  Background short  Completed                   -   33124                 - [-   -    -]
1098                format_scsi = re.compile(
1099                    r'^[#\s]*(\d+)\s{2,}(.*[^\s])\s{2,}(.*[^\s])\s{2,}(.*[^\s])\s{2,}(.*[^\s])\s{2,}(.*[^\s])\s+\[([^\s]+)\s+([^\s]+)\s+([^\s]+)\]$').match(line)
1100
1101                ## ATA FORMAT ##
1102                # Example smartctl output:
1103                # SMART Self-test log structure revision number 1
1104                # Num  Test_Description    Status                  Remaining  LifeTime(hours)  LBA_of_first_error
1105                # # 1  Extended offline    Completed without error       00%     46660         -
1106                format_ata = re.compile(
1107                    r'^[#\s]*(\d+)\s{2,}(.*[^\s])\s{2,}(.*[^\s])\s{1,}(.*[^\s])\s{2,}(.*[^\s])\s{2,}(.*[^\s])$').match(line)
1108
1109                if format_scsi is not None:
1110                    ## SCSI FORMAT ##
1111                    pass
1112
1113                elif format_ata is not None:
1114                    ## ATA FORMAT ##
1115                    format = 'ata'
1116                    parsed = format_ata.groups()
1117                    num = parsed[0]
1118                    test_type = parsed[1]
1119                    status = parsed[2]
1120                    remain = parsed[3]
1121                    hours = parsed[4]
1122                    lba = parsed[5]
1123
1124                    try:
1125                        num = int(num)
1126                    except:
1127                        num = None
1128
1129                    self.tests.append(
1130                        TestEntry(format, num, test_type, status,
1131                                  hours, lba, remain=remain)
1132                    )
1133                else:
1134                    pass
1135
1136            # Basic device information parsing
1137            if any_in(line, 'Device Model', 'Product', 'Model Number'):
1138                self.model = line.split(':')[1].lstrip().rstrip()
1139                self._guess_smart_type(line.lower())
1140                continue
1141
1142            if 'Model Family' in line:
1143                self.family = line.split(':')[1].strip()
1144                self._guess_smart_type(line.lower())
1145                continue
1146
1147            if 'LU WWN' in line:
1148                self._guess_smart_type(line.lower())
1149                continue
1150
1151            if any_in(line, 'Serial Number', 'Serial number'):
1152                try:
1153                    self.serial = line.split(':')[1].split()[0].rstrip()
1154                except IndexError:
1155                    # Serial reported empty
1156                    self.serial = ""
1157                continue
1158
1159            vendor = re.compile(r'^Vendor:\s+(\w+)').match(line)
1160            if vendor is not None:
1161                self._vendor = vendor.groups()[0]
1162
1163            if any_in(line, 'Firmware Version', 'Revision'):
1164                self.firmware = line.split(':')[1].strip()
1165
1166            if any_in(line, 'User Capacity', 'Total NVM Capacity', 'Namespace 1 Size/Capacity'):
1167                # TODO: support for multiple NVMe namespaces
1168                m = re.match(
1169                    r'.*:\s+([\d,. \u2019\u00a0]+)\s\D*\[?([^\]]+)?\]?', line.strip())
1170
1171                if m is not None:
1172                    tmp = m.groups()
1173                    if tmp[0] == ' ':
1174                        # This capacity is set to 0, skip it
1175                        continue
1176
1177                    self._capacity = int(
1178                        tmp[0].strip().replace(',', '').replace('.', '').replace(' ', '').replace('\u2019', '').replace('\u00a0', ''))
1179
1180                    if len(tmp) == 2 and tmp[1] is not None:
1181                        self._capacity_human = tmp[1].strip().replace(',', '.')
1182
1183            if 'SMART support' in line:
1184                # self.smart_capable = 'Available' in line
1185                # self.smart_enabled = 'Enabled' in line
1186                # Since this line repeats twice the above method is flawed
1187                # Lets try the following instead, it is a bit redundant but
1188                # more robust.
1189                if any_in(line, 'Unavailable', 'device lacks SMART capability'):
1190                    self.smart_capable = False
1191                    self.smart_enabled = False
1192                elif 'Enabled' in line:
1193                    self.smart_enabled = True
1194                elif 'Disabled' in line:
1195                    self.smart_enabled = False
1196                elif any_in(line, 'Available', 'device has SMART capability'):
1197                    self.smart_capable = True
1198                continue
1199
1200            if 'does not support SMART' in line:
1201                self.smart_capable = False
1202                self.smart_enabled = False
1203                continue
1204
1205            if 'Rotation Rate' in line:
1206                if 'Solid State Device' in line:
1207                    self.is_ssd = True
1208                elif 'rpm' in line:
1209                    self.is_ssd = False
1210                    try:
1211                        self.rotation_rate = int(
1212                            line.split(':')[1].lstrip().rstrip()[:-4])
1213                    except ValueError:
1214                        # Cannot parse the RPM? Assigning None instead
1215                        self.rotation_rate = None
1216                continue
1217
1218            if 'SMART overall-health self-assessment' in line:  # ATA devices
1219                if line.split(':')[1].strip() == 'PASSED':
1220                    self.assessment = 'PASS'
1221                else:
1222                    self.assessment = 'FAIL'
1223                continue
1224
1225            if 'SMART Health Status' in line:  # SCSI devices
1226                if line.split(':')[1].strip() == 'OK':
1227                    self.assessment = 'PASS'
1228                else:
1229                    self.assessment = 'FAIL'
1230                    parse_ascq = True  # Set flag to capture status message
1231                    message = line.split(':')[1].lstrip().rstrip()
1232                continue
1233
1234            # Parse SMART test capabilities (ATA only)
1235            # Note: SCSI does not list this but and allows for only 'offline', 'short' and 'long'
1236            if 'SMART execute Offline immediate' in line:
1237                self.test_capabilities['offline'] = 'No' not in line
1238                continue
1239
1240            if 'Conveyance Self-test supported' in line:
1241                self.test_capabilities['conveyance'] = 'No' not in line
1242                continue
1243
1244            if 'Selective Self-test supported' in line:
1245                self.test_capabilities['selective'] = 'No' not in line
1246                continue
1247
1248            if 'Self-test supported' in line:
1249                self.test_capabilities['short'] = 'No' not in line
1250                self.test_capabilities['long'] = 'No' not in line
1251                continue
1252
1253            # Parse SMART test capabilities (NVMe only)
1254            if 'Optional Admin Commands' in line:
1255                if 'Self_Test' in line:
1256                    self.test_capabilities['short'] = True
1257                    self.test_capabilities['long'] = True
1258
1259            if 'Short self-test routine' in line:
1260                polling_minute_type = 'short'
1261                continue
1262            if 'Extended self-test routine' in line:
1263                polling_minute_type = 'long'
1264                continue
1265            if 'Conveyance self-test routine' in line:
1266                polling_minute_type = 'conveyance'
1267                continue
1268            if 'recommended polling time:' in line:
1269                self.test_polling_time[polling_minute_type] = float(
1270                    re.sub("[^0-9]", "", line)
1271                )
1272                continue
1273
1274            # For some reason smartctl does not show a currently running test
1275            # for 'ATA' in the Test log so I just have to catch it this way i guess!
1276            # For 'scsi' I still do it since it is the only place I get % remaining in scsi
1277            if 'Self-test execution status' in line:
1278                if 'progress' in line:
1279                    self._test_running = True
1280                    # for ATA the "%" remaining is on the next line
1281                    # thus set the parse_running_test flag and move on
1282                    parse_running_test = True
1283                elif '%' in line:
1284                    # for scsi the progress is on the same line
1285                    # so we can just parse it and move on
1286                    self._test_running = True
1287                    try:
1288                        self._test_progress = 100 - \
1289                            int(line.split('%')[0][-3:].strip())
1290                    except ValueError:
1291                        pass
1292                continue
1293            if parse_running_test is True:
1294                try:
1295                    self._test_progress = 100 - \
1296                        int(line.split('%')[0][-3:].strip())
1297                except ValueError:
1298                    pass
1299                parse_running_test = False
1300
1301            if "Self-test log" in line:
1302                parse_self_tests = True  # Set flag to capture test entries
1303                continue
1304
1305            #######################################
1306            #              SCSI only              #
1307            #######################################
1308            #
1309            # Everything from here on is parsing SCSI information that takes
1310            # the place of similar ATA SMART information
1311
1312            if 'Current Drive Temperature' in line or ('Temperature:' in
1313                                                       line and interface == 'nvme'):
1314                try:
1315                    self._temperature = int(
1316                        line.split(':')[-1].strip().split()[0])
1317
1318                    if 'fahrenheit' in line.lower():
1319                        self._temperature = int(
1320                            (self.temperature - 32) * 5 / 9)
1321
1322                except ValueError:
1323                    pass
1324
1325                continue
1326
1327            if 'Temperature Sensor ' in line:
1328                try:
1329                    match = re.search(
1330                        r'Temperature\sSensor\s([0-9]+):\s+(-?[0-9]+)', line)
1331                    if match:
1332                        (tempsensor_number_s, tempsensor_value_s) = match.group(1, 2)
1333                        tempsensor_number = int(tempsensor_number_s)
1334                        tempsensor_value = int(tempsensor_value_s)
1335
1336                        if 'fahrenheit' in line.lower():
1337                            tempsensor_value = int(
1338                                (tempsensor_value - 32) * 5 / 9)
1339
1340                        self.temperatures[tempsensor_number] = tempsensor_value
1341                        if self.temperature is None or tempsensor_number == 0:
1342                            self._temperature = tempsensor_value
1343                except ValueError:
1344                    pass
1345
1346                continue
1347
1348            #######################################
1349            #            Common values            #
1350            #######################################
1351
1352            # Sector sizes
1353            if 'Sector Sizes' in line:  # ATA
1354                m = re.match(
1355                    r'.* (\d+) bytes logical,\s*(\d+) bytes physical', line)
1356                if m:
1357                    self.logical_sector_size = int(m.group(1))
1358                    self.physical_sector_size = int(m.group(2))
1359                continue
1360            if 'Logical block size:' in line:  # SCSI 1/2
1361                self.logical_sector_size = int(
1362                    line.split(':')[1].strip().split(' ')[0])
1363                continue
1364            if 'Physical block size:' in line:  # SCSI 2/2
1365                self.physical_sector_size = int(
1366                    line.split(':')[1].strip().split(' ')[0])
1367                continue
1368            if 'Namespace 1 Formatted LBA Size' in line:  # NVMe
1369                # Note: we will assume that there is only one namespace
1370                self.logical_sector_size = int(
1371                    line.split(':')[1].strip().split(' ')[0])
1372                continue
1373
1374        if not self.abridged:
1375            if not interface == 'scsi':
1376                # Parse the SMART table for below-threshold attributes and create
1377                # corresponding warnings for non-SCSI disks
1378                self._make_smart_warnings()
1379
1380        # Now that we have finished the update routine, if we did not find a runnning selftest
1381        # nuke the self._test_ECD and self._test_progress
1382        if self._test_running is False:
1383            self._test_ECD = None
1384            self._test_progress = None

Represents any device attached to an internal storage interface, such as a hard drive or DVD-ROM, and detected by smartmontools. Includes eSATA (considered SATA) but excludes other external devices (USB, Firewire).

Device( name: str, interface: Optional[str] = None, abridged: bool = False, smart_options: Union[str, List[str], NoneType] = None, smartctl: pySMART.smartctl.Smartctl = <pySMART.smartctl.Smartctl object>)
 88    def __init__(self, name: str, interface: Optional[str] = None, abridged: bool = False, smart_options: Union[str, List[str], None] = None, smartctl: Smartctl = SMARTCTL):
 89        """Instantiates and initializes the `pySMART.device.Device`."""
 90        if not (
 91                interface is None or
 92                smartctl_isvalid_type(interface.lower())
 93        ):
 94            raise ValueError(
 95                'Unknown interface: {0} specified for {1}'.format(interface, name))
 96        self.abridged = abridged or interface == 'UNKNOWN INTERFACE'
 97        if smart_options is not None:
 98            if isinstance(smart_options,  str):
 99                smart_options = smart_options.split(' ')
100            smartctl.add_options(smart_options)
101        self.smartctl = smartctl
102        """
103        """
104        self.name: str = name.replace('/dev/', '').replace('nvd', 'nvme')
105        """
106        **(str):** Device's hardware ID, without the '/dev/' prefix.
107        (ie: sda (Linux), pd0 (Windows))
108        """
109        self.family: Optional[str] = None
110        """**(str):** Device's family (if any)."""
111        self.model: Optional[str] = None
112        """**(str):** Device's model number (if any)."""
113        self.serial: Optional[str] = None
114        """**(str):** Device's serial number (if any)."""
115        self._vendor: Optional[str] = None
116        """**(str):** Device's vendor (if any)."""
117        self._interface: Optional[str] = None if interface == 'UNKNOWN INTERFACE' else interface
118        """
119        **(str):** Device's interface type. Must be one of:
120            * **ATA** - Advanced Technology Attachment
121            * **SATA** - Serial ATA
122            * **SCSI** - Small Computer Systems Interface
123            * **SAS** - Serial Attached SCSI
124            * **SAT** - SCSI-to-ATA Translation (SATA device plugged into a
125            SAS port)
126            * **CSMI** - Common Storage Management Interface (Intel ICH /
127            Matrix RAID)
128        Generally this should not be specified to allow auto-detection to
129        occur. Otherwise, this value overrides the auto-detected type and could
130        produce unexpected or no data.
131        """
132        self._capacity: Optional[int] = None
133        """**(str):** Device's user capacity as reported directly by smartctl (RAW)."""
134        self._capacity_human: Optional[str] = None
135        """**(str):** Device's user capacity (human readable) as reported directly by smartctl (RAW)."""
136        self.firmware: Optional[str] = None
137        """**(str):** Device's firmware version."""
138        self.smart_capable: bool = self._interface == 'nvme'
139        """
140        **(bool):** True if the device has SMART Support Available.
141        False otherwise. This is useful for VMs amongst other things.
142        """
143        self.smart_enabled: bool = self._interface == 'nvme'
144        """
145        **(bool):** True if the device supports SMART (or SCSI equivalent) and
146        has the feature set enabled. False otherwise.
147        """
148        self.assessment: Optional[str] = None
149        """
150        **(str):** SMART health self-assessment as reported by the device.
151        """
152        self.messages: List[str] = []
153        """
154        **(list of str):** Contains any SMART warnings or other error messages
155        reported by the device (ie: ascq codes).
156        """
157        self.is_ssd: bool = self._interface == 'nvme'
158        """
159        **(bool):** True if this device is a Solid State Drive.
160        False otherwise.
161        """
162        self.rotation_rate: Optional[int] = None
163        """
164        **(int):** The Roatation Rate of the Drive if it is not a SSD.
165        The Metric is RPM.
166        """
167        self.test_capabilities = {
168            'offline': False,  # SMART execute Offline immediate (ATA only)
169            'short': 'nvme' not in self.name,  # SMART short Self-test
170            'long': 'nvme' not in self.name,  # SMART long Self-test
171            'conveyance': False,  # SMART Conveyance Self-Test (ATA only)
172            'selective': False,  # SMART Selective Self-Test (ATA only)
173        }
174        # Note have not included 'offline' test for scsi as it runs in the foregorund
175        # mode. While this may be beneficial to us in someways it is against the
176        # general layout and pattern that the other tests issued using pySMART are
177        # followed hence not doing it currently
178        """
179        **(dict): ** This dictionary contains key == 'Test Name' and
180        value == 'True/False' of self-tests that this device is capable of.
181        """
182        # Note: The above are just default values and can/will be changed
183        # upon update() when the attributes and type of the disk is actually
184        # determined.
185        self.test_polling_time = {
186            'short': 10,
187            'long': 1000,
188            'conveyance': 20,
189        }
190        """
191        **(dict): ** This dictionary contains key == 'Test Name' and
192        value == int of approximate times to run each test type that this
193        device is capable of.
194        """
195        # Note: The above are just default values and can/will be changed
196        # upon update() when the attributes and type of the disk is actually
197        # determined.
198        self.tests: List[TestEntry] = []
199        """
200        **(list of `TestEntry`):** Contains the complete SMART self-test log
201        for this device, as provided by smartctl.
202        """
203        self._test_running = False
204        """
205        **(bool):** True if a self-test is currently being run.
206        False otherwise.
207        """
208        self._test_ECD = None
209        """
210        **(str):** Estimated completion time of the running SMART selftest.
211        Not provided by SAS/SCSI devices.
212        """
213        self._test_progress = None
214        """
215        **(int):** Estimate progress percantage of the running SMART selftest.
216        """
217        self._temperature: Optional[int] = None
218        """
219        **(int or None): Since SCSI disks do not report attributes like ATA ones
220        we need to grep/regex the shit outta the normal "smartctl -a" output.
221        In case the device have more than one temperature sensor the first value
222        will be stored here too.
223        Note: Temperatures are always in Celsius (if possible).
224        """
225        self.temperatures: Dict[int, int] = {}
226        """
227        **(dict of int): NVMe disks usually report multiple temperatures, which
228        will be stored here if available. Keys are sensor numbers as reported in
229        output data.
230        Note: Temperatures are always in Celsius (if possible).
231        """
232        self.logical_sector_size: Optional[int] = None
233        """
234        **(int):** The logical sector size of the device (or LBA).
235        """
236        self.physical_sector_size: Optional[int] = None
237        """
238        **(int):** The physical sector size of the device.
239        """
240        self.if_attributes: Union[None,
241                                  AtaAttributes,
242                                  NvmeAttributes,
243                                  SCSIAttributes] = None
244        """
245        **(NvmeAttributes):** This object may vary for each device interface attributes.
246        It will store all data obtained from smartctl
247        """
248
249        if self.name is None:
250            warnings.warn(
251                "\nDevice '{0}' does not exist! This object should be destroyed.".format(
252                    name)
253            )
254            return
255        # If no interface type was provided, scan for the device
256        # Lets do this only for the non-abridged case
257        # (we can work with no interface for abridged case)
258        elif self._interface is None and not self.abridged:
259            logger.debug(
260                "Determining interface of disk: {0}".format(self.name))
261            raw, returncode = self.smartctl.generic_call(
262                ['-d', 'test', self.dev_reference])
263
264            if len(raw) > 0:
265                # I do not like this parsing logic but it works for now!
266                # just for reference _stdout.split('\n') gets us
267                # something like
268                # [
269                #     ...copyright string...,
270                #     '',
271                #     "/dev/ada2: Device of type 'atacam' [ATA] detected",
272                #     "/dev/ada2: Device of type 'atacam' [ATA] opened",
273                #     ''
274                # ]
275                # The above example should be enough for anyone to understand the line below
276                try:
277                    for line in reversed(raw):
278                        if "opened" in line:
279                            self._interface = line.split("'")[1]
280
281                            if self._interface == "nvme":  # if nvme set SMART to true
282                                self.smart_capable = True
283                                self.smart_enabled = True
284                                self.is_ssd = True
285
286                            break
287                except:
288                    # for whatever reason we could not get the interface type
289                    # we should mark this as an `abbridged` case and move on
290                    self._interface = None
291                    self.abbridged = True
292                # TODO: Uncomment the classify call if we ever find out that we need it
293                # Disambiguate the generic interface to a specific type
294                # self._classify()
295            else:
296                warnings.warn(
297                    "\nDevice '{0}' does not exist! This object should be destroyed.".format(
298                        name)
299                )
300                return
301        # If a valid device was detected, populate its information
302        # OR if in unabridged mode, then do it even without interface info
303        if self._interface is not None or self.abridged:
304            self.update()

Instantiates and initializes the pySMART.device.Device.

abridged
smartctl
name: str

(str): Device's hardware ID, without the '/dev/' prefix. (ie: sda (Linux), pd0 (Windows))

family: Optional[str]

(str): Device's family (if any).

model: Optional[str]

(str): Device's model number (if any).

serial: Optional[str]

(str): Device's serial number (if any).

firmware: Optional[str]

(str): Device's firmware version.

smart_capable: bool

(bool): True if the device has SMART Support Available. False otherwise. This is useful for VMs amongst other things.

smart_enabled: bool

(bool): True if the device supports SMART (or SCSI equivalent) and has the feature set enabled. False otherwise.

assessment: Optional[str]

(str): SMART health self-assessment as reported by the device.

messages: List[str]

(list of str): Contains any SMART warnings or other error messages reported by the device (ie: ascq codes).

is_ssd: bool

(bool): True if this device is a Solid State Drive. False otherwise.

rotation_rate: Optional[int]

(int): The Roatation Rate of the Drive if it is not a SSD. The Metric is RPM.

test_capabilities

*(dict): * This dictionary contains key == 'Test Name' and value == 'True/False' of self-tests that this device is capable of.

test_polling_time

*(dict): * This dictionary contains key == 'Test Name' and value == int of approximate times to run each test type that this device is capable of.

tests: List[TestEntry]

(list of TestEntry): Contains the complete SMART self-test log for this device, as provided by smartctl.

temperatures: Dict[int, int]

**(dict of int): NVMe disks usually report multiple temperatures, which will be stored here if available. Keys are sensor numbers as reported in output data. Note: Temperatures are always in Celsius (if possible).

logical_sector_size: Optional[int]

(int): The logical sector size of the device (or LBA).

physical_sector_size: Optional[int]

(int): The physical sector size of the device.

if_attributes: Union[NoneType, pySMART.interface.ata.AtaAttributes, pySMART.interface.nvme.NvmeAttributes, pySMART.interface.scsi.SCSIAttributes]

(NvmeAttributes): This object may vary for each device interface attributes. It will store all data obtained from smartctl

attributes: List[Optional[Attribute]]
306    @property
307    def attributes(self) -> List[Optional[Attribute]]:
308        """Returns the SMART attributes of the device.
309        Note: This is only filled with ATA/SATA attributes. SCSI/SAS/NVMe devices will have empty lists!!
310        @deprecated: Use `if_attributes` instead.
311
312        Returns:
313            list of `Attribute`: The SMART attributes of the device.
314        """
315        if self.if_attributes is None or not isinstance(self.if_attributes, AtaAttributes):
316            return [None] * 256
317        else:
318            return self.if_attributes.legacyAttributes

Returns the SMART attributes of the device. Note: This is only filled with ATA/SATA attributes. SCSI/SAS/NVMe devices will have empty lists!! @deprecated: Use if_attributes instead.

Returns: list of Attribute: The SMART attributes of the device.

dev_interface: Optional[str]
320    @property
321    def dev_interface(self) -> Optional[str]:
322        """Returns the internal interface type of the device.
323           It may not be the same as the interface type as used by smartctl.
324
325        Returns:
326            str: The interface type of the device. (example: ata, scsi, nvme)
327                 None if the interface type could not be determined.
328        """
329        # Try to get the fine-tuned interface type
330        fineType = self._classify()
331
332        # If return still contains a megaraid, just asume it's type
333        if 'megaraid' in fineType:
334            # If any attributes is not None and has at least non None value, then it is a sat+megaraid device
335            if isinstance(self.if_attributes, AtaAttributes):
336                return 'ata'
337            else:
338                return 'sas'
339
340        return fineType

Returns the internal interface type of the device. It may not be the same as the interface type as used by smartctl.

Returns: str: The interface type of the device. (example: ata, scsi, nvme) None if the interface type could not be determined.

temperature: Optional[int]
342    @property
343    def temperature(self) -> Optional[int]:
344        """Returns the temperature of the device.
345
346        Returns:
347            int: The temperature of the device in Celsius.
348                 None if the temperature could not be determined.
349        """
350        if self.if_attributes is None:
351            return self._temperature
352        else:
353            return self.if_attributes.temperature or self._temperature

Returns the temperature of the device.

Returns: int: The temperature of the device in Celsius. None if the temperature could not be determined.

smartctl_interface: Optional[str]
355    @property
356    def smartctl_interface(self) -> Optional[str]:
357        """Returns the interface type of the device as it is used in smartctl.
358
359        Returns:
360            str: The interface type of the device. (example: ata, scsi, nvme)
361                 None if the interface type could not be determined.
362        """
363        return self._interface

Returns the interface type of the device as it is used in smartctl.

Returns: str: The interface type of the device. (example: ata, scsi, nvme) None if the interface type could not be determined.

interface: Optional[str]
365    @property
366    def interface(self) -> Optional[str]:
367        """Returns the interface type of the device as it is used in smartctl.
368
369        Returns:
370            str: The interface type of the device. (example: ata, scsi, nvme)
371                 None if the interface type could not be determined.
372        """
373        return self.smartctl_interface

Returns the interface type of the device as it is used in smartctl.

Returns: str: The interface type of the device. (example: ata, scsi, nvme) None if the interface type could not be determined.

dev_reference: str
375    @property
376    def dev_reference(self) -> str:
377        """The reference to the device as provided by smartctl.
378           - On unix-like systems, this is the path to the device. (example /dev/<name>)
379           - On MacOS, this is the name of the device. (example <name>)
380           - On Windows, this is the drive letter of the device. (example <drive letter>)
381
382        Returns:
383            str: The reference to the device as provided by smartctl.
384        """
385
386        # detect if we are on MacOS
387        if 'IOService' in self.name:
388            return self.name
389
390        # otherwise asume we are on unix-like systems
391        return os.path.join('/dev/', self.name)

The reference to the device as provided by smartctl.

  • On unix-like systems, this is the path to the device. (example /dev/)
  • On MacOS, this is the name of the device. (example )
  • On Windows, this is the drive letter of the device. (example )

Returns: str: The reference to the device as provided by smartctl.

vendor: Optional[str]
393    @property
394    def vendor(self) -> Optional[str]:
395        """Returns the vendor of the device.
396
397        Returns:
398            str: The vendor of the device.
399        """
400        if self._vendor:
401            return self._vendor
402
403        # If family is present, try to stract from family. Skip anything but letters.
404        elif self.family:
405            filter = re.search(r'^[a-zA-Z]+', self.family.strip())
406            if filter:
407                return filter.group(0)
408
409        # If model is present, try to stract from model. Skip anything but letters.
410        elif self.model:
411            filter = re.search(r'^[a-zA-Z]+', self.model.strip())
412            if filter:
413                return filter.group(0)
414
415        # If all else fails, return None
416        return None

Returns the vendor of the device.

Returns: str: The vendor of the device.

capacity: Optional[str]
418    @property
419    def capacity(self) -> Optional[str]:
420        """Returns the capacity in the raw smartctl format.
421        This may be deprecated in the future and its only retained for compatibility.
422
423        Returns:
424            str: The capacity in the raw smartctl format
425        """
426        return self._capacity_human

Returns the capacity in the raw smartctl format. This may be deprecated in the future and its only retained for compatibility.

Returns: str: The capacity in the raw smartctl format

diagnostics: Optional[pySMART.interface.scsi.diagnostics.Diagnostics]
428    @property
429    def diagnostics(self) -> Optional[Diagnostics]:
430        """Gets the old/deprecated version of SCSI/SAS diagnostics atribute.
431        """
432        if self.if_attributes is None or not isinstance(self.if_attributes, SCSIAttributes):
433            return None
434
435        else:
436            return self.if_attributes.diagnostics

Gets the old/deprecated version of SCSI/SAS diagnostics atribute.

diags: Dict[str, str]
438    @property
439    def diags(self) -> Dict[str, str]:
440        """Gets the old/deprecated version of SCSI/SAS diags atribute.
441        """
442        if self.if_attributes is None or not isinstance(self.if_attributes, SCSIAttributes):
443            # Return an empty dict if the device is not SCSI/SAS
444            return {}
445
446        else:
447            return self.if_attributes.diagnostics.get_classic_format()

Gets the old/deprecated version of SCSI/SAS diags atribute.

size_raw: Optional[str]
449    @property
450    def size_raw(self) -> Optional[str]:
451        """Returns the capacity in the raw smartctl format.
452
453        Returns:
454            str: The capacity in the raw smartctl format
455        """
456        return self._capacity_human

Returns the capacity in the raw smartctl format.

Returns: str: The capacity in the raw smartctl format

size: int
458    @property
459    def size(self) -> int:
460        """Returns the capacity in bytes
461
462        Returns:
463            int: The capacity in bytes
464        """
465        import humanfriendly
466
467        if self._capacity is not None:
468            return self._capacity
469        elif self._capacity_human is not None:
470            return humanfriendly.parse_size(self._capacity_human)
471        else:
472            return 0

Returns the capacity in bytes

Returns: int: The capacity in bytes

sector_size: int
474    @property
475    def sector_size(self) -> int:
476        """Returns the sector size of the device.
477
478        Returns:
479            int: The sector size of the device in Bytes. If undefined, we'll assume 512B
480        """
481        if self.logical_sector_size is not None:
482            return self.logical_sector_size
483        elif self.physical_sector_size is not None:
484            return self.physical_sector_size
485        else:
486            return 512

Returns the sector size of the device.

Returns: int: The sector size of the device in Bytes. If undefined, we'll assume 512B

def smart_toggle(self, action: str) -> Tuple[bool, List[str]]:
535    def smart_toggle(self, action: str) -> Tuple[bool, List[str]]:
536        """
537        A basic function to enable/disable SMART on device.
538
539        # Args:
540        * **action (str):** Can be either 'on'(for enabling) or 'off'(for disabling).
541
542        # Returns"
543        * **(bool):** Return True (if action succeded) else False
544        * **(List[str]):** None if option succeded else contains the error message.
545        """
546        # Lets make the action verb all lower case
547        if self._interface == 'nvme':
548            return False, ['NVME devices do not currently support toggling SMART enabled']
549        action_lower = action.lower()
550        if action_lower not in ['on', 'off']:
551            return False, ['Unsupported action {0}'.format(action)]
552        # Now lets check if the device's smart enabled status is already that of what
553        # the supplied action is intending it to be. If so then just return successfully
554        if self.smart_enabled:
555            if action_lower == 'on':
556                return True, []
557        else:
558            if action_lower == 'off':
559                return True, []
560        if self._interface is not None:
561            raw, returncode = self.smartctl.generic_call(
562                ['-s', action_lower, '-d', self._interface, self.dev_reference])
563        else:
564            raw, returncode = self.smartctl.generic_call(
565                ['-s', action_lower, self.dev_reference])
566
567        if returncode != 0:
568            return False, raw
569        # if everything worked out so far lets perform an update() and check the result
570        self.update()
571        if action_lower == 'off' and self.smart_enabled:
572            return False, ['Failed to turn SMART off.']
573        if action_lower == 'on' and not self.smart_enabled:
574            return False, ['Failed to turn SMART on.']
575        return True, []

A basic function to enable/disable SMART on device.

Args:

  • action (str): Can be either 'on'(for enabling) or 'off'(for disabling).

Returns"

  • (bool): Return True (if action succeded) else False
  • (List[str]): None if option succeded else contains the error message.
def all_attributes(self, print_fn=<built-in function print>):
577    def all_attributes(self, print_fn=print):
578        """
579        Prints the entire SMART attribute table, in a format similar to
580        the output of smartctl.
581        allows usage of custom print function via parameter print_fn by default uses print
582        """
583        header_printed = False
584        for attr in self.attributes:
585            if attr is not None:
586                if not header_printed:
587                    print_fn("{0:>3} {1:24}{2:4}{3:4}{4:4}{5:9}{6:8}{7:12}{8}"
588                             .format('ID#', 'ATTRIBUTE_NAME', 'CUR', 'WST', 'THR', 'TYPE', 'UPDATED', 'WHEN_FAIL',
589                                     'RAW'))
590                    header_printed = True
591                print_fn(attr)
592        if not header_printed:
593            print_fn('This device does not support SMART attributes.')

Prints the entire SMART attribute table, in a format similar to the output of smartctl. allows usage of custom print function via parameter print_fn by default uses print

def all_selftests(self):
595    def all_selftests(self):
596        """
597        Prints the entire SMART self-test log, in a format similar to
598        the output of smartctl.
599        """
600        if self.tests:
601            all_tests = []
602            if smartctl_type(self._interface) == 'scsi':
603                header = "{0:3}{1:17}{2:23}{3:7}{4:14}{5:15}".format(
604                    'ID',
605                    'Test Description',
606                    'Status',
607                    'Hours',
608                    '1st_Error@LBA',
609                    '[SK  ASC  ASCQ]'
610                )
611            else:
612                header = ("{0:3}{1:17}{2:30}{3:5}{4:7}{5:17}".format(
613                    'ID',
614                    'Test_Description',
615                    'Status',
616                    'Left',
617                    'Hours',
618                    '1st_Error@LBA'))
619            all_tests.append(header)
620            for test in self.tests:
621                all_tests.append(str(test))
622
623            return all_tests
624        else:
625            no_tests = 'No self-tests have been logged for this device.'
626            return no_tests

Prints the entire SMART self-test log, in a format similar to the output of smartctl.

def get_selftest_result(self, output=None):
743    def get_selftest_result(self, output=None):
744        """
745        Refreshes a device's `pySMART.device.Device.tests` attribute to obtain
746        the latest test results. If a new test result is obtained, its content
747        is returned.
748
749        # Args:
750        * **output (str, optional):** If set to 'str', the string
751        representation of the most recent test result will be returned, instead
752        of a `Test_Entry` object.
753
754        # Returns:
755        * **(int):** Return status code. One of the following:
756            * 0 - Success. Object (or optionally, string rep) is attached.
757            * 1 - Self-test in progress. Must wait for it to finish.
758            * 2 - No new test results.
759            * 3 - The Self-test was Aborted by host
760        * **(`Test_Entry` or str):** Most recent `Test_Entry` object (or
761        optionally it's string representation) if new data exists.  Status
762        message string on failure.
763        * **(int):** Estimate progress percantage of the running SMART selftest, if known.
764        Otherwise 'None'.
765        """
766        # SCSI self-test logs hold 20 entries while ATA logs hold 21
767        if smartctl_type(self._interface) == 'scsi':
768            maxlog = 20
769        else:
770            maxlog = 21
771        # If we looked only at the most recent test result we could be fooled
772        # by two short tests run close together (within the same hour)
773        # appearing identical. Comparing the length of the log adds some
774        # confidence until it maxes, as above. Comparing the least-recent test
775        # result greatly diminishes the chances that two sets of two tests each
776        # were run within an hour of themselves, but with 16-17 other tests run
777        # in between them.
778        if self.tests:
779            _first_entry = self.tests[0]
780            _len = len(self.tests)
781            _last_entry = self.tests[_len - 1]
782        else:
783            _len = 0
784        self.update()
785        # Since I have changed the update() parsing to DTRT to pickup currently
786        # running selftests we can now purely rely on that for self._test_running
787        # Thus check for that variable first and return if it is True with appropos message.
788        if self._test_running is True:
789            return 1, 'Self-test in progress. Please wait.', self._test_progress
790        # Check whether the list got longer (ie: new entry)
791        # If so return the newest test result
792        # If not, because it's max size already, check for new entries
793        if (
794                (len(self.tests) != _len) or
795                (
796                    _len == maxlog and
797                    (
798                        _first_entry.type != self.tests[0].type or
799                        _first_entry.hours != self.tests[0].hours or
800                        _last_entry.type != self.tests[len(self.tests) - 1].type or
801                        _last_entry.hours != self.tests[len(
802                            self.tests) - 1].hours
803                    )
804                )
805        ):
806            return (
807                0 if 'Aborted' not in self.tests[0].status else 3,
808                str(self.tests[0]) if output == 'str' else self.tests[0],
809                None
810            )
811        else:
812            return 2, 'No new self-test results found.', None

Refreshes a device's pySMART.device.Device.tests attribute to obtain the latest test results. If a new test result is obtained, its content is returned.

Args:

  • output (str, optional): If set to 'str', the string representation of the most recent test result will be returned, instead of a Test_Entry object.

Returns:

  • (int): Return status code. One of the following:
    • 0 - Success. Object (or optionally, string rep) is attached.
    • 1 - Self-test in progress. Must wait for it to finish.
    • 2 - No new test results.
    • 3 - The Self-test was Aborted by host
  • (Test_Entry or str): Most recent Test_Entry object (or optionally it's string representation) if new data exists. Status message string on failure.
  • (int): Estimate progress percantage of the running SMART selftest, if known. Otherwise 'None'.
def abort_selftest(self):
814    def abort_selftest(self):
815        """
816        Aborts non-captive SMART Self Tests.   Note that this command
817        will  abort the Offline Immediate Test routine only if your disk
818        has the "Abort Offline collection upon new command"  capability.
819
820        # Args: Nothing (just aborts directly)
821
822        # Returns:
823        * **(int):** The returncode of calling `smartctl -X device_path`
824        """
825        return self.smartctl.test_stop(smartctl_type(self._interface), self.dev_reference)

Aborts non-captive SMART Self Tests. Note that this command will abort the Offline Immediate Test routine only if your disk has the "Abort Offline collection upon new command" capability.

Args: Nothing (just aborts directly)

Returns:

  • (int): The returncode of calling smartctl -X device_path
def run_selftest(self, test_type, ETA_type='date'):
827    def run_selftest(self, test_type, ETA_type='date'):
828        """
829        Instructs a device to begin a SMART self-test. All tests are run in
830        'offline' / 'background' mode, allowing normal use of the device while
831        it is being tested.
832
833        # Args:
834        * **test_type (str):** The type of test to run. Accepts the following
835        (not case sensitive):
836            * **short** - Brief electo-mechanical functionality check.
837            Generally takes 2 minutes or less.
838            * **long** - Thorough electro-mechanical functionality check,
839            including complete recording media scan. Generally takes several
840            hours.
841            * **conveyance** - Brief test used to identify damage incurred in
842            shipping. Generally takes 5 minutes or less. **This test is not
843            supported by SAS or SCSI devices.**
844            * **offline** - Runs SMART Immediate Offline Test. The effects of
845            this test are visible only in that it updates the SMART Attribute
846            values, and if errors are found they will appear in the SMART error
847            log, visible with the '-l error' option to smartctl. **This test is
848            not supported by SAS or SCSI devices in pySMART use cli smartctl for
849            running 'offline' selftest (runs in foreground) on scsi devices.**
850            * **ETA_type** - Format to return the estimated completion time/date
851            in. Default is 'date'. One could otherwise specidy 'seconds'.
852            Again only for ATA devices.
853
854        # Returns:
855        * **(int):** Return status code.  One of the following:
856            * 0 - Self-test initiated successfully
857            * 1 - Previous self-test running. Must wait for it to finish.
858            * 2 - Unknown or unsupported (by the device) test type requested.
859            * 3 - Unspecified smartctl error. Self-test not initiated.
860        * **(str):** Return status message.
861        * **(str)/(float):** Estimated self-test completion time if a test is started.
862        The optional argument of 'ETA_type' (see above) controls the return type.
863        if 'ETA_type' == 'date' then a date string is returned else seconds(float)
864        is returned.
865        Note: The self-test completion time can only be obtained for ata devices.
866        Otherwise 'None'.
867        """
868        # Lets call get_selftest_result() here since it does an update() and
869        # checks for an existing selftest is running or not, this way the user
870        # can issue a test from the cli and this can still pick that up
871        # Also note that we do not need to obtain the results from this as the
872        # data is already stored in the Device class object's variables
873        self.get_selftest_result()
874        if self._test_running:
875            return 1, 'Self-test in progress. Please wait.', self._test_ECD
876        test_type = test_type.lower()
877        interface = smartctl_type(self._interface)
878        try:
879            if not self.test_capabilities[test_type]:
880                return (
881                    2,
882                    "Device {0} does not support the '{1}' test ".format(
883                        self.name, test_type),
884                    None
885                )
886        except KeyError:
887            return 2, "Unknown test type '{0}' requested.".format(test_type), None
888
889        raw, rc = self.smartctl.test_start(
890            interface, test_type, self.dev_reference)
891        _success = False
892        _running = False
893        for line in raw:
894            if 'has begun' in line:
895                _success = True
896                self._test_running = True
897            if 'aborting current test' in line:
898                _running = True
899                try:
900                    self._test_progress = 100 - \
901                        int(line.split('(')[-1].split('%')[0])
902                except ValueError:
903                    pass
904
905            if _success and 'complete after' in line:
906                self._test_ECD = line[25:].rstrip()
907                if ETA_type == 'seconds':
908                    self._test_ECD = mktime(
909                        strptime(self._test_ECD, '%a %b %d %H:%M:%S %Y')) - time()
910                self._test_progress = 0
911        if _success:
912            return 0, 'Self-test started successfully', self._test_ECD
913        else:
914            if _running:
915                return 1, 'Self-test already in progress. Please wait.', self._test_ECD
916            else:
917                return 3, 'Unspecified Error. Self-test not started.', None

Instructs a device to begin a SMART self-test. All tests are run in 'offline' / 'background' mode, allowing normal use of the device while it is being tested.

Args:

  • test_type (str): The type of test to run. Accepts the following (not case sensitive):
    • short - Brief electo-mechanical functionality check. Generally takes 2 minutes or less.
    • long - Thorough electro-mechanical functionality check, including complete recording media scan. Generally takes several hours.
    • conveyance - Brief test used to identify damage incurred in shipping. Generally takes 5 minutes or less. This test is not supported by SAS or SCSI devices.
    • offline - Runs SMART Immediate Offline Test. The effects of this test are visible only in that it updates the SMART Attribute values, and if errors are found they will appear in the SMART error log, visible with the '-l error' option to smartctl. This test is not supported by SAS or SCSI devices in pySMART use cli smartctl for running 'offline' selftest (runs in foreground) on scsi devices.
    • ETA_type - Format to return the estimated completion time/date in. Default is 'date'. One could otherwise specidy 'seconds'. Again only for ATA devices.

Returns:

  • (int): Return status code. One of the following:
    • 0 - Self-test initiated successfully
    • 1 - Previous self-test running. Must wait for it to finish.
    • 2 - Unknown or unsupported (by the device) test type requested.
    • 3 - Unspecified smartctl error. Self-test not initiated.
  • (str): Return status message.
  • (str)/(float): Estimated self-test completion time if a test is started. The optional argument of 'ETA_type' (see above) controls the return type. if 'ETA_type' == 'date' then a date string is returned else seconds(float) is returned. Note: The self-test completion time can only be obtained for ata devices. Otherwise 'None'.
def run_selftest_and_wait(self, test_type, output=None, polling=5, progress_handler=None):
919    def run_selftest_and_wait(self, test_type, output=None, polling=5, progress_handler=None):
920        """
921        This is essentially a wrapper around run_selftest() such that we
922        call self.run_selftest() and wait on the running selftest till
923        it finished before returning.
924        The above holds true for all pySMART supported tests with the
925        exception of the 'offline' test (ATA only) as it immediately
926        returns, since the entire test only affects the smart error log
927        (if any errors found) and updates the SMART attributes. Other
928        than that it is not visibile anywhere else, so we start it and
929        simply return.
930        # Args:
931        * **test_type (str):** The type of test to run. Accepts the following
932        (not case sensitive):
933            * **short** - Brief electo-mechanical functionality check.
934            Generally takes 2 minutes or less.
935            * **long** - Thorough electro-mechanical functionality check,
936            including complete recording media scan. Generally takes several
937            hours.
938            * **conveyance** - Brief test used to identify damage incurred in
939            shipping. Generally takes 5 minutes or less. **This test is not
940            supported by SAS or SCSI devices.**
941            * **offline** - Runs SMART Immediate Offline Test. The effects of
942            this test are visible only in that it updates the SMART Attribute
943            values, and if errors are found they will appear in the SMART error
944            log, visible with the '-l error' option to smartctl. **This test is
945            not supported by SAS or SCSI devices in pySMART use cli smartctl for
946            running 'offline' selftest (runs in foreground) on scsi devices.**
947        * **output (str, optional):** If set to 'str', the string
948            representation of the most recent test result will be returned,
949            instead of a `Test_Entry` object.
950        * **polling (int, default=5):** The time duration to sleep for between
951            checking for test_results and progress.
952        * **progress_handler (function, optional):** This if provided is called
953            with self._test_progress as the supplied argument everytime a poll to
954            check the status of the selftest is done.
955        # Returns:
956        * **(int):** Return status code.  One of the following:
957            * 0 - Self-test executed and finished successfully
958            * 1 - Previous self-test running. Must wait for it to finish.
959            * 2 - Unknown or illegal test type requested.
960            * 3 - The Self-test was Aborted by host
961            * 4 - Unspecified smartctl error. Self-test not initiated.
962        * **(`Test_Entry` or str):** Most recent `Test_Entry` object (or
963        optionally it's string representation) if new data exists.  Status
964        message string on failure.
965        """
966        test_initiation_result = self.run_selftest(test_type)
967        if test_initiation_result[0] != 0:
968            return test_initiation_result[:2]
969        if test_type == 'offline':
970            self._test_running = False
971        # if not then the test initiated correctly and we can start the polling.
972        # For now default 'polling' value is 5 seconds if not specified by the user
973
974        # Do an initial check, for good measure.
975        # In the probably impossible case that self._test_running is instantly False...
976        selftest_results = self.get_selftest_result(output=output)
977        while self._test_running:
978            if selftest_results[0] != 1:
979                # the selftest is run and finished lets return with the results
980                break
981            # Otherwise see if we are provided with the progress_handler to update progress
982            if progress_handler is not None:
983                progress_handler(
984                    selftest_results[2] if selftest_results[2] is not None else 50)
985            # Now sleep 'polling' seconds before checking the progress again
986            sleep(polling)
987
988            # Check after the sleep to ensure we return the right result, and not an old one.
989            selftest_results = self.get_selftest_result(output=output)
990
991        # Now if (selftes_results[0] == 2) i.e No new selftest (because the same
992        # selftest was run twice within the last hour) but we know for a fact that
993        # we just ran a new selftest then just return the latest entry in self.tests
994        if selftest_results[0] == 2:
995            selftest_return_value = 0 if 'Aborted' not in self.tests[0].status else 3
996            return selftest_return_value, str(self.tests[0]) if output == 'str' else self.tests[0]
997        return selftest_results[:2]

This is essentially a wrapper around run_selftest() such that we call self.run_selftest() and wait on the running selftest till it finished before returning. The above holds true for all pySMART supported tests with the exception of the 'offline' test (ATA only) as it immediately returns, since the entire test only affects the smart error log (if any errors found) and updates the SMART attributes. Other than that it is not visibile anywhere else, so we start it and simply return.

Args:

  • test_type (str): The type of test to run. Accepts the following (not case sensitive):
    • short - Brief electo-mechanical functionality check. Generally takes 2 minutes or less.
    • long - Thorough electro-mechanical functionality check, including complete recording media scan. Generally takes several hours.
    • conveyance - Brief test used to identify damage incurred in shipping. Generally takes 5 minutes or less. This test is not supported by SAS or SCSI devices.
    • offline - Runs SMART Immediate Offline Test. The effects of this test are visible only in that it updates the SMART Attribute values, and if errors are found they will appear in the SMART error log, visible with the '-l error' option to smartctl. This test is not supported by SAS or SCSI devices in pySMART use cli smartctl for running 'offline' selftest (runs in foreground) on scsi devices.
  • output (str, optional): If set to 'str', the string representation of the most recent test result will be returned, instead of a Test_Entry object.
  • polling (int, default=5): The time duration to sleep for between checking for test_results and progress.
  • progress_handler (function, optional): This if provided is called with self._test_progress as the supplied argument everytime a poll to check the status of the selftest is done.

    Returns:

  • (int): Return status code. One of the following:

    • 0 - Self-test executed and finished successfully
    • 1 - Previous self-test running. Must wait for it to finish.
    • 2 - Unknown or illegal test type requested.
    • 3 - The Self-test was Aborted by host
    • 4 - Unspecified smartctl error. Self-test not initiated.
  • (Test_Entry or str): Most recent Test_Entry object (or optionally it's string representation) if new data exists. Status message string on failure.
def update(self):
 999    def update(self):
1000        """
1001        Queries for device information using smartctl and updates all
1002        class members, including the SMART attribute table and self-test log.
1003        Can be called at any time to refresh the `pySMART.device.Device`
1004        object's data content.
1005        """
1006        # set temperature back to None so that if update() is called more than once
1007        # any logic that relies on self.temperature to be None to rescan it works.it
1008        self._temperature = None
1009        # same for temperatures
1010        self.temperatures = {}
1011        if self.abridged:
1012            interface = None
1013            raw = self.smartctl.info(self.dev_reference)
1014
1015        else:
1016            interface = smartctl_type(self._interface)
1017            raw = self.smartctl.all(
1018                self.dev_reference, interface)
1019
1020        parse_self_tests = False
1021        parse_running_test = False
1022        parse_ascq = False
1023        polling_minute_type = None
1024        message = ''
1025        self.tests = []
1026        self._test_running = False
1027        self._test_progress = None
1028        # Lets skip the first couple of non-useful lines
1029        _stdout = raw[4:]
1030
1031        #######################################
1032        #           Encoding fixing           #
1033        #######################################
1034        # In some scenarios, smartctl returns some lines with a different/strange encoding
1035        # This is a workaround to fix that
1036        for i, line in enumerate(_stdout):
1037            # character ' ' (U+202F) should be removed
1038            _stdout[i] = line.replace('\u202f', '')
1039
1040        #######################################
1041        #   Dedicated interface attributes    #
1042        #######################################
1043        if AtaAttributes.has_compatible_data(iter(_stdout)):
1044            self.if_attributes = AtaAttributes(iter(_stdout))
1045
1046        elif self.dev_interface == 'nvme':
1047            self.if_attributes = NvmeAttributes(iter(_stdout))
1048
1049            # Get Tests
1050            for test in self.if_attributes.tests:
1051                self.tests.append(TestEntry('nvme', test.num, test.description, test.status, test.powerOnHours,
1052                                  test.failingLBA, nsid=test.nsid, segment=test.seg, sct=test.sct, code=test.code, remain=100-test.progress))
1053
1054            # Set running test
1055            if any(test.status == 'Running' for test in self.if_attributes.tests):
1056                self._test_running = True
1057                self._test_progress = self.if_attributes.tests[0].progress
1058            else:
1059                self._test_running = False
1060                self._test_progress = None
1061
1062        elif SCSIAttributes.has_compatible_data(iter(_stdout)):
1063            self.__get_smart_status(iter(_stdout))
1064            self.if_attributes = SCSIAttributes(iter(_stdout),
1065                                                abridged=self.abridged,
1066                                                smartEnabled=self.smart_enabled,
1067                                                sm=self.smartctl,
1068                                                dev_reference=self.dev_reference)
1069
1070            # Import (for now) the tests from if_attributes
1071            self.tests = self.if_attributes.tests
1072
1073        else:
1074            self.if_attributes = None
1075
1076        #######################################
1077        #    Global / generic  attributes     #
1078        #######################################
1079        stdout_iter = iter(_stdout)
1080        for line in stdout_iter:
1081            if line.strip() == '':  # Blank line stops sub-captures
1082                if parse_self_tests is True:
1083                    parse_self_tests = False
1084                if parse_ascq:
1085                    parse_ascq = False
1086                    self.messages.append(message)
1087            if parse_ascq:
1088                message += ' ' + line.lstrip().rstrip()
1089            if parse_self_tests:
1090                # Detect Test Format
1091
1092                ## SCSI/SAS FORMAT ##
1093                # Example smartctl output
1094                # SMART Self-test log
1095                # Num  Test              Status                 segment  LifeTime  LBA_first_err [SK ASC ASQ]
1096                #      Description                              number   (hours)
1097                # # 1  Background short  Completed                   -   33124                 - [-   -    -]
1098                format_scsi = re.compile(
1099                    r'^[#\s]*(\d+)\s{2,}(.*[^\s])\s{2,}(.*[^\s])\s{2,}(.*[^\s])\s{2,}(.*[^\s])\s{2,}(.*[^\s])\s+\[([^\s]+)\s+([^\s]+)\s+([^\s]+)\]$').match(line)
1100
1101                ## ATA FORMAT ##
1102                # Example smartctl output:
1103                # SMART Self-test log structure revision number 1
1104                # Num  Test_Description    Status                  Remaining  LifeTime(hours)  LBA_of_first_error
1105                # # 1  Extended offline    Completed without error       00%     46660         -
1106                format_ata = re.compile(
1107                    r'^[#\s]*(\d+)\s{2,}(.*[^\s])\s{2,}(.*[^\s])\s{1,}(.*[^\s])\s{2,}(.*[^\s])\s{2,}(.*[^\s])$').match(line)
1108
1109                if format_scsi is not None:
1110                    ## SCSI FORMAT ##
1111                    pass
1112
1113                elif format_ata is not None:
1114                    ## ATA FORMAT ##
1115                    format = 'ata'
1116                    parsed = format_ata.groups()
1117                    num = parsed[0]
1118                    test_type = parsed[1]
1119                    status = parsed[2]
1120                    remain = parsed[3]
1121                    hours = parsed[4]
1122                    lba = parsed[5]
1123
1124                    try:
1125                        num = int(num)
1126                    except:
1127                        num = None
1128
1129                    self.tests.append(
1130                        TestEntry(format, num, test_type, status,
1131                                  hours, lba, remain=remain)
1132                    )
1133                else:
1134                    pass
1135
1136            # Basic device information parsing
1137            if any_in(line, 'Device Model', 'Product', 'Model Number'):
1138                self.model = line.split(':')[1].lstrip().rstrip()
1139                self._guess_smart_type(line.lower())
1140                continue
1141
1142            if 'Model Family' in line:
1143                self.family = line.split(':')[1].strip()
1144                self._guess_smart_type(line.lower())
1145                continue
1146
1147            if 'LU WWN' in line:
1148                self._guess_smart_type(line.lower())
1149                continue
1150
1151            if any_in(line, 'Serial Number', 'Serial number'):
1152                try:
1153                    self.serial = line.split(':')[1].split()[0].rstrip()
1154                except IndexError:
1155                    # Serial reported empty
1156                    self.serial = ""
1157                continue
1158
1159            vendor = re.compile(r'^Vendor:\s+(\w+)').match(line)
1160            if vendor is not None:
1161                self._vendor = vendor.groups()[0]
1162
1163            if any_in(line, 'Firmware Version', 'Revision'):
1164                self.firmware = line.split(':')[1].strip()
1165
1166            if any_in(line, 'User Capacity', 'Total NVM Capacity', 'Namespace 1 Size/Capacity'):
1167                # TODO: support for multiple NVMe namespaces
1168                m = re.match(
1169                    r'.*:\s+([\d,. \u2019\u00a0]+)\s\D*\[?([^\]]+)?\]?', line.strip())
1170
1171                if m is not None:
1172                    tmp = m.groups()
1173                    if tmp[0] == ' ':
1174                        # This capacity is set to 0, skip it
1175                        continue
1176
1177                    self._capacity = int(
1178                        tmp[0].strip().replace(',', '').replace('.', '').replace(' ', '').replace('\u2019', '').replace('\u00a0', ''))
1179
1180                    if len(tmp) == 2 and tmp[1] is not None:
1181                        self._capacity_human = tmp[1].strip().replace(',', '.')
1182
1183            if 'SMART support' in line:
1184                # self.smart_capable = 'Available' in line
1185                # self.smart_enabled = 'Enabled' in line
1186                # Since this line repeats twice the above method is flawed
1187                # Lets try the following instead, it is a bit redundant but
1188                # more robust.
1189                if any_in(line, 'Unavailable', 'device lacks SMART capability'):
1190                    self.smart_capable = False
1191                    self.smart_enabled = False
1192                elif 'Enabled' in line:
1193                    self.smart_enabled = True
1194                elif 'Disabled' in line:
1195                    self.smart_enabled = False
1196                elif any_in(line, 'Available', 'device has SMART capability'):
1197                    self.smart_capable = True
1198                continue
1199
1200            if 'does not support SMART' in line:
1201                self.smart_capable = False
1202                self.smart_enabled = False
1203                continue
1204
1205            if 'Rotation Rate' in line:
1206                if 'Solid State Device' in line:
1207                    self.is_ssd = True
1208                elif 'rpm' in line:
1209                    self.is_ssd = False
1210                    try:
1211                        self.rotation_rate = int(
1212                            line.split(':')[1].lstrip().rstrip()[:-4])
1213                    except ValueError:
1214                        # Cannot parse the RPM? Assigning None instead
1215                        self.rotation_rate = None
1216                continue
1217
1218            if 'SMART overall-health self-assessment' in line:  # ATA devices
1219                if line.split(':')[1].strip() == 'PASSED':
1220                    self.assessment = 'PASS'
1221                else:
1222                    self.assessment = 'FAIL'
1223                continue
1224
1225            if 'SMART Health Status' in line:  # SCSI devices
1226                if line.split(':')[1].strip() == 'OK':
1227                    self.assessment = 'PASS'
1228                else:
1229                    self.assessment = 'FAIL'
1230                    parse_ascq = True  # Set flag to capture status message
1231                    message = line.split(':')[1].lstrip().rstrip()
1232                continue
1233
1234            # Parse SMART test capabilities (ATA only)
1235            # Note: SCSI does not list this but and allows for only 'offline', 'short' and 'long'
1236            if 'SMART execute Offline immediate' in line:
1237                self.test_capabilities['offline'] = 'No' not in line
1238                continue
1239
1240            if 'Conveyance Self-test supported' in line:
1241                self.test_capabilities['conveyance'] = 'No' not in line
1242                continue
1243
1244            if 'Selective Self-test supported' in line:
1245                self.test_capabilities['selective'] = 'No' not in line
1246                continue
1247
1248            if 'Self-test supported' in line:
1249                self.test_capabilities['short'] = 'No' not in line
1250                self.test_capabilities['long'] = 'No' not in line
1251                continue
1252
1253            # Parse SMART test capabilities (NVMe only)
1254            if 'Optional Admin Commands' in line:
1255                if 'Self_Test' in line:
1256                    self.test_capabilities['short'] = True
1257                    self.test_capabilities['long'] = True
1258
1259            if 'Short self-test routine' in line:
1260                polling_minute_type = 'short'
1261                continue
1262            if 'Extended self-test routine' in line:
1263                polling_minute_type = 'long'
1264                continue
1265            if 'Conveyance self-test routine' in line:
1266                polling_minute_type = 'conveyance'
1267                continue
1268            if 'recommended polling time:' in line:
1269                self.test_polling_time[polling_minute_type] = float(
1270                    re.sub("[^0-9]", "", line)
1271                )
1272                continue
1273
1274            # For some reason smartctl does not show a currently running test
1275            # for 'ATA' in the Test log so I just have to catch it this way i guess!
1276            # For 'scsi' I still do it since it is the only place I get % remaining in scsi
1277            if 'Self-test execution status' in line:
1278                if 'progress' in line:
1279                    self._test_running = True
1280                    # for ATA the "%" remaining is on the next line
1281                    # thus set the parse_running_test flag and move on
1282                    parse_running_test = True
1283                elif '%' in line:
1284                    # for scsi the progress is on the same line
1285                    # so we can just parse it and move on
1286                    self._test_running = True
1287                    try:
1288                        self._test_progress = 100 - \
1289                            int(line.split('%')[0][-3:].strip())
1290                    except ValueError:
1291                        pass
1292                continue
1293            if parse_running_test is True:
1294                try:
1295                    self._test_progress = 100 - \
1296                        int(line.split('%')[0][-3:].strip())
1297                except ValueError:
1298                    pass
1299                parse_running_test = False
1300
1301            if "Self-test log" in line:
1302                parse_self_tests = True  # Set flag to capture test entries
1303                continue
1304
1305            #######################################
1306            #              SCSI only              #
1307            #######################################
1308            #
1309            # Everything from here on is parsing SCSI information that takes
1310            # the place of similar ATA SMART information
1311
1312            if 'Current Drive Temperature' in line or ('Temperature:' in
1313                                                       line and interface == 'nvme'):
1314                try:
1315                    self._temperature = int(
1316                        line.split(':')[-1].strip().split()[0])
1317
1318                    if 'fahrenheit' in line.lower():
1319                        self._temperature = int(
1320                            (self.temperature - 32) * 5 / 9)
1321
1322                except ValueError:
1323                    pass
1324
1325                continue
1326
1327            if 'Temperature Sensor ' in line:
1328                try:
1329                    match = re.search(
1330                        r'Temperature\sSensor\s([0-9]+):\s+(-?[0-9]+)', line)
1331                    if match:
1332                        (tempsensor_number_s, tempsensor_value_s) = match.group(1, 2)
1333                        tempsensor_number = int(tempsensor_number_s)
1334                        tempsensor_value = int(tempsensor_value_s)
1335
1336                        if 'fahrenheit' in line.lower():
1337                            tempsensor_value = int(
1338                                (tempsensor_value - 32) * 5 / 9)
1339
1340                        self.temperatures[tempsensor_number] = tempsensor_value
1341                        if self.temperature is None or tempsensor_number == 0:
1342                            self._temperature = tempsensor_value
1343                except ValueError:
1344                    pass
1345
1346                continue
1347
1348            #######################################
1349            #            Common values            #
1350            #######################################
1351
1352            # Sector sizes
1353            if 'Sector Sizes' in line:  # ATA
1354                m = re.match(
1355                    r'.* (\d+) bytes logical,\s*(\d+) bytes physical', line)
1356                if m:
1357                    self.logical_sector_size = int(m.group(1))
1358                    self.physical_sector_size = int(m.group(2))
1359                continue
1360            if 'Logical block size:' in line:  # SCSI 1/2
1361                self.logical_sector_size = int(
1362                    line.split(':')[1].strip().split(' ')[0])
1363                continue
1364            if 'Physical block size:' in line:  # SCSI 2/2
1365                self.physical_sector_size = int(
1366                    line.split(':')[1].strip().split(' ')[0])
1367                continue
1368            if 'Namespace 1 Formatted LBA Size' in line:  # NVMe
1369                # Note: we will assume that there is only one namespace
1370                self.logical_sector_size = int(
1371                    line.split(':')[1].strip().split(' ')[0])
1372                continue
1373
1374        if not self.abridged:
1375            if not interface == 'scsi':
1376                # Parse the SMART table for below-threshold attributes and create
1377                # corresponding warnings for non-SCSI disks
1378                self._make_smart_warnings()
1379
1380        # Now that we have finished the update routine, if we did not find a runnning selftest
1381        # nuke the self._test_ECD and self._test_progress
1382        if self._test_running is False:
1383            self._test_ECD = None
1384            self._test_progress = None

Queries for device information using smartctl and updates all class members, including the SMART attribute table and self-test log. Can be called at any time to refresh the pySMART.device.Device object's data content.

def smart_health_assement( disk_name: str, interface: Optional[str] = None, smartctl: pySMART.smartctl.Smartctl = <pySMART.smartctl.Smartctl object>) -> Optional[str]:
49def smart_health_assement(disk_name: str, interface: Optional[str] = None, smartctl: Smartctl = SMARTCTL) -> Optional[str]:
50    """
51    This function gets the SMART Health Status of the disk (IF the disk
52    is SMART capable and smart is enabled on it else returns None).
53    This function is to be used only in abridged mode and not otherwise,
54    since in non-abridged mode update gets this information anyways.
55
56    Args:
57        disk_name (str): name of the disk
58        interface (str, optional): interface type of the disk (e.g. 'sata', 'scsi', 'nvme',... Defaults to None.)
59
60    Returns:
61        str: SMART Health Status of the disk. Returns None if the disk is not SMART capable or smart is not enabled on it.
62             Possible values are 'PASS', 'FAIL' or None.
63    """
64    assessment = None
65    raw = smartctl.health(os.path.join(
66        '/dev/', disk_name.replace('nvd', 'nvme')), interface)
67    line = raw[4]  # We only need this line
68    if 'SMART overall-health self-assessment' in line:  # ATA devices
69        if line.split(':')[1].strip() == 'PASSED':
70            assessment = 'PASS'
71        else:
72            assessment = 'FAIL'
73    if 'SMART Health Status' in line:  # SCSI devices
74        if line.split(':')[1].strip() == 'OK':
75            assessment = 'PASS'
76        else:
77            assessment = 'FAIL'
78    return assessment

This function gets the SMART Health Status of the disk (IF the disk is SMART capable and smart is enabled on it else returns None). This function is to be used only in abridged mode and not otherwise, since in non-abridged mode update gets this information anyways.

Args: disk_name (str): name of the disk interface (str, optional): interface type of the disk (e.g. 'sata', 'scsi', 'nvme',... Defaults to None.)

Returns: str: SMART Health Status of the disk. Returns None if the disk is not SMART capable or smart is not enabled on it. Possible values are 'PASS', 'FAIL' or None.