remap-badblocks 0.8__py3-none-any.whl → 0.9__py3-none-any.whl

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 (42) hide show
  1. remap_badblocks/__init__.py +1 -1
  2. remap_badblocks/cli/commands/update.py +8 -6
  3. remap_badblocks/src/badblocks/_remap_badblocks.py +23 -23
  4. remap_badblocks/src/mapping.py +5 -1
  5. remap_badblocks-0.9.dist-info/METADATA +107 -0
  6. remap_badblocks-0.9.dist-info/RECORD +36 -0
  7. {remap_badblocks-0.8.dist-info → remap_badblocks-0.9.dist-info}/WHEEL +1 -1
  8. __init__.py +0 -0
  9. cli/__init__.py +0 -0
  10. cli/__main__.py +0 -239
  11. cli/commands/__init__.py +0 -8
  12. cli/commands/add.py +0 -91
  13. cli/commands/apply.py +0 -81
  14. cli/commands/get.py +0 -28
  15. cli/commands/remove.py +0 -45
  16. cli/commands/update.py +0 -208
  17. cli/commands/version.py +0 -15
  18. remap_badblocks-0.8.dist-info/METADATA +0 -130
  19. remap_badblocks-0.8.dist-info/RECORD +0 -66
  20. src/badblocks/_compute_good_ranges.py +0 -43
  21. src/badblocks/_find_badblocks.py +0 -76
  22. src/badblocks/_mapping_generation.py +0 -12
  23. src/badblocks/_remap_badblocks.py +0 -114
  24. src/badblocks/badblocks.py +0 -40
  25. src/devices/__init__.py +0 -0
  26. src/devices/device_config.py +0 -62
  27. src/devices/devices_config.py +0 -300
  28. src/devices/exceptions.py +0 -22
  29. src/devices_config_constants.py +0 -3
  30. src/mapping.py +0 -109
  31. src/remappers/_check_applied_devices.py +0 -10
  32. src/remappers/_generate_dm_table.py +0 -27
  33. src/test_utils.py +0 -18
  34. src/utils/__init__.py +0 -0
  35. src/utils/_get_device_info.py +0 -43
  36. src/utils/_iterable_bytes_converter.py +0 -19
  37. src/utils/_parse_inputs.py +0 -84
  38. src/utils/_run_command.py +0 -76
  39. src/utils/_sort_devices.py +0 -26
  40. {remap_badblocks-0.8.dist-info → remap_badblocks-0.9.dist-info}/entry_points.txt +0 -0
  41. {remap_badblocks-0.8.dist-info → remap_badblocks-0.9.dist-info}/licenses/LICENSE +0 -0
  42. {remap_badblocks-0.8.dist-info → remap_badblocks-0.9.dist-info}/top_level.txt +0 -0
