diskinfo 4.2.0__tar.gz → 4.2.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (25) hide show
  1. {diskinfo-4.2.0/src/diskinfo.egg-info → diskinfo-4.2.1}/PKG-INFO +1 -1
  2. {diskinfo-4.2.0 → diskinfo-4.2.1}/pyproject.toml +1 -1
  3. {diskinfo-4.2.0 → diskinfo-4.2.1}/src/diskinfo/disk.py +6 -6
  4. {diskinfo-4.2.0 → diskinfo-4.2.1}/src/diskinfo/filesystem.py +4 -4
  5. {diskinfo-4.2.0 → diskinfo-4.2.1}/src/diskinfo/partition.py +4 -4
  6. {diskinfo-4.2.0 → diskinfo-4.2.1}/src/diskinfo/utils.py +34 -8
  7. {diskinfo-4.2.0 → diskinfo-4.2.1/src/diskinfo.egg-info}/PKG-INFO +1 -1
  8. {diskinfo-4.2.0 → diskinfo-4.2.1}/test/test_disk.py +12 -0
  9. {diskinfo-4.2.0 → diskinfo-4.2.1}/test/test_filesystem.py +25 -0
  10. {diskinfo-4.2.0 → diskinfo-4.2.1}/test/test_partition.py +18 -3
  11. {diskinfo-4.2.0 → diskinfo-4.2.1}/test/test_utils.py +27 -6
  12. {diskinfo-4.2.0 → diskinfo-4.2.1}/LICENSE +0 -0
  13. {diskinfo-4.2.0 → diskinfo-4.2.1}/README.md +0 -0
  14. {diskinfo-4.2.0 → diskinfo-4.2.1}/setup.cfg +0 -0
  15. {diskinfo-4.2.0 → diskinfo-4.2.1}/src/diskinfo/__init__.py +0 -0
  16. {diskinfo-4.2.0 → diskinfo-4.2.1}/src/diskinfo/demo.py +0 -0
  17. {diskinfo-4.2.0 → diskinfo-4.2.1}/src/diskinfo/diskinfo.py +0 -0
  18. {diskinfo-4.2.0 → diskinfo-4.2.1}/src/diskinfo/disksmart.py +0 -0
  19. {diskinfo-4.2.0 → diskinfo-4.2.1}/src/diskinfo/disktype.py +0 -0
  20. {diskinfo-4.2.0 → diskinfo-4.2.1}/src/diskinfo.egg-info/SOURCES.txt +0 -0
  21. {diskinfo-4.2.0 → diskinfo-4.2.1}/src/diskinfo.egg-info/dependency_links.txt +0 -0
  22. {diskinfo-4.2.0 → diskinfo-4.2.1}/src/diskinfo.egg-info/requires.txt +0 -0
  23. {diskinfo-4.2.0 → diskinfo-4.2.1}/src/diskinfo.egg-info/top_level.txt +0 -0
  24. {diskinfo-4.2.0 → diskinfo-4.2.1}/test/test_diskinfo.py +0 -0
  25. {diskinfo-4.2.0 → diskinfo-4.2.1}/test/test_disksmart.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: diskinfo
3
- Version: 4.2.0
3
+ Version: 4.2.1
4
4
  Summary: Disk information Python library for Linux
5
5
  Author-email: Peter Sulyok <peter@sulyok.net>
6
6
  License-Expression: MIT
@@ -1,7 +1,7 @@
1
1
  [project]
2
2
  name = "diskinfo"
3
3
  description = "Disk information Python library for Linux"
4
- version = "4.2.0"
4
+ version = "4.2.1"
5
5
  readme = "README.md"
6
6
  authors = [
7
7
  { name = "Peter Sulyok", email = "peter@sulyok.net" }
@@ -7,7 +7,7 @@ import re
7
7
  from typing import List, Tuple, Union
8
8
  import pySMART
9
9
  from pyudev import Context, Device, Devices, DeviceNotFoundAtPathError
10
- from diskinfo.utils import _read_file, size_in_hrf, _pyudev_getenc
10
+ from diskinfo.utils import _read_file, size_in_hrf, _pyudev_getenc, _pyudev_getstr
11
11
  from diskinfo.disktype import DiskType
12
12
  from diskinfo.filesystem import FileSystem
13
13
  from diskinfo.partition import Partition
@@ -186,11 +186,11 @@ class Disk:
186
186
  raise RuntimeError(f"Disk type cannot be determined: disk={pyudev_device.sys_name}, queue/rotational={rotational}.")
187
187
 
188
188
  # Read further disk attributes.
189
- self.__serial_number = pyudev_device.get("ID_SERIAL_SHORT")
190
- self.__firmware = pyudev_device.get("ID_REVISION")
191
- self.__wwn = pyudev_device.get("ID_WWN")
192
- self.__part_table_type = pyudev_device.get("ID_PART_TABLE_TYPE")
193
- self.__part_table_uuid = pyudev_device.get("ID_PART_TABLE_UUID")
189
+ self.__serial_number = _pyudev_getstr(pyudev_device, "ID_SERIAL_SHORT")
190
+ self.__firmware = _pyudev_getstr(pyudev_device, "ID_REVISION")
191
+ self.__wwn = _pyudev_getstr(pyudev_device, "ID_WWN")
192
+ self.__part_table_type = _pyudev_getstr(pyudev_device, "ID_PART_TABLE_TYPE")
193
+ self.__part_table_uuid = _pyudev_getstr(pyudev_device, "ID_PART_TABLE_UUID")
194
194
  self.__model = _pyudev_getenc(pyudev_device, "ID_MODEL")
195
195
 
196
196
  # Read `/dev/disk/by-byid/` and `/dev/disk/by-path/` path elements.
@@ -6,7 +6,7 @@ import os
6
6
  import re
7
7
  from typing import Tuple
8
8
  from pyudev import Device
9
- from diskinfo.utils import size_in_hrf, _pyudev_getenc
9
+ from diskinfo.utils import size_in_hrf, _pyudev_getenc, _pyudev_getstr
10
10
 
11
11
  # Path to the kernel mount table.
12
12
  _PROC_MOUNTS = "/proc/mounts"
@@ -60,9 +60,9 @@ class FileSystem:
60
60
  # Read filesystem attributes from udev properties.
61
61
  self.__fs_label = _pyudev_getenc(_device, "ID_FS_LABEL")
62
62
  self.__fs_uuid = _pyudev_getenc(_device, "ID_FS_UUID")
63
- self.__fs_type = _device.get("ID_FS_TYPE")
64
- self.__fs_version = _device.get("ID_FS_VERSION")
65
- self.__fs_usage = _device.get("ID_FS_USAGE")
63
+ self.__fs_type = _pyudev_getstr(_device, "ID_FS_TYPE")
64
+ self.__fs_version = _pyudev_getstr(_device, "ID_FS_VERSION")
65
+ self.__fs_usage = _pyudev_getstr(_device, "ID_FS_USAGE")
66
66
  self.__fs_mounting_point = ""
67
67
  self.__fs_size = 0
68
68
  self.__fs_free_size = 0
@@ -4,7 +4,7 @@
4
4
  #
5
5
  from typing import List, Tuple
6
6
  from pyudev import Device
7
- from diskinfo.utils import size_in_hrf, _pyudev_getint, _pyudev_getesc
7
+ from diskinfo.utils import size_in_hrf, _pyudev_getint, _pyudev_getesc, _pyudev_getstr
8
8
  from diskinfo.filesystem import FileSystem
9
9
 
10
10
 
@@ -95,10 +95,10 @@ class Partition:
95
95
  continue
96
96
 
97
97
  # Save further partition attributes.
98
- self.__part_scheme = _device.get("ID_PART_ENTRY_SCHEME")
98
+ self.__part_scheme = _pyudev_getstr(_device, "ID_PART_ENTRY_SCHEME")
99
99
  self.__part_label = _pyudev_getesc(_device, "ID_PART_ENTRY_NAME")
100
- self.__part_uuid = _device.get("ID_PART_ENTRY_UUID")
101
- self.__part_type = _device.get("ID_PART_ENTRY_TYPE")
100
+ self.__part_uuid = _pyudev_getstr(_device, "ID_PART_ENTRY_UUID")
101
+ self.__part_type = _pyudev_getstr(_device, "ID_PART_ENTRY_TYPE")
102
102
  self.__part_number = _pyudev_getint(_device, "ID_PART_ENTRY_NUMBER", 0)
103
103
  self.__part_offset = _pyudev_getint(_device, "ID_PART_ENTRY_OFFSET", -1)
104
104
  self.__part_size = _pyudev_getint(_device, "ID_PART_ENTRY_SIZE", 0)
@@ -242,17 +242,42 @@ def _pyudev_getint(device: Device, key: str, default_value: int = None) -> Union
242
242
  return default_value
243
243
 
244
244
 
245
- def _pyudev_getesc(dev: Device, key: str) -> Union[str, None]:
245
+ def _pyudev_getstr(device: Device, key: str) -> str:
246
+ """Returns a string attribute value of a pyudev.Device() class. The function returns an empty string if the
247
+ attribute does not exist.
248
+
249
+ Args:
250
+ device (pyudev.Device): pyudev.Device() class
251
+ key (str): attribute key
252
+
253
+ Returns:
254
+ str: attribute value or empty string
255
+
256
+ Example:
257
+ An example about the use of the function::
258
+
259
+ >>> from diskinfo import *
260
+ >>> from pyudev import *
261
+ >>> c = Context()
262
+ >>> d = Devices.from_name(c, "block", "sda1")
263
+ >>> print(_pyudev_getstr(d, "ID_FS_TYPE"))
264
+ ext4
265
+
266
+ """
267
+ return device.get(key) or ""
268
+
269
+
270
+ def _pyudev_getesc(dev: Device, key: str) -> str:
246
271
  """Returns a device attribute value where udev stores the escaped value in the property itself, so there is
247
272
  no `_ENC` key pair (e.g. `ID_PART_ENTRY_NAME`). The escape sequences of the value will be decoded and the
248
- result will be stripped.
273
+ result will be stripped. The function returns an empty string if the attribute does not exist.
249
274
 
250
275
  Args:
251
276
  device (pyudev.Device): pyudev.Device() class
252
277
  key (str): attribute key
253
278
 
254
279
  Returns:
255
- Union[str, None]: str attribute value or None
280
+ str: attribute value or empty string
256
281
 
257
282
  Example:
258
283
  An example about the use of the function::
@@ -268,21 +293,22 @@ def _pyudev_getesc(dev: Device, key: str) -> Union[str, None]:
268
293
  value = dev.get(key)
269
294
  if value:
270
295
  return _unescape_udev_value(value).strip()
271
- return value
296
+ return ""
272
297
 
273
298
 
274
- def _pyudev_getenc(dev: Device, key: str) -> Union[str, None]:
299
+ def _pyudev_getenc(dev: Device, key: str) -> str:
275
300
  """Returns one of the device attribute key pairs. There are udev attributes key pairs (e.g. `ID_MODEL` and
276
301
  `ID_MODEL_ENC`), where this function reads and decodes the `_ENC` version of the key if it exists, otherwise
277
302
  the simple key will be read. The `_ENC` version is always preferred, because the simple key contains the
278
- sanitized value of udev (e.g. the space characters are replaced there).
303
+ sanitized value of udev (e.g. the space characters are replaced there). The function returns an empty string
304
+ if none of the attributes exists.
279
305
 
280
306
  Args:
281
307
  device (pyudev.Device): pyudev.Device() class
282
308
  key (str): attribute key
283
309
 
284
310
  Returns:
285
- Union[str, None]: str attribute value or None
311
+ str: attribute value or empty string
286
312
 
287
313
  Example:
288
314
  An example about the use of the function::
@@ -302,7 +328,7 @@ def _pyudev_getenc(dev: Device, key: str) -> Union[str, None]:
302
328
  if value:
303
329
  return _unescape_udev_value(value).strip()
304
330
  # Read the simple key.
305
- return dev.get(key)
331
+ return dev.get(key) or ""
306
332
 
307
333
 
308
334
  # End
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: diskinfo
3
- Version: 4.2.0
3
+ Version: 4.2.1
4
4
  Summary: Disk information Python library for Linux
5
5
  Author-email: Peter Sulyok <peter@sulyok.net>
6
6
  License-Expression: MIT
@@ -1053,6 +1053,18 @@ def test_disk_blank_no_filesystem():
1053
1053
  assert d.get_filesystem() is None
1054
1054
 
1055
1055
 
1056
+ def test_disk_missing_properties_return_empty_strings():
1057
+ """Disk returns empty strings (not None) for all missing udev string properties."""
1058
+ dev = _make_device(name="sdc", dev_id="8:32", size=2097152, phys_bs=512, log_bs=512, rotational="1",
1059
+ serial=None, firmware=None, wwn=None, model=None, part_table_type=None, part_table_uuid=None)
1060
+ with pyudev_patched(dev):
1061
+ d = Disk(dev.sys_name)
1062
+
1063
+ values = [d.get_serial_number(), d.get_firmware(), d.get_wwn(), d.get_model(), d.get_partition_table_type(),
1064
+ d.get_partition_table_uuid()]
1065
+ assert values == [""] * 6
1066
+
1067
+
1056
1068
  def test_disk_with_partitions_no_raw_filesystem(ssd_device):
1057
1069
  """Disk does not detect a raw filesystem when partitions exist."""
1058
1070
  part = _make_partition_device(
@@ -83,6 +83,31 @@ def test_filesystem_stores_all_attributes():
83
83
  assert fs.get_fs_mounting_point() == "/mnt/data"
84
84
 
85
85
 
86
+ @pytest.mark.parametrize("attribute", ["fs_label", "fs_uuid", "fs_type", "fs_version", "fs_usage"])
87
+ def test_filesystem_missing_attribute_returns_empty_string(attribute):
88
+ """FileSystem returns an empty string (not None) when a udev property is missing."""
89
+ dev = _make_fs_device(**{attribute: None})
90
+ with patch("builtins.open", _proc_mounts()):
91
+ fs = FileSystem(dev)
92
+
93
+ value = getattr(fs, f"get_{attribute}")()
94
+ assert value == ""
95
+ assert isinstance(value, str)
96
+
97
+
98
+ def test_filesystem_without_any_property_returns_empty_strings():
99
+ """FileSystem of a device without file system properties (e.g. a reserved partition) returns empty values."""
100
+ dev = _make_fs_device(fs_label=None, fs_uuid=None, fs_type=None, fs_version=None, fs_usage=None)
101
+ with patch("builtins.open", _proc_mounts()):
102
+ fs = FileSystem(dev)
103
+
104
+ values = [fs.get_fs_label(), fs.get_fs_uuid(), fs.get_fs_type(), fs.get_fs_version(), fs.get_fs_usage(),
105
+ fs.get_fs_mounting_point()]
106
+ assert values == [""] * 6
107
+ assert fs.get_fs_size() == 0
108
+ assert fs.get_fs_free_size() == 0
109
+
110
+
86
111
  # ── FileSystem.__init__ – /proc/mounts parsing ──────────────────────────
87
112
 
88
113
 
@@ -397,15 +397,30 @@ def test_partition_decodes_part_label(escaped, expected):
397
397
  assert p.get_part_label() == expected
398
398
 
399
399
 
400
- def test_partition_missing_part_label_stays_none():
401
- """Partition keeps the missing partition label as None instead of raising an exception."""
400
+ def test_partition_missing_part_label_returns_empty_string():
401
+ """Partition returns an empty string for a missing partition label instead of raising an exception."""
402
402
  dev = _make_part_device(part_label=None)
403
403
  m = _proc_mounts(("/dev/sda1", "/boot/efi"))
404
404
  with (patch("builtins.open", m),
405
405
  patch("os.statvfs", return_value=_statvfs_result(f_bavail=512000))):
406
406
  p = Partition(dev)
407
407
 
408
- assert p.get_part_label() is None
408
+ assert p.get_part_label() == ""
409
+
410
+
411
+ def test_partition_missing_properties_return_empty_strings():
412
+ """Partition and its FileSystem return empty strings (not None) for all missing udev string properties."""
413
+ dev = _make_part_device(scheme=None, part_label=None, part_uuid=None, part_type=None, fs_label=None,
414
+ fs_uuid=None, fs_type=None, fs_version=None, fs_usage=None)
415
+ with patch("builtins.open", _proc_mounts()):
416
+ p = Partition(dev)
417
+
418
+ fs = p.get_filesystem()
419
+ values = [p.get_part_scheme(), p.get_part_label(), p.get_part_uuid(), p.get_part_type(), fs.get_fs_label(),
420
+ fs.get_fs_uuid(), fs.get_fs_type(), fs.get_fs_version(), fs.get_fs_usage()]
421
+ assert values == [""] * 9
422
+ # A typical caller pattern must not fail on a partition without a label.
423
+ assert fs.get_fs_label().startswith("hassos") is False
409
424
 
410
425
 
411
426
  # End
@@ -14,6 +14,7 @@ from diskinfo.utils import (
14
14
  _pyudev_getenc,
15
15
  _unescape_udev_value,
16
16
  _pyudev_getesc,
17
+ _pyudev_getstr,
17
18
  )
18
19
 
19
20
 
@@ -246,6 +247,26 @@ def test_pyudev_getint_non_integer_no_default_returns_none():
246
247
  assert _pyudev_getint(dev, "key") is None
247
248
 
248
249
 
250
+ # ── _pyudev_getstr ────────────────────────────────────────────────────────────
251
+
252
+
253
+ def test_pyudev_getstr_returns_value():
254
+ """_pyudev_getstr() returns the value of an existing key."""
255
+ dev = MagicMock()
256
+ dev.get.side_effect = {"ID_FS_TYPE": "ext4"}.get
257
+ assert _pyudev_getstr(dev, "ID_FS_TYPE") == "ext4"
258
+
259
+
260
+ @pytest.mark.parametrize("value", [None, ""], ids=["missing", "empty"])
261
+ def test_pyudev_getstr_missing_or_empty_returns_empty_string(value):
262
+ """_pyudev_getstr() returns an empty string if the key does not exist or its value is empty."""
263
+ dev = MagicMock()
264
+ dev.get.return_value = value
265
+ result = _pyudev_getstr(dev, "ID_FS_TYPE")
266
+ assert result == ""
267
+ assert isinstance(result, str)
268
+
269
+
249
270
  # ── _pyudev_getenc ────────────────────────────────────────────────────────────
250
271
 
251
272
 
@@ -290,11 +311,11 @@ def test_pyudev_getenc_decodes_non_ascii_without_space():
290
311
  assert _pyudev_getenc(dev, "ID_FS_LABEL") == "CLÉUSB"
291
312
 
292
313
 
293
- def test_pyudev_getenc_returns_none_when_both_keys_absent():
294
- """_pyudev_getenc() returns None when neither _ENC nor plain key exists."""
314
+ def test_pyudev_getenc_returns_empty_string_when_both_keys_absent():
315
+ """_pyudev_getenc() returns an empty string when neither _ENC nor plain key exists."""
295
316
  dev = MagicMock()
296
317
  dev.get.return_value = None
297
- assert _pyudev_getenc(dev, "ID_MODEL") is None
318
+ assert _pyudev_getenc(dev, "ID_MODEL") == ""
298
319
 
299
320
 
300
321
  def test_pyudev_getenc_strips_whitespace_after_decode():
@@ -354,11 +375,11 @@ def test_pyudev_getesc_strips_whitespace_after_decode():
354
375
  assert _pyudev_getesc(dev, "ID_PART_ENTRY_NAME") == "Boot"
355
376
 
356
377
 
357
- def test_pyudev_getesc_missing_key_returns_none():
358
- """_pyudev_getesc() returns None if the key does not exist."""
378
+ def test_pyudev_getesc_missing_key_returns_empty_string():
379
+ """_pyudev_getesc() returns an empty string if the key does not exist."""
359
380
  dev = MagicMock()
360
381
  dev.get.return_value = None
361
- assert _pyudev_getesc(dev, "ID_PART_ENTRY_NAME") is None
382
+ assert _pyudev_getesc(dev, "ID_PART_ENTRY_NAME") == ""
362
383
 
363
384
 
364
385
  def test_pyudev_getesc_empty_value_is_kept():
File without changes
File without changes
File without changes
File without changes
File without changes