@@ -1,76 +0,0 @@
1
- from itertools import chain
2
- from pathlib import Path
3
- from typing import Iterable, Optional, Union
4
-
5
- from remap_badblocks.src.utils._run_command import run_command_realtime
6
-
7
-
8
- def parse_badblocks(lines: Iterable[str]) -> Iterable[int]:
9
- """Parse badblocks output and return a set of bad sector numbers."""
10
- lines = map(lambda line: line.strip(), lines)
11
- lines = filter(lambda line: line.isdigit(), lines)
12
- return map(int, lines)
13
-
14
-
15
- def read_known_badblocks(known_badblocks_file: Union[Path, str]) -> Iterable[int]:
16
- """Merge badblocks with known badblocks from a file."""
17
- with open(known_badblocks_file, "r") as f:
18
- known_badblocks = parse_badblocks(f.readlines())
19
- return known_badblocks
20
-
21
-
22
- def build_badblocks_command(
23
- device: Union[Path, str],
24
- sector_size: int,
25
- mode: str,
26
- known_badblocks_file: Optional[Union[Path, str]],
27
- blocks_range: Optional[tuple[int, int]],
28
- ) -> list[str]:
29
- """
30
- Build the badblocks command based on the mode and optional known badblocks file.
31
- blocks_range is a right-open interval
32
- """
33
- if mode == "read":
34
- cmd = ["badblocks", "-sv"]
35
- elif mode == "write":
36
- cmd = ["badblocks", "-wsv"]
37
- else:
38
- raise ValueError("Invalid mode. Use 'read' or 'write'.")
39
-
40
- cmd += ["-b", str(sector_size)]
41
-
42
- if known_badblocks_file:
43
- cmd += ["-i", str(known_badblocks_file)]
44
-
45
- cmd += [str(device)]
46
-
47
- if blocks_range:
48
- start_block, end_block = blocks_range
49
- cmd += [str(end_block), str(start_block)]
50
-
51
- return cmd
52
-
53
-
54
- def get_all_badblocks(
55
- device: Path,
56
- sector_size: int,
57
- mode: str = "read",
58
- known_badblocks_file: Optional[Path] = None,
59
- block_range: Optional[tuple[int, int]] = None,
60
- ) -> Iterable[int]:
61
- """
62
- Run badblocks on a device and return the results as a set of bad sectors.
63
- block_range is a right-open interval
64
- """
65
- badblocks: Iterable[int] = set()
66
-
67
- if known_badblocks_file:
68
- badblocks = read_known_badblocks(known_badblocks_file)
69
-
70
- cmd = build_badblocks_command(
71
- device, sector_size, mode, known_badblocks_file, block_range
72
- )
73
- badblocks_lines = run_command_realtime(cmd)
74
- badblocks = chain(badblocks, parse_badblocks(badblocks_lines))
75
-
76
- return badblocks
@@ -1,12 +0,0 @@
1
- from typing import Generator, Iterable
2
-
3
-
4
- def generate_mapping(
5
- ranges: Iterable[tuple[int, int]],
6
- ) -> Generator[tuple[int, int, int], None, None]:
7
- """Generate a mapping from good ranges."""
8
- offset = 0
9
- for start, end in ranges:
10
- length = end - start
11
- yield (offset, start, length) # (start_id_virtual, start_id_real, length)
12
- offset += length
@@ -1,114 +0,0 @@
1
- import itertools
2
- from typing import Iterable, Iterator, Optional, Union
3
-
4
- from remap_badblocks.src.devices.devices_config import Mapping
5
-
6
-
7
- def remap_badblocks(
8
- mapping: Iterable[tuple[int, int, int]],
9
- badblocks: Iterable[int],
10
- spare_sectors: Iterator[int],
11
- ) -> Iterable[tuple[int, int, int]]:
12
- sorted_badblocks = sorted(badblocks)
13
-
14
- for start_virtual, start_real, length in mapping:
15
- end_real = start_real + length - 1
16
- sorted_to_remap: list[int] = []
17
- sorted_to_remap_virtual: list[int] = []
18
- for badblock in sorted_badblocks:
19
- if start_real <= badblock <= end_real:
20
- sorted_to_remap.append(badblock)
21
- if not sorted_to_remap:
22
- # nothing to remap
23
- yield start_virtual, start_real, length
24
- else:
25
- current_start_virtual = start_virtual
26
- current_start_real = start_real
27
- for badblock in sorted_to_remap:
28
- if current_start_real < badblock:
29
- # pieces to keep
30
- yield current_start_virtual, current_start_real, badblock - current_start_real
31
- step = badblock - current_start_real + 1
32
- current_start_virtual += step
33
- current_start_real += step
34
- sorted_to_remap_virtual.append(current_start_virtual - 1)
35
- if current_start_real < end_real:
36
- # pieces to keep
37
- yield current_start_virtual, current_start_real, end_real - current_start_real + 1
38
- for virt_to_remap in sorted_to_remap_virtual:
39
- try:
40
- while (spare_sector := next(spare_sectors)) in badblocks:
41
- pass
42
- except StopIteration:
43
- raise RuntimeError
44
- # remap
45
- yield virt_to_remap, spare_sector, 1
46
-
47
-
48
- def identify_simplifiable_couple_in_mapping(
49
- mapping: list[tuple[int, int, int]],
50
- ) -> Optional[tuple[int, int]]:
51
- for i, (start_virtual_0, start_real_0, length_0) in enumerate(mapping[:-1]):
52
- end_virtual_0 = start_virtual_0 + length_0
53
- end_real_0 = start_real_0 + length_0
54
- for _j, (start_virtual_1, start_real_1, length_1) in enumerate(
55
- mapping[i + 1 :]
56
- ):
57
- j = _j + i + 1
58
- end_virtual_1 = start_virtual_1 + length_1
59
- end_real_1 = start_real_1 + length_1
60
- if (start_virtual_0 == end_virtual_1) and (start_real_0 == end_real_1):
61
- return j, i
62
- if (start_virtual_1 == end_virtual_0) and (start_real_1 == end_real_0):
63
- return i, j
64
- return None
65
-
66
-
67
- def simplify_mapping(
68
- mapping: list[tuple[int, int, int]],
69
- ) -> Iterable[tuple[int, int, int]]:
70
- while (to_simplify := identify_simplifiable_couple_in_mapping(mapping)) is not None:
71
- i, j = to_simplify
72
- element_i = mapping[i]
73
- element_j = mapping[j]
74
-
75
- simplified_element = element_i[0], element_i[1], element_i[2] + element_j[2]
76
-
77
- _i = min(i, j)
78
- _j = max(i, j)
79
-
80
- mapping = (
81
- mapping[:_i]
82
- + mapping[_i + 1 : _j]
83
- + mapping[_j + 1 :]
84
- + [simplified_element]
85
- )
86
-
87
- return mapping
88
-
89
-
90
- def iter_all_spare_sectors(
91
- n_spare_sectors: int, first_spare_sector: int
92
- ) -> Iterator[int]:
93
- return iter(range(first_spare_sector, first_spare_sector + n_spare_sectors))
94
-
95
-
96
- def iter_free_spare_sectors(
97
- spare_sectors: Iterable[int], n_used_spare_sectors: int
98
- ) -> Iterator[int]:
99
- return itertools.islice(spare_sectors, n_used_spare_sectors, None)
100
-
101
-
102
- def count_used_spare_sectors(
103
- mapping: Union[Iterable[tuple[int, int, int]], Mapping],
104
- spare_sectors: Iterable[int],
105
- ) -> int:
106
- spare_sectors = set(spare_sectors)
107
- used = 0
108
- for _, start, length in mapping:
109
- end = start + length
110
- for s in spare_sectors:
111
- if start <= s < end:
112
- used += 1
113
-
114
- return used
@@ -1,40 +0,0 @@
1
- from dataclasses import dataclass
2
- from typing import Iterable, Iterator
3
-
4
- from remap_badblocks.src.utils._iterable_bytes_converter import (
5
- iterable_from_bytes, iterable_to_bytes)
6
-
7
-
8
- @dataclass
9
- class Badblocks(Iterable[int]):
10
- badblocks: set[int]
11
-
12
- INT_LENGTH: int = 8
13
-
14
- def __init__(self, badblocks: Iterable[int]):
15
- self.badblocks = set(badblocks)
16
-
17
- def __bytes__(self) -> bytes:
18
- """
19
- Convert the Badblocks to bytes for storage.
20
- """
21
- return iterable_to_bytes(self.badblocks, length=self.INT_LENGTH)
22
-
23
- @classmethod
24
- def from_bytes(cls, data: bytes) -> "Badblocks":
25
- """
26
- Create a Badblocks instance from bytes.
27
- """
28
- return cls(badblocks=set(iterable_from_bytes(data, length=cls.INT_LENGTH)))
29
-
30
- def __len__(self) -> int:
31
- """
32
- Get the number of bad blocks.
33
- """
34
- return len(self.badblocks)
35
-
36
- def __iter__(self) -> Iterator[int]:
37
- """
38
- Iterate over the bad blocks.
39
- """
40
- return iter(self.badblocks)
src/devices/__init__.py DELETED
File without changes
@@ -1,62 +0,0 @@
1
- from dataclasses import dataclass
2
- from pathlib import Path
3
- from typing import Iterable, Optional, Union
4
-
5
- from remap_badblocks.src.badblocks.badblocks import Badblocks
6
- from remap_badblocks.src.mapping import Mapping
7
-
8
-
9
- @dataclass
10
- class DeviceConfig:
11
- id: int
12
- path: Path
13
- name: str
14
- sector_size: int
15
- badblocks: Badblocks
16
- depends_on: set[int]
17
- logical_range: tuple[int, int]
18
- apply_at_startup: bool = False
19
- spare_sectors: Optional[int] = None
20
- mapping: Optional[Mapping] = None
21
-
22
- def __init__(
23
- self,
24
- id: int,
25
- path: Union[Path, str],
26
- name: str,
27
- sector_size: int,
28
- badblocks: Iterable[int],
29
- logical_range: tuple[int, int],
30
- apply_at_startup: bool = False,
31
- depends_on: Iterable[int] = set(),
32
- spare_sectors: Optional[int] = None,
33
- mapping: Optional[Mapping] = None,
34
- ):
35
- if isinstance(path, str):
36
- path = Path(path)
37
- self.id = id
38
- self.path = path
39
- self.name = name
40
- self.sector_size = sector_size
41
- self.badblocks = Badblocks(badblocks)
42
- self.mapping = mapping if mapping is not None else None
43
- self.spare_sectors = spare_sectors
44
- self.depends_on = set(depends_on)
45
- self.logical_range = logical_range
46
- self.apply_at_startup = apply_at_startup
47
-
48
- def __str__(self) -> str:
49
- return (
50
- "DeviceConfig("
51
- f" id={self.id}, path={self.path}, name={self.name},"
52
- f" sector_size={self.sector_size}, badblocks={len(self.badblocks)}, spare_sectors={self.spare_sectors},"
53
- f" mapping={'Undefined' if self.mapping is None else 'Defined'},"
54
- f" depends_on={self.depends_on}, logical_range={self.logical_range}"
55
- ")"
56
- )
57
-
58
- def get_applied_path(self) -> Path:
59
- return Path("/dev/mapper/", self.name)
60
-
61
- def __hash__(self) -> int:
62
- return self.id
@@ -1,300 +0,0 @@
1
- import os
2
- import re
3
- import sqlite3 as sqlite
4
- from collections import OrderedDict
5
- from pathlib import Path
6
- from typing import (Any, Collection, Iterable, Literal, Optional, Union,
7
- overload)
8
-
9
- from remap_badblocks.src.badblocks.badblocks import Badblocks
10
- from remap_badblocks.src.mapping import Mapping
11
- from remap_badblocks.src.utils._iterable_bytes_converter import (
12
- iterable_from_bytes, iterable_to_bytes)
13
-
14
- from .device_config import DeviceConfig
15
- from .exceptions import DeviceNotFoundError
16
-
17
- DEFAULT_INT_LENGTH = 8
18
- DEVICE_INFO_COLUMNS_TYPES = OrderedDict(
19
- {
20
- "id": "INTEGER PRIMARY KEY AUTOINCREMENT",
21
- "path": "TEXT NOT NULL",
22
- "name": "TEXT NOT NULL",
23
- "sector_size": "INTEGER NOT NULL",
24
- "badblocks": "BLOB NOT NULL",
25
- "spare_sectors": "INTEGER",
26
- "mapping": "BLOB",
27
- "depends_on": "BLOB NOT NULL",
28
- "logical_range_start": "INTEGER NOT NULL",
29
- "logical_range_end": "INTEGER NOT NULL",
30
- "apply_at_startup": "BOOLEAN DEFAULT FALSE NOT NULL",
31
- }
32
- )
33
-
34
-
35
- class DevicesConfig:
36
- _path: Path
37
-
38
- def __init__(self, path: Union[Path, str]):
39
- if isinstance(path, str):
40
- path = Path(path)
41
- assert not path.is_dir(), "The provided path must not be a directory."
42
- self._path = path
43
- self.__init_table()
44
-
45
- def __init_table(self) -> None:
46
- """
47
- Initialize the devices table in the database.
48
- """
49
- os.makedirs(os.path.dirname(self._path), exist_ok=True)
50
- with sqlite.connect(self._path) as conn:
51
- cursor = conn.cursor()
52
- cursor.execute(
53
- """
54
- CREATE TABLE IF NOT EXISTS devices (
55
- """
56
- + ",".join(
57
- (
58
- col_name + " " + col_type
59
- for col_name, col_type in DEVICE_INFO_COLUMNS_TYPES.items()
60
- )
61
- )
62
- + """
63
- )
64
- """
65
- )
66
- conn.commit()
67
-
68
- def __check_device_in_db(self, conn: sqlite.Connection, id: int) -> bool:
69
- """
70
- Check if a device with the given ID exists in the database.
71
- """
72
- cursor = conn.cursor()
73
- cursor.execute("SELECT id FROM devices WHERE id = ? LIMIT 1", (id,))
74
- val = cursor.fetchone()
75
- return val is not None
76
-
77
- def _assert_depends_on(
78
- self, conn: sqlite.Connection, depends_on: Collection[int]
79
- ) -> None:
80
- missing: set[int] = set()
81
- for _id in depends_on:
82
- if not self.__check_device_in_db(conn, id=_id):
83
- missing.add(_id)
84
- if missing:
85
- raise DeviceNotFoundError(device_id=missing)
86
-
87
- def __parse_device_from_row(
88
- self,
89
- id: int,
90
- path: Union[Path, str],
91
- name: str,
92
- sector_size: int,
93
- badblocks: bytes,
94
- spare_sectors: Optional[int],
95
- mapping: Optional[bytes],
96
- depends_on: bytes,
97
- logical_range_start: int,
98
- logical_range_end: int,
99
- apply_at_startup: bool,
100
- ) -> DeviceConfig:
101
- """
102
- Parse a device configuration from a database row.
103
- """
104
- return DeviceConfig(
105
- id=id,
106
- path=Path(path),
107
- name=name,
108
- sector_size=sector_size,
109
- badblocks=Badblocks.from_bytes(badblocks),
110
- spare_sectors=spare_sectors,
111
- mapping=Mapping.from_bytes(mapping) if mapping is not None else None,
112
- depends_on=set(iterable_from_bytes(depends_on, length=DEFAULT_INT_LENGTH)),
113
- logical_range=(logical_range_start, logical_range_end),
114
- apply_at_startup=apply_at_startup,
115
- )
116
-
117
- def get_devices(self) -> Iterable[DeviceConfig]:
118
- """
119
- Get the devices configuration as a DataFrame.
120
- """
121
- with sqlite.connect(self._path) as conn:
122
- data = conn.execute(
123
- """SELECT """
124
- + ",".join(DEVICE_INFO_COLUMNS_TYPES.keys())
125
- + """ FROM devices"""
126
- ).fetchall()
127
-
128
- data = [
129
- self.__parse_device_from_row(
130
- **dict(zip(DEVICE_INFO_COLUMNS_TYPES.keys(), row))
131
- )
132
- for row in data
133
- ]
134
- return data
135
-
136
- @overload
137
- def get_device(self, *, id: int, name: Literal[None] = None) -> DeviceConfig: ...
138
- @overload
139
- def get_device(self, *, id: Literal[None] = None, name: str) -> DeviceConfig: ...
140
-
141
- def get_device(
142
- self, *, id: Optional[int] = None, name: Optional[str] = None
143
- ) -> DeviceConfig:
144
- if id is not None and name is not None:
145
- raise ValueError("Only one of id or name can be provided to get a device.")
146
-
147
- with sqlite.connect(self._path) as conn:
148
- cursor = conn.cursor()
149
- QUERY = (
150
- """SELECT """
151
- + ",".join(DEVICE_INFO_COLUMNS_TYPES.keys())
152
- + """ FROM devices"""
153
- )
154
- if id is not None:
155
- cursor.execute(QUERY + " WHERE id = ?", (id,))
156
- elif name is not None:
157
- cursor.execute(QUERY + " WHERE name = ?", (name,))
158
- else:
159
- raise ValueError("Either id or name must be provided to get a device.")
160
- row = cursor.fetchone()
161
- if row is None:
162
- raise DeviceNotFoundError(device_id=id, device_name=name)
163
-
164
- return self.__parse_device_from_row(
165
- **dict(zip(DEVICE_INFO_COLUMNS_TYPES.keys(), row))
166
- )
167
-
168
- def __insert_into_devices_table(self, cursor: sqlite.Cursor, row: dict[str, Any]):
169
- cols = list(row.keys())
170
- cursor.execute(
171
- """INSERT INTO devices
172
- ("""
173
- + ",".join(cols)
174
- + """)
175
- VALUES ("""
176
- + ",".join(("?",) * len(cols))
177
- + """)""",
178
- tuple(row[col] for col in cols),
179
- )
180
-
181
- def add_device(
182
- self,
183
- path: Union[Path, str],
184
- name: str,
185
- logical_range: tuple[int, int],
186
- *,
187
- sector_size: Optional[int] = None,
188
- badblocks: Union[set[int], Badblocks] = set(),
189
- depends_on: Collection[int] = set(),
190
- ) -> None:
191
- _path: Path = Path(path)
192
- assert _path.exists(), f"The provided device {_path} does not exist."
193
-
194
- assert (
195
- re.fullmatch(r"[0-9A-Za-z#+\-\.:=@_]+", name) is not None
196
- ), "Name does not match requirements"
197
-
198
- try:
199
- existing_dev = self.get_device(name=name)
200
- raise ValueError(f"Name {name} is used by device {existing_dev.id}.")
201
- except DeviceNotFoundError:
202
- pass
203
-
204
- badblocks = Badblocks(badblocks)
205
-
206
- with sqlite.connect(self._path) as conn:
207
- self._assert_depends_on(conn, depends_on)
208
-
209
- cursor = conn.cursor()
210
- self.__insert_into_devices_table(
211
- cursor,
212
- {
213
- "path": str(_path),
214
- "name": name,
215
- "sector_size": sector_size,
216
- "badblocks": sqlite.Binary(bytes(badblocks)),
217
- "spare_sectors": None,
218
- "mapping": None,
219
- "depends_on": iterable_to_bytes(
220
- depends_on, length=DEFAULT_INT_LENGTH
221
- ),
222
- "logical_range_start": logical_range[0],
223
- "logical_range_end": logical_range[1],
224
- },
225
- )
226
- conn.commit()
227
-
228
- def remove_device(self, id: int) -> None:
229
- with sqlite.connect(self._path) as conn:
230
- if not self.__check_device_in_db(conn, id=id):
231
- raise DeviceNotFoundError(device_id=id)
232
-
233
- for dev in self.get_devices():
234
- if id in dev.depends_on:
235
- raise KeyError(
236
- f"Device {dev.id} depends on device {id}, can't remove the latter."
237
- )
238
-
239
- cursor = conn.cursor()
240
- cursor.execute("DELETE FROM devices WHERE id = ?", (id,))
241
- conn.commit()
242
-
243
- def update_device(
244
- self,
245
- id: int,
246
- *,
247
- path: Optional[Union[Path, str]] = None,
248
- name: Optional[str] = None,
249
- badblocks: Optional[Iterable[int]] = None,
250
- mapping: Optional[Mapping] = None,
251
- spare_sectors: Optional[int] = None,
252
- depends_on: Optional[Collection[int]] = None,
253
- ) -> None:
254
- with sqlite.connect(self._path) as conn:
255
- if not self.__check_device_in_db(conn, id=id):
256
- raise DeviceNotFoundError(device_id=id)
257
-
258
- cursor = conn.cursor()
259
- if path is not None:
260
- cursor.execute(
261
- "UPDATE devices SET path = ? WHERE id = ?", (str(path), id)
262
- )
263
- if name is not None:
264
- cursor.execute("UPDATE devices SET name = ? WHERE id = ?", (name, id))
265
- if badblocks is not None:
266
- badblocks = Badblocks(badblocks)
267
- cursor.execute(
268
- "UPDATE devices SET badblocks = ? WHERE id = ?",
269
- (sqlite.Binary(bytes(badblocks)), id),
270
- )
271
- if mapping is not None:
272
- cursor.execute(
273
- "UPDATE devices SET mapping = ? WHERE id = ?",
274
- (sqlite.Binary(bytes(mapping)), id),
275
- )
276
- if spare_sectors is not None:
277
- cursor.execute(
278
- "UPDATE devices SET spare_sectors = ? WHERE id = ?",
279
- (spare_sectors, id),
280
- )
281
- if depends_on is not None:
282
- self._assert_depends_on(conn, depends_on)
283
- cursor.execute(
284
- "UPDATE devices SET depends_on = ? WHERE id = ?",
285
- (
286
- iterable_to_bytes(depends_on, length=DEFAULT_INT_LENGTH),
287
- id,
288
- ),
289
- )
290
- conn.commit()
291
-
292
- def __len__(self) -> int:
293
- """
294
- Get the number of devices in the database.
295
- """
296
- with sqlite.connect(self._path) as conn:
297
- cursor = conn.cursor()
298
- cursor.execute("SELECT COUNT(*) FROM devices")
299
- count = cursor.fetchone()[0]
300
- return count
src/devices/exceptions.py DELETED
@@ -1,22 +0,0 @@
1
- from typing import Iterable, Optional, Union
2
-
3
-
4
- class DeviceNotFoundError(KeyError):
5
- def __init__(
6
- self,
7
- *,
8
- device_id: Optional[Union[int, Iterable[int]]] = None,
9
- device_name: Optional[Union[str, Iterable[str]]] = None,
10
- ) -> None:
11
- if device_id is not None and device_name is not None:
12
- raise ValueError("Only one of device_id or device_name can be provided.")
13
- if device_id is not None:
14
- message = f"Device(s) with id(s) {device_id} does not exist."
15
- args = (message,)
16
- elif device_name is not None:
17
- message = f"Device(s) with name(s) '{device_name}' does not exist."
18
- args = (message,)
19
- else:
20
- message = "Device(s) not found."
21
- args = (message,)
22
- super().__init__(*args)
@@ -1,3 +0,0 @@
1
- from pathlib import Path
2
-
3
- DEFAULT_DEVICES_CONFIG_PATH = Path("/etc/remap_badblocks/devices_config.db")