dissect.ntfs 3.13.dev1__tar.gz → 3.14.dev1__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 (43) hide show
  1. {dissect_ntfs-3.13.dev1/dissect.ntfs.egg-info → dissect_ntfs-3.14.dev1}/PKG-INFO +2 -2
  2. {dissect_ntfs-3.13.dev1 → dissect_ntfs-3.14.dev1}/dissect/ntfs/attr.py +33 -33
  3. {dissect_ntfs-3.13.dev1 → dissect_ntfs-3.14.dev1}/dissect/ntfs/c_ntfs.py +3 -0
  4. {dissect_ntfs-3.13.dev1 → dissect_ntfs-3.14.dev1}/dissect/ntfs/index.py +12 -13
  5. {dissect_ntfs-3.13.dev1 → dissect_ntfs-3.14.dev1}/dissect/ntfs/mft.py +26 -23
  6. {dissect_ntfs-3.13.dev1 → dissect_ntfs-3.14.dev1}/dissect/ntfs/ntfs.py +13 -8
  7. {dissect_ntfs-3.13.dev1 → dissect_ntfs-3.14.dev1}/dissect/ntfs/secure.py +10 -7
  8. {dissect_ntfs-3.13.dev1 → dissect_ntfs-3.14.dev1}/dissect/ntfs/stream.py +2 -0
  9. {dissect_ntfs-3.13.dev1 → dissect_ntfs-3.14.dev1}/dissect/ntfs/usnjrnl.py +11 -11
  10. {dissect_ntfs-3.13.dev1 → dissect_ntfs-3.14.dev1}/dissect/ntfs/util.py +14 -14
  11. {dissect_ntfs-3.13.dev1 → dissect_ntfs-3.14.dev1/dissect.ntfs.egg-info}/PKG-INFO +2 -2
  12. {dissect_ntfs-3.13.dev1 → dissect_ntfs-3.14.dev1}/pyproject.toml +48 -5
  13. {dissect_ntfs-3.13.dev1 → dissect_ntfs-3.14.dev1}/tests/conftest.py +11 -6
  14. {dissect_ntfs-3.13.dev1 → dissect_ntfs-3.14.dev1}/tests/test_attr.py +2 -0
  15. {dissect_ntfs-3.13.dev1 → dissect_ntfs-3.14.dev1}/tests/test_exceptions.py +3 -1
  16. {dissect_ntfs-3.13.dev1 → dissect_ntfs-3.14.dev1}/tests/test_index.py +2 -0
  17. {dissect_ntfs-3.13.dev1 → dissect_ntfs-3.14.dev1}/tests/test_mft.py +2 -0
  18. {dissect_ntfs-3.13.dev1 → dissect_ntfs-3.14.dev1}/tests/test_ntfs.py +2 -0
  19. {dissect_ntfs-3.13.dev1 → dissect_ntfs-3.14.dev1}/tests/test_secure.py +3 -1
  20. {dissect_ntfs-3.13.dev1 → dissect_ntfs-3.14.dev1}/tests/test_usnjrnl.py +2 -0
  21. {dissect_ntfs-3.13.dev1 → dissect_ntfs-3.14.dev1}/tests/test_util.py +10 -8
  22. {dissect_ntfs-3.13.dev1 → dissect_ntfs-3.14.dev1}/tox.ini +4 -17
  23. {dissect_ntfs-3.13.dev1 → dissect_ntfs-3.14.dev1}/COPYRIGHT +0 -0
  24. {dissect_ntfs-3.13.dev1 → dissect_ntfs-3.14.dev1}/LICENSE +0 -0
  25. {dissect_ntfs-3.13.dev1 → dissect_ntfs-3.14.dev1}/MANIFEST.in +0 -0
  26. {dissect_ntfs-3.13.dev1 → dissect_ntfs-3.14.dev1}/README.md +0 -0
  27. {dissect_ntfs-3.13.dev1 → dissect_ntfs-3.14.dev1}/dissect/ntfs/__init__.py +2 -2
  28. {dissect_ntfs-3.13.dev1 → dissect_ntfs-3.14.dev1}/dissect/ntfs/exceptions.py +0 -0
  29. {dissect_ntfs-3.13.dev1 → dissect_ntfs-3.14.dev1}/dissect.ntfs.egg-info/SOURCES.txt +0 -0
  30. {dissect_ntfs-3.13.dev1 → dissect_ntfs-3.14.dev1}/dissect.ntfs.egg-info/dependency_links.txt +0 -0
  31. {dissect_ntfs-3.13.dev1 → dissect_ntfs-3.14.dev1}/dissect.ntfs.egg-info/requires.txt +0 -0
  32. {dissect_ntfs-3.13.dev1 → dissect_ntfs-3.14.dev1}/dissect.ntfs.egg-info/top_level.txt +0 -0
  33. {dissect_ntfs-3.13.dev1 → dissect_ntfs-3.14.dev1}/setup.cfg +0 -0
  34. {dissect_ntfs-3.13.dev1 → dissect_ntfs-3.14.dev1}/tests/__init__.py +0 -0
  35. {dissect_ntfs-3.13.dev1 → dissect_ntfs-3.14.dev1}/tests/data/boot_2m.bin.gz +0 -0
  36. {dissect_ntfs-3.13.dev1 → dissect_ntfs-3.14.dev1}/tests/data/mft.bin.gz +0 -0
  37. {dissect_ntfs-3.13.dev1 → dissect_ntfs-3.14.dev1}/tests/data/ntfs.bin.gz +0 -0
  38. {dissect_ntfs-3.13.dev1 → dissect_ntfs-3.14.dev1}/tests/data/ntfs_fragmented_mft.csv.gz +0 -0
  39. {dissect_ntfs-3.13.dev1 → dissect_ntfs-3.14.dev1}/tests/data/sds.bin.gz +0 -0
  40. {dissect_ntfs-3.13.dev1 → dissect_ntfs-3.14.dev1}/tests/data/sds_complex.bin.gz +0 -0
  41. {dissect_ntfs-3.13.dev1 → dissect_ntfs-3.14.dev1}/tests/docs/Makefile +0 -0
  42. {dissect_ntfs-3.13.dev1 → dissect_ntfs-3.14.dev1}/tests/docs/conf.py +0 -0
  43. {dissect_ntfs-3.13.dev1 → dissect_ntfs-3.14.dev1}/tests/docs/index.rst +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: dissect.ntfs
3
- Version: 3.13.dev1
3
+ Version: 3.14.dev1
4
4
  Summary: A Dissect module implementing a parser for the NTFS file system, used by the Windows operating system
5
5
  Author-email: Dissect Team <dissect@fox-it.com>
6
6
  License: Affero General Public License v3
@@ -1,8 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import io
4
- from datetime import datetime
5
- from typing import TYPE_CHECKING, Any, BinaryIO, Iterator, Optional
4
+ from typing import TYPE_CHECKING, Any, BinaryIO
6
5
 
7
6
  from dissect.util.stream import RangeStream, RunlistStream
8
7
  from dissect.util.ts import wintimestamp
@@ -18,6 +17,9 @@ from dissect.ntfs.exceptions import MftNotAvailableError, VolumeNotAvailableErro
18
17
  from dissect.ntfs.util import ensure_volume, get_full_path, ts_to_ns
19
18
 
20
19
  if TYPE_CHECKING:
20
+ from collections.abc import Iterator
21
+ from datetime import datetime
22
+
21
23
  from dissect.ntfs.mft import MftRecord
22
24
 
23
25
 
@@ -31,9 +33,9 @@ class Attribute:
31
33
  header: The AttributeHeader for this Attribute.
32
34
  """
33
35
 
34
- __slots__ = ("record", "header", "attribute")
36
+ __slots__ = ("attribute", "header", "record")
35
37
 
36
- def __init__(self, header: AttributeHeader, record: Optional[MftRecord] = None):
38
+ def __init__(self, header: AttributeHeader, record: MftRecord | None = None):
37
39
  self.header = header
38
40
  self.record = record
39
41
  self.attribute = None
@@ -52,7 +54,7 @@ class Attribute:
52
54
  return f"<${self.header.type.name} name={self.header.name}>"
53
55
 
54
56
  @classmethod
55
- def from_fh(cls, fh: BinaryIO, record: Optional[MftRecord] = None) -> Attribute:
57
+ def from_fh(cls, fh: BinaryIO, record: MftRecord | None = None) -> Attribute:
56
58
  """Parse an attribute from a file-like object.
57
59
 
58
60
  Args:
@@ -62,7 +64,7 @@ class Attribute:
62
64
  return cls(AttributeHeader(fh, 0, record), record)
63
65
 
64
66
  @classmethod
65
- def from_bytes(cls, data: bytes, record: Optional[MftRecord] = None) -> Attribute:
67
+ def from_bytes(cls, data: bytes, record: MftRecord | None = None) -> Attribute:
66
68
  """Parse an attribute from bytes.
67
69
 
68
70
  Args:
@@ -120,9 +122,9 @@ class AttributeHeader:
120
122
  offset: The offset in the file-like object to parse an attribute header from.
121
123
  """
122
124
 
123
- __slots__ = ("record", "fh", "offset", "header")
125
+ __slots__ = ("fh", "header", "offset", "record")
124
126
 
125
- def __init__(self, fh: BinaryIO, offset: int, record: Optional[MftRecord] = None):
127
+ def __init__(self, fh: BinaryIO, offset: int, record: MftRecord | None = None):
126
128
  self.fh = fh
127
129
  self.offset = offset
128
130
  self.record = record
@@ -134,7 +136,7 @@ class AttributeHeader:
134
136
  return f"<${self.type.name} size={self.size}>"
135
137
 
136
138
  @classmethod
137
- def from_bytes(cls, data: bytes, record: Optional[MftRecord] = None) -> AttributeHeader:
139
+ def from_bytes(cls, data: bytes, record: MftRecord | None = None) -> AttributeHeader:
138
140
  """Parse an attribute header from bytes.
139
141
 
140
142
  Args:
@@ -175,22 +177,22 @@ class AttributeHeader:
175
177
  return self.header.Form.Resident.ValueLength if self.resident else self.header.Form.Nonresident.FileSize
176
178
 
177
179
  @property
178
- def allocated_size(self) -> Optional[int]:
180
+ def allocated_size(self) -> int | None:
179
181
  """Return the allocated size if non-resident, else None."""
180
182
  return self.header.Form.Nonresident.AllocatedLength if not self.resident else None
181
183
 
182
184
  @property
183
- def lowest_vcn(self) -> Optional[int]:
185
+ def lowest_vcn(self) -> int | None:
184
186
  """Return the lowest VCN if non-resident, else None."""
185
187
  return self.header.Form.Nonresident.LowestVcn if not self.resident else None
186
188
 
187
189
  @property
188
- def highest_vcn(self) -> Optional[int]:
190
+ def highest_vcn(self) -> int | None:
189
191
  """Return the highest VCN if non-resident, else None."""
190
192
  return self.header.Form.Nonresident.HighestVcn if not self.resident else None
191
193
 
192
194
  @property
193
- def compression_unit(self) -> Optional[int]:
195
+ def compression_unit(self) -> int | None:
194
196
  """Return the compression unit if non-resident, else None."""
195
197
  return self.header.Form.Nonresident.CompressionUnit if not self.resident else None
196
198
 
@@ -242,16 +244,16 @@ class AttributeHeader:
242
244
  self.offset + self.header.Form.Resident.ValueOffset,
243
245
  self.size,
244
246
  )
245
- else:
246
- ntfs = self.record.ntfs if self.record else None
247
- ensure_volume(ntfs)
248
247
 
249
- return RunlistStream(
250
- ntfs.fh,
251
- self.dataruns(),
252
- self.size,
253
- ntfs.cluster_size,
254
- )
248
+ ntfs = self.record.ntfs if self.record else None
249
+ ensure_volume(ntfs)
250
+
251
+ return RunlistStream(
252
+ ntfs.fh,
253
+ self.dataruns(),
254
+ self.size,
255
+ ntfs.cluster_size,
256
+ )
255
257
 
256
258
  def data(self) -> bytes:
257
259
  """Read and return all the data of this attribute.
@@ -272,13 +274,11 @@ class AttributeRecord:
272
274
 
273
275
  __slots__ = ("record",)
274
276
 
275
- def __init__(self, fh: BinaryIO, record: Optional[MftRecord] = None):
277
+ def __init__(self, fh: BinaryIO, record: MftRecord | None = None):
276
278
  self.record = record
277
279
 
278
280
  @classmethod
279
- def from_fh(
280
- cls, fh: BinaryIO, attr_type: ATTRIBUTE_TYPE_CODE, record: Optional[MftRecord] = None
281
- ) -> AttributeRecord:
281
+ def from_fh(cls, fh: BinaryIO, attr_type: ATTRIBUTE_TYPE_CODE, record: MftRecord | None = None) -> AttributeRecord:
282
282
  """Parse an attribute from a file-like object.
283
283
 
284
284
  Selects a more specific :class:`AttributeRecord` class if one is available for the given attribute type.
@@ -296,7 +296,7 @@ class AttributeList(AttributeRecord):
296
296
 
297
297
  __slots__ = ("entries",)
298
298
 
299
- def __init__(self, fh: BinaryIO, record: Optional[MftRecord] = None):
299
+ def __init__(self, fh: BinaryIO, record: MftRecord | None = None):
300
300
  super().__init__(fh, record)
301
301
 
302
302
  offset = 0
@@ -343,7 +343,7 @@ class StandardInformation(AttributeRecord):
343
343
 
344
344
  __slots__ = ("attr",)
345
345
 
346
- def __init__(self, fh: BinaryIO, record: Optional[MftRecord] = None):
346
+ def __init__(self, fh: BinaryIO, record: MftRecord | None = None):
347
347
  super().__init__(fh, record)
348
348
  # Data can be less than the _STANDARD_INFORMATION structure size, so pad remaining fields with null bytes
349
349
  data = fh.read().ljust(len(c_ntfs._STANDARD_INFORMATION), b"\x00")
@@ -413,7 +413,7 @@ class FileName(AttributeRecord):
413
413
 
414
414
  __slots__ = ("attr",)
415
415
 
416
- def __init__(self, fh: BinaryIO, record: Optional[MftRecord] = None):
416
+ def __init__(self, fh: BinaryIO, record: MftRecord | None = None):
417
417
  super().__init__(fh, record)
418
418
  data = fh.read().ljust(len(c_ntfs.STANDARD_INFORMATION_EX), b"\x00")
419
419
  self.attr = c_ntfs._FILE_NAME(data)
@@ -489,9 +489,9 @@ class FileName(AttributeRecord):
489
489
  class ReparsePoint(AttributeRecord):
490
490
  """Specific :class:`AttributeRecord` parser for ``$REPARSE_POINT``."""
491
491
 
492
- __slots__ = ("attr", "tag_header", "buffer")
492
+ __slots__ = ("attr", "buffer", "tag_header")
493
493
 
494
- def __init__(self, fh: BinaryIO, record: Optional[MftRecord] = None):
494
+ def __init__(self, fh: BinaryIO, record: MftRecord | None = None):
495
495
  super().__init__(fh, record)
496
496
  self.attr = c_ntfs._REPARSE_DATA_BUFFER(fh)
497
497
  data = io.BytesIO(fh.read(self.attr.ReparseDataLength))
@@ -512,7 +512,7 @@ class ReparsePoint(AttributeRecord):
512
512
  return self.attr.ReparseTag
513
513
 
514
514
  @property
515
- def substitute_name(self) -> Optional[str]:
515
+ def substitute_name(self) -> str | None:
516
516
  if not self.tag_header:
517
517
  return None
518
518
 
@@ -521,7 +521,7 @@ class ReparsePoint(AttributeRecord):
521
521
  return self.buffer[offset : offset + length].decode("utf-16-le")
522
522
 
523
523
  @property
524
- def print_name(self) -> Optional[str]:
524
+ def print_name(self) -> str | None:
525
525
  if not self.tag_header:
526
526
  return None
527
527
 
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  import struct
2
4
 
3
5
  from dissect.cstruct import cstruct
@@ -652,3 +654,4 @@ def bsf(value: int, size: int = 32) -> int:
652
654
  for i in range(size):
653
655
  if value & (1 << i):
654
656
  return i
657
+ return 0
@@ -3,7 +3,7 @@ from __future__ import annotations
3
3
  import io
4
4
  from enum import Enum, auto
5
5
  from functools import cached_property, lru_cache
6
- from typing import TYPE_CHECKING, Any, BinaryIO, Callable, Iterator, Optional
6
+ from typing import TYPE_CHECKING, Any, BinaryIO, Callable
7
7
 
8
8
  from dissect.ntfs.attr import AttributeRecord
9
9
  from dissect.ntfs.c_ntfs import (
@@ -24,6 +24,8 @@ from dissect.ntfs.exceptions import (
24
24
  from dissect.ntfs.util import apply_fixup
25
25
 
26
26
  if TYPE_CHECKING:
27
+ from collections.abc import Iterator
28
+
27
29
  from dissect.ntfs.mft import MftRecord
28
30
 
29
31
 
@@ -79,7 +81,7 @@ class Index:
79
81
  return IndexBuffer(self, self._index_stream, vcn << self._vcn_size_shift, self.root.bytes_per_index_buffer)
80
82
 
81
83
  def search(
82
- self, value: Any, exact: bool = True, cmp: Optional[Callable[[IndexEntry, Any], Match]] = None
84
+ self, value: Any, exact: bool = True, cmp: Callable[[IndexEntry, Any], Match] | None = None
83
85
  ) -> IndexEntry:
84
86
  """Perform a binary search on this index.
85
87
 
@@ -114,8 +116,7 @@ class Index:
114
116
  entry = _bsearch(entries, search_value, cmp)
115
117
  if not entry.is_node or (not entry.is_end and cmp(entry, search_value) == Match.Equal):
116
118
  break
117
- else:
118
- entries = list(self.index_buffer(entry.node_vcn).entries())
119
+ entries = list(self.index_buffer(entry.node_vcn).entries())
119
120
 
120
121
  if exact and (entry.is_end or cmp(entry, search_value) != Match.Equal):
121
122
  raise KeyError(f"Value not found: {value}")
@@ -216,7 +217,7 @@ class IndexBuffer:
216
217
  buf = fh.read(size)
217
218
 
218
219
  if len(buf) != size:
219
- raise EOFError()
220
+ raise EOFError
220
221
 
221
222
  if buf[:4] != b"INDX":
222
223
  raise BrokenIndexError("Broken INDX header")
@@ -264,7 +265,7 @@ class IndexEntry:
264
265
  """
265
266
  record = self.index.record
266
267
  if not record or not record.ntfs or not record.ntfs.mft:
267
- raise MftNotAvailableError()
268
+ raise MftNotAvailableError
268
269
 
269
270
  return record.ntfs.mft.get(segment_reference(self.header.FileReference))
270
271
 
@@ -284,7 +285,7 @@ class IndexEntry:
284
285
  return self.buf[offset : offset + self.header.DataLength]
285
286
 
286
287
  @cached_property
287
- def attribute(self) -> Optional[AttributeRecord]:
288
+ def attribute(self) -> AttributeRecord | None:
288
289
  """Return the :class:`dissect.ntfs.attr.AttributeRecord` of the attribute contained in this entry."""
289
290
  if self.key_length and self.index.root.attribute_type:
290
291
  return AttributeRecord.from_fh(
@@ -366,10 +367,9 @@ def _cmp_filename(entry: IndexEntry, value: str) -> Match:
366
367
 
367
368
  if value < test_value:
368
369
  return Match.Less
369
- elif value == test_value:
370
+ if value == test_value:
370
371
  return Match.Equal
371
- else:
372
- return Match.Greater
372
+ return Match.Greater
373
373
 
374
374
 
375
375
  def _cmp_ulong(entry: IndexEntry, value: int) -> Match:
@@ -380,7 +380,6 @@ def _cmp_ulong(entry: IndexEntry, value: int) -> Match:
380
380
 
381
381
  if value < test_value:
382
382
  return Match.Less
383
- elif value == test_value:
383
+ if value == test_value:
384
384
  return Match.Equal
385
- else:
386
- return Match.Greater
385
+ return Match.Greater
@@ -4,7 +4,7 @@ import ntpath
4
4
  from functools import cached_property, lru_cache
5
5
  from io import BytesIO
6
6
  from operator import itemgetter
7
- from typing import TYPE_CHECKING, Any, BinaryIO, Iterator, Optional, Union
7
+ from typing import TYPE_CHECKING, BinaryIO
8
8
 
9
9
  from dissect.ntfs.attr import Attribute, AttributeHeader
10
10
  from dissect.ntfs.c_ntfs import (
@@ -30,6 +30,8 @@ from dissect.ntfs.index import Index, IndexEntry
30
30
  from dissect.ntfs.util import AttributeCollection, AttributeMap, apply_fixup
31
31
 
32
32
  if TYPE_CHECKING:
33
+ from collections.abc import Iterator
34
+
33
35
  from dissect.ntfs.ntfs import NTFS
34
36
 
35
37
 
@@ -41,13 +43,13 @@ class Mft:
41
43
  ntfs: An optional NTFS class instance.
42
44
  """
43
45
 
44
- def __init__(self, fh: BinaryIO, ntfs: Optional[NTFS] = None):
46
+ def __init__(self, fh: BinaryIO, ntfs: NTFS | None = None):
45
47
  self.fh = fh
46
48
  self.ntfs = ntfs
47
49
 
48
50
  self.get = lru_cache(4096)(self.get)
49
51
 
50
- def __call__(self, ref, *args, **kwargs) -> MftRecord:
52
+ def __call__(self, ref: int | str | c_ntfs._MFT_SEGMENT_REFERENCE, *args, **kwargs) -> MftRecord:
51
53
  return self.get(ref, *args, **kwargs)
52
54
 
53
55
  @cached_property
@@ -55,7 +57,7 @@ class Mft:
55
57
  """Return the root directory MFT record."""
56
58
  return self.get(FILE_NUMBER_ROOT)
57
59
 
58
- def _get_path(self, path: str, root: Optional[MftRecord] = None) -> MftRecord:
60
+ def _get_path(self, path: str, root: MftRecord | None = None) -> MftRecord:
59
61
  """Resolve a file path to the correct MFT record.
60
62
 
61
63
  Args:
@@ -89,7 +91,7 @@ class Mft:
89
91
 
90
92
  return node
91
93
 
92
- def get(self, ref: Union[int, str, c_ntfs._MFT_SEGMENT_REFERENCE], root: Optional[MftRecord] = None) -> MftRecord:
94
+ def get(self, ref: int | str | c_ntfs._MFT_SEGMENT_REFERENCE, root: MftRecord | None = None) -> MftRecord:
93
95
  """Retrieve an MFT record using a variety of methods.
94
96
 
95
97
  Supported references are:
@@ -113,10 +115,11 @@ class Mft:
113
115
  record = MftRecord.from_fh(self.fh, ref * record_size, ntfs=self.ntfs)
114
116
  record.segment = ref
115
117
  return record
116
- elif isinstance(ref, str):
118
+
119
+ if isinstance(ref, str):
117
120
  return self._get_path(ref, root)
118
- else:
119
- raise TypeError(f"Invalid MFT reference: {ref!r}")
121
+
122
+ raise TypeError(f"Invalid MFT reference: {ref!r}")
120
123
 
121
124
  def segments(self, start: int = 0, end: int = -1) -> Iterator[MftRecord]:
122
125
  """Yield all valid MFT records, regardless if they're allocated or not.
@@ -134,7 +137,7 @@ class Mft:
134
137
  for segment in range(start, end + step, step):
135
138
  try:
136
139
  yield self.get(segment)
137
- except Error:
140
+ except Error: # noqa: PERF203
138
141
  continue
139
142
  except EOFError:
140
143
  break
@@ -147,16 +150,16 @@ class MftRecord:
147
150
  """
148
151
 
149
152
  def __init__(self):
150
- self.ntfs: Optional[NTFS] = None
151
- self.segment: Optional[int] = None
152
- self.offset: Optional[int] = None
153
- self.data: Optional[bytes] = None
154
- self.header: Optional[c_ntfs._FILE_RECORD_SEGMENT_HEADER] = None
153
+ self.ntfs: NTFS | None = None
154
+ self.segment: int | None = None
155
+ self.offset: int | None = None
156
+ self.data: bytes | None = None
157
+ self.header: c_ntfs._FILE_RECORD_SEGMENT_HEADER | None = None
155
158
 
156
159
  def __repr__(self) -> str:
157
160
  return f"<MftRecord {self.segment}#{self.header.SequenceNumber}>"
158
161
 
159
- def __eq__(self, other: Any) -> bool:
162
+ def __eq__(self, other: object) -> bool:
160
163
  if isinstance(other, MftRecord):
161
164
  return self.segment == other.segment and self.header.SequenceNumber == other.header.SequenceNumber
162
165
  return False
@@ -164,7 +167,7 @@ class MftRecord:
164
167
  __hash__ = object.__hash__
165
168
 
166
169
  @classmethod
167
- def from_fh(cls, fh: BinaryIO, offset: int, ntfs: Optional[NTFS] = None) -> MftRecord:
170
+ def from_fh(cls, fh: BinaryIO, offset: int, ntfs: NTFS | None = None) -> MftRecord:
168
171
  """Parse an MFT record from a file-like object.
169
172
 
170
173
  Args:
@@ -182,7 +185,7 @@ class MftRecord:
182
185
  return obj
183
186
 
184
187
  @classmethod
185
- def from_bytes(cls, data: bytes, ntfs: Optional[NTFS] = None) -> MftRecord:
188
+ def from_bytes(cls, data: bytes, ntfs: NTFS | None = None) -> MftRecord:
186
189
  """Parse an MFT record from bytes.
187
190
 
188
191
  Args:
@@ -212,7 +215,7 @@ class MftRecord:
212
215
  MftNotAvailableError: If no MFT is available.
213
216
  """
214
217
  if not self.ntfs or not self.ntfs.mft:
215
- raise MftNotAvailableError()
218
+ raise MftNotAvailableError
216
219
  return self.ntfs.mft.get(path, root=self)
217
220
 
218
221
  @cached_property
@@ -264,7 +267,7 @@ class MftRecord:
264
267
  return any(attr.header.resident for attr in self.attributes[ATTRIBUTE_TYPE_CODE.DATA])
265
268
 
266
269
  @cached_property
267
- def filename(self) -> Optional[str]:
270
+ def filename(self) -> str | None:
268
271
  """Return the first file name, or ``None`` if this record has no file names."""
269
272
  filenames = self.filenames()
270
273
  return filenames[0] if filenames else None
@@ -282,7 +285,7 @@ class MftRecord:
282
285
  result.append((attr.flags, attr.file_name))
283
286
  return [item[1] for item in sorted(result, key=itemgetter(0))]
284
287
 
285
- def full_path(self, ignore_dos: bool = False) -> Optional[str]:
288
+ def full_path(self, ignore_dos: bool = False) -> str | None:
286
289
  """Return the first full path, or ``None`` if this record has no file names.
287
290
 
288
291
  Args:
@@ -353,7 +356,7 @@ class MftRecord:
353
356
  raise NotAReparsePointError(f"{self!r} is not a reparse point")
354
357
 
355
358
  if not self.ntfs or not self.ntfs.mft:
356
- raise MftNotAvailableError()
359
+ raise MftNotAvailableError
357
360
 
358
361
  reparse_point = self.attributes[ATTRIBUTE_TYPE_CODE.REPARSE_POINT]
359
362
 
@@ -442,7 +445,7 @@ class MftRecord:
442
445
  """
443
446
  return Index(self, name)
444
447
 
445
- def iterdir(self, dereference: bool = False, ignore_dos: bool = False) -> Iterator[Union[IndexEntry, MftRecord]]:
448
+ def iterdir(self, dereference: bool = False, ignore_dos: bool = False) -> Iterator[IndexEntry | MftRecord]:
446
449
  """Yield directory entries of this record.
447
450
 
448
451
  Args:
@@ -460,7 +463,7 @@ class MftRecord:
460
463
  continue
461
464
  yield entry.dereference() if dereference else entry
462
465
 
463
- def listdir(self, dereference: bool = False, ignore_dos: bool = False) -> dict[str, Union[IndexEntry, MftRecord]]:
466
+ def listdir(self, dereference: bool = False, ignore_dos: bool = False) -> dict[str, IndexEntry | MftRecord]:
464
467
  """Return a dictionary of the directory entries of this record.
465
468
 
466
469
  Args:
@@ -1,5 +1,7 @@
1
+ from __future__ import annotations
2
+
1
3
  from functools import cached_property
2
- from typing import BinaryIO, Iterator, Optional
4
+ from typing import TYPE_CHECKING, BinaryIO
3
5
 
4
6
  from dissect.ntfs.c_ntfs import (
5
7
  ATTRIBUTE_TYPE_CODE,
@@ -17,6 +19,9 @@ from dissect.ntfs.mft import Mft, MftRecord
17
19
  from dissect.ntfs.secure import Secure
18
20
  from dissect.ntfs.usnjrnl import UsnJrnl
19
21
 
22
+ if TYPE_CHECKING:
23
+ from collections.abc import Iterator
24
+
20
25
 
21
26
  class NTFS:
22
27
  """Implementation for Microsoft NTFS.
@@ -36,11 +41,11 @@ class NTFS:
36
41
 
37
42
  def __init__(
38
43
  self,
39
- fh: Optional[BinaryIO] = None,
40
- boot: Optional[BinaryIO] = None,
41
- mft: Optional[BinaryIO] = None,
42
- usnjrnl: Optional[BinaryIO] = None,
43
- sds: Optional[BinaryIO] = None,
44
+ fh: BinaryIO | None = None,
45
+ boot: BinaryIO | None = None,
46
+ mft: BinaryIO | None = None,
47
+ usnjrnl: BinaryIO | None = None,
48
+ sds: BinaryIO | None = None,
44
49
  ):
45
50
  self.fh = fh
46
51
 
@@ -130,11 +135,11 @@ class NTFS:
130
135
  pass
131
136
 
132
137
  @cached_property
133
- def serial(self) -> Optional[int]:
138
+ def serial(self) -> int | None:
134
139
  return self.boot_sector.SerialNumber if self.boot_sector else None
135
140
 
136
141
  @cached_property
137
- def volume_name(self) -> Optional[str]:
142
+ def volume_name(self) -> str | None:
138
143
  if not self.mft:
139
144
  return None
140
145
 
@@ -2,13 +2,17 @@ from __future__ import annotations
2
2
 
3
3
  import io
4
4
  from functools import lru_cache
5
- from typing import BinaryIO, Iterator
5
+ from typing import TYPE_CHECKING, BinaryIO
6
6
  from uuid import UUID
7
7
 
8
8
  from dissect.util.sid import read_sid
9
9
 
10
10
  from dissect.ntfs.c_ntfs import ACE_OBJECT_FLAGS, ACE_TYPE, c_ntfs
11
- from dissect.ntfs.mft import MftRecord
11
+
12
+ if TYPE_CHECKING:
13
+ from collections.abc import Iterator
14
+
15
+ from dissect.ntfs.mft import MftRecord
12
16
 
13
17
 
14
18
  class Secure:
@@ -21,7 +25,7 @@ class Secure:
21
25
  sds: A file-like object of the ``$SDS`` stream, used when opening from separate system files.
22
26
  """
23
27
 
24
- def __init__(self, record: MftRecord = None, sds: BinaryIO = None):
28
+ def __init__(self, record: MftRecord = None, sds: BinaryIO | None = None):
25
29
  self.record = record
26
30
  self.sds = None
27
31
  self.sii = None
@@ -187,18 +191,17 @@ class ACE:
187
191
  def __repr__(self) -> str:
188
192
  if self.is_standard_ace:
189
193
  return f"<{self.header.AceType.name} mask=0x{self.mask:x} sid={self.sid}>"
190
- elif self.is_compound_ace:
194
+ if self.is_compound_ace:
191
195
  return (
192
196
  f"<{self.header.AceType.name} mask=0x{self.mask:x} type={self.compound_type.name}"
193
197
  f" server_sid={self.server_sid} client_sid={self.sid}>"
194
198
  )
195
- elif self.is_object_ace:
199
+ if self.is_object_ace:
196
200
  return (
197
201
  f"<{self.header.AceType.name} mask=0x{self.mask:x} flags={self.flags} object_type={self.object_type}"
198
202
  f" inherited_object_type={self.inherited_object_type} sid={self.sid}>"
199
203
  )
200
- else:
201
- return f"<ACE type={self.header.AceType} flags={self.header.AceFlags} size={self.header.AceSize}>"
204
+ return f"<ACE type={self.header.AceType} flags={self.header.AceFlags} size={self.header.AceSize}>"
202
205
 
203
206
  @property
204
207
  def type(self) -> ACE_TYPE:
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  import io
2
4
  from typing import BinaryIO
3
5
 
@@ -1,18 +1,20 @@
1
1
  from __future__ import annotations
2
2
 
3
- from datetime import datetime
4
3
  from functools import cached_property
5
- from typing import TYPE_CHECKING, Any, BinaryIO, Iterator, Optional
4
+ from typing import TYPE_CHECKING, Any, BinaryIO
6
5
 
7
6
  from dissect.util.stream import RunlistStream
8
7
  from dissect.util.ts import wintimestamp
9
8
 
10
9
  from dissect.ntfs.c_ntfs import USN_PAGE_SIZE, c_ntfs, segment_reference
11
10
  from dissect.ntfs.exceptions import Error
12
- from dissect.ntfs.mft import MftRecord
13
11
  from dissect.ntfs.util import ts_to_ns
14
12
 
15
13
  if TYPE_CHECKING:
14
+ from collections.abc import Iterator
15
+ from datetime import datetime
16
+
17
+ from dissect.ntfs.mft import MftRecord
16
18
  from dissect.ntfs.ntfs import NTFS
17
19
 
18
20
 
@@ -24,7 +26,7 @@ class UsnJrnl:
24
26
  ntfs: An optional :class:`~dissect.ntfs.ntfs.NTFS` class instance, used for resolving file paths.
25
27
  """
26
28
 
27
- def __init__(self, fh: BinaryIO, ntfs: Optional[NTFS] = None):
29
+ def __init__(self, fh: BinaryIO, ntfs: NTFS | None = None):
28
30
  self.fh = fh
29
31
  self.ntfs = ntfs
30
32
 
@@ -105,13 +107,13 @@ class UsnRecord:
105
107
  return getattr(self.record, attr)
106
108
 
107
109
  @cached_property
108
- def file(self) -> Optional[MftRecord]:
110
+ def file(self) -> MftRecord | None:
109
111
  if self.usnjrnl.ntfs and self.usnjrnl.ntfs.mft:
110
112
  return self.usnjrnl.ntfs.mft(self.record.FileReferenceNumber)
111
113
  return None
112
114
 
113
115
  @cached_property
114
- def parent(self) -> Optional[MftRecord]:
116
+ def parent(self) -> MftRecord | None:
115
117
  if self.usnjrnl.ntfs and self.usnjrnl.ntfs.mft:
116
118
  return self.usnjrnl.ntfs.mft(self.record.ParentFileReferenceNumber)
117
119
  return None
@@ -133,12 +135,10 @@ class UsnRecord:
133
135
 
134
136
  ref = segment_reference(self.record.ParentFileReferenceNumber)
135
137
  if parent is None:
136
- parent_path = (
137
- f"<unavailable_reference_0x{ref:x}" f"#{self.record.ParentFileReferenceNumber.SequenceNumber}>"
138
- )
138
+ parent_path = f"<unavailable_reference_0x{ref:x}#{self.record.ParentFileReferenceNumber.SequenceNumber}>"
139
139
  elif parent.header.SequenceNumber == self.record.ParentFileReferenceNumber.SequenceNumber:
140
140
  parent_path = parent.full_path()
141
141
  else:
142
- parent_path = f"<broken_reference_0x{ref:x}" f"#{self.record.ParentFileReferenceNumber.SequenceNumber}>"
142
+ parent_path = f"<broken_reference_0x{ref:x}#{self.record.ParentFileReferenceNumber.SequenceNumber}>"
143
143
 
144
- return "\\".join([parent_path, self.filename])
144
+ return f"{parent_path}\\{self.filename}"
@@ -2,7 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  import struct
4
4
  from collections import UserDict
5
- from typing import TYPE_CHECKING, Any, BinaryIO, Optional, Union
5
+ from typing import TYPE_CHECKING, Any, BinaryIO
6
6
 
7
7
  from dissect.cstruct import Enum
8
8
  from dissect.util.stream import RunlistStream
@@ -44,12 +44,12 @@ class AttributeMap(UserDict):
44
44
 
45
45
  return super().__getattribute__(attr)
46
46
 
47
- def __getitem__(self, item: Union[ATTRIBUTE_TYPE_CODE, int]) -> AttributeCollection:
47
+ def __getitem__(self, item: ATTRIBUTE_TYPE_CODE | int) -> AttributeCollection:
48
48
  if isinstance(item, Enum):
49
49
  item = item.value
50
50
  return self.data.get(item, AttributeCollection())
51
51
 
52
- def __contains__(self, key: Union[ATTRIBUTE_TYPE_CODE, int]) -> bool:
52
+ def __contains__(self, key: ATTRIBUTE_TYPE_CODE | int) -> bool:
53
53
  if isinstance(key, Enum):
54
54
  key = key.value
55
55
  return super().__contains__(key)
@@ -132,13 +132,13 @@ class AttributeCollection(list):
132
132
  ntfs.cluster_size,
133
133
  attrs[0].header.compression_unit,
134
134
  )
135
- else:
136
- return RunlistStream(
137
- ntfs.fh,
138
- runs,
139
- size,
140
- ntfs.cluster_size,
141
- )
135
+
136
+ return RunlistStream(
137
+ ntfs.fh,
138
+ runs,
139
+ size,
140
+ ntfs.cluster_size,
141
+ )
142
142
 
143
143
  def size(self, allocated: bool = False) -> int:
144
144
  """Retrieve the data stream size for this list of attributes.
@@ -169,7 +169,7 @@ class AttributeCollection(list):
169
169
  def _get_stream_attrs(self) -> list[Attribute]:
170
170
  return sorted((attr for attr in self if not attr.header.resident), key=lambda attr: attr.header.lowest_vcn)
171
171
 
172
- def _get_dataruns(self, attrs: Optional[list[Attribute]] = None) -> list[tuple[int, int]]:
172
+ def _get_dataruns(self, attrs: list[Attribute] | None = None) -> list[tuple[int, int]]:
173
173
  attrs = attrs or self._get_stream_attrs()
174
174
 
175
175
  runs = []
@@ -232,10 +232,10 @@ def ensure_volume(ntfs: NTFS) -> None:
232
232
  VolumeNotAvailableError: If a volume is not available.
233
233
  """
234
234
  if not ntfs or not ntfs.fh:
235
- raise VolumeNotAvailableError()
235
+ raise VolumeNotAvailableError
236
236
 
237
237
 
238
- def get_full_path(mft: Mft, name: str, parent: c_ntfs._MFT_SEGMENT_REFERENCE, seen: set[str] = None) -> str:
238
+ def get_full_path(mft: Mft, name: str, parent: c_ntfs._MFT_SEGMENT_REFERENCE, seen: set[str] | None = None) -> str:
239
239
  """Walk up parent file references to construct a full path.
240
240
 
241
241
  Args:
@@ -264,7 +264,7 @@ def get_full_path(mft: Mft, name: str, parent: c_ntfs._MFT_SEGMENT_REFERENCE, se
264
264
  try:
265
265
  record = mft.get(parent_ref)
266
266
  if not record.filename:
267
- raise FilenameNotAvailableError("No filename")
267
+ raise FilenameNotAvailableError("No filename") # noqa: TRY301
268
268
 
269
269
  if record.header.SequenceNumber != parent.SequenceNumber:
270
270
  path.append(f"<broken_reference_0x{parent_ref:x}#{parent.SequenceNumber}>")
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: dissect.ntfs
3
- Version: 3.13.dev1
3
+ Version: 3.14.dev1
4
4
  Summary: A Dissect module implementing a parser for the NTFS file system, used by the Windows operating system
5
5
  Author-email: Dissect Team <dissect@fox-it.com>
6
6
  License: Affero General Public License v3
@@ -41,13 +41,56 @@ dev = [
41
41
  "dissect.util>=3.0.dev,<4.0.dev",
42
42
  ]
43
43
 
44
- [tool.black]
44
+ [tool.ruff]
45
45
  line-length = 120
46
+ required-version = ">=0.9.0"
46
47
 
47
- [tool.isort]
48
- profile = "black"
49
- known_first_party = ["dissect.ntfs"]
50
- known_third_party = ["dissect"]
48
+ [tool.ruff.format]
49
+ docstring-code-format = true
50
+
51
+ [tool.ruff.lint]
52
+ select = [
53
+ "F",
54
+ "E",
55
+ "W",
56
+ "I",
57
+ "UP",
58
+ "YTT",
59
+ "ANN",
60
+ "B",
61
+ "C4",
62
+ "DTZ",
63
+ "T10",
64
+ "FA",
65
+ "ISC",
66
+ "G",
67
+ "INP",
68
+ "PIE",
69
+ "PYI",
70
+ "PT",
71
+ "Q",
72
+ "RSE",
73
+ "RET",
74
+ "SLOT",
75
+ "SIM",
76
+ "TID",
77
+ "TCH",
78
+ "PTH",
79
+ "PLC",
80
+ "TRY",
81
+ "FLY",
82
+ "PERF",
83
+ "FURB",
84
+ "RUF",
85
+ ]
86
+ ignore = ["E203", "B904", "UP024", "ANN002", "ANN003", "ANN204", "ANN401", "SIM105", "TRY003"]
87
+
88
+ [tool.ruff.lint.per-file-ignores]
89
+ "tests/docs/**" = ["INP001"]
90
+
91
+ [tool.ruff.lint.isort]
92
+ known-first-party = ["dissect.ntfs"]
93
+ known-third-party = ["dissect"]
51
94
 
52
95
  [tool.setuptools]
53
96
  license-files = ["LICENSE", "COPYRIGHT"]
@@ -1,15 +1,20 @@
1
+ from __future__ import annotations
2
+
1
3
  import csv
2
4
  import gzip
3
5
  import io
4
- import os
5
- from typing import BinaryIO, Iterator
6
+ from pathlib import Path
7
+ from typing import TYPE_CHECKING, BinaryIO
6
8
 
7
9
  import pytest
8
10
  from dissect.util.stream import MappingStream
9
11
 
12
+ if TYPE_CHECKING:
13
+ from collections.abc import Iterator
14
+
10
15
 
11
- def absolute_path(filename: str) -> str:
12
- return os.path.join(os.path.dirname(__file__), filename)
16
+ def absolute_path(filename: str) -> Path:
17
+ return Path(__file__).parent / filename
13
18
 
14
19
 
15
20
  def open_file_gz(name: str, mode: str = "rb") -> Iterator[BinaryIO]:
@@ -43,7 +48,7 @@ def boot_2m_bin() -> Iterator[BinaryIO]:
43
48
 
44
49
 
45
50
  @pytest.fixture
46
- def ntfs_fragmented_mft_fh() -> Iterator[BinaryIO]:
51
+ def ntfs_fragmented_mft_fh() -> BinaryIO:
47
52
  # Test data from https://github.com/msuhanov/ntfs-samples
48
53
  # This is from the file ntfs_extremely_fragmented_mft.raw which has, as the name implies, a heavily fragmented MFT
49
54
  # The entire file is way too large, so only take just enough data that we actually need to make dissect.ntfs happy
@@ -55,4 +60,4 @@ def ntfs_fragmented_mft_fh() -> Iterator[BinaryIO]:
55
60
  buf = bytes.fromhex(data)
56
61
  stream.add(int(offset), len(buf), io.BytesIO(buf), 0)
57
62
 
58
- yield stream
63
+ return stream
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  import datetime
2
4
 
3
5
  import pytest
@@ -1,10 +1,12 @@
1
+ from __future__ import annotations
2
+
1
3
  import pytest
2
4
 
3
5
  from dissect.ntfs import exceptions
4
6
 
5
7
 
6
8
  @pytest.mark.parametrize(
7
- "exc, std",
9
+ ("exc", "std"),
8
10
  [
9
11
  (exceptions.FileNotFoundError, FileNotFoundError),
10
12
  (exceptions.IsADirectoryError, IsADirectoryError),
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  import io
2
4
  import struct
3
5
  from typing import BinaryIO
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  import io
2
4
  from typing import BinaryIO
3
5
 
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  from io import BytesIO
2
4
  from typing import BinaryIO
3
5
 
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  from typing import BinaryIO
2
4
 
3
5
  import pytest
@@ -58,5 +60,5 @@ def test_secure_complex_acl(sds_complex_bin: BinaryIO) -> None:
58
60
 
59
61
 
60
62
  def test_secure_fail() -> None:
61
- with pytest.raises(ValueError):
63
+ with pytest.raises(ValueError, match="Either record or SDS stream is required"):
62
64
  Secure()
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  from io import BytesIO
2
4
 
3
5
  from dissect.ntfs.usnjrnl import UsnRecord
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  from dissect.ntfs.attr import Attribute
2
4
  from dissect.ntfs.c_ntfs import ATTRIBUTE_TYPE_CODE
3
5
  from dissect.ntfs.util import AttributeMap, apply_fixup
@@ -7,7 +9,7 @@ def test_fixup() -> None:
7
9
  buf = bytearray(
8
10
  b"FILE\x30\x00"
9
11
  + (b"\x00" * 42)
10
- + b"\x02\x00\xFF\x00\xFE\x00"
12
+ + b"\x02\x00\xff\x00\xfe\x00"
11
13
  + (b"\x00" * 456)
12
14
  + b"\x02\x00"
13
15
  + (b"\x00" * 510)
@@ -15,13 +17,13 @@ def test_fixup() -> None:
15
17
  )
16
18
  fixed = apply_fixup(buf)
17
19
 
18
- assert fixed[510:512] == b"\xFF\x00"
19
- assert fixed[1022:1024] == b"\xFE\x00"
20
+ assert fixed[510:512] == b"\xff\x00"
21
+ assert fixed[1022:1024] == b"\xfe\x00"
20
22
 
21
23
  buf = bytearray(
22
24
  b"FILE\x30\x00"
23
25
  + (b"\x00" * 42)
24
- + b"\x02\x00\xFF\x00\xFE\x00\xFD\x00\xFC\x00"
26
+ + b"\x02\x00\xff\x00\xfe\x00\xfd\x00\xfc\x00"
25
27
  + (b"\x00" * 452)
26
28
  + b"\x02\x00"
27
29
  + (b"\x00" * 510)
@@ -33,10 +35,10 @@ def test_fixup() -> None:
33
35
  )
34
36
  fixed = apply_fixup(buf)
35
37
 
36
- assert fixed[510:512] == b"\xFF\x00"
37
- assert fixed[1022:1024] == b"\xFE\x00"
38
- assert fixed[1534:1536] == b"\xFD\x00"
39
- assert fixed[2046:2048] == b"\xFC\x00"
38
+ assert fixed[510:512] == b"\xff\x00"
39
+ assert fixed[1022:1024] == b"\xfe\x00"
40
+ assert fixed[1534:1536] == b"\xfd\x00"
41
+ assert fixed[2046:2048] == b"\xfc\x00"
40
42
 
41
43
 
42
44
  def test_attribute_map() -> None:
@@ -32,32 +32,19 @@ commands =
32
32
  [testenv:fix]
33
33
  package = skip
34
34
  deps =
35
- black==23.1.0
36
- isort==5.11.4
35
+ ruff==0.9.2
37
36
  commands =
38
- black dissect tests
39
- isort dissect tests
37
+ ruff format dissect tests
40
38
 
41
39
  [testenv:lint]
42
40
  package = skip
43
41
  deps =
44
- black==23.1.0
45
- flake8
46
- flake8-black
47
- flake8-isort
48
- isort==5.11.4
42
+ ruff==0.9.2
49
43
  vermin
50
44
  commands =
51
- flake8 dissect tests
45
+ ruff check dissect tests
52
46
  vermin -t=3.9- --no-tips --lint dissect tests
53
47
 
54
- [flake8]
55
- max-line-length = 120
56
- extend-ignore =
57
- # See https://github.com/PyCQA/pycodestyle/issues/373
58
- E203,
59
- statistics = True
60
-
61
48
  [testenv:docs-build]
62
49
  allowlist_externals = make
63
50
  deps =
@@ -10,6 +10,8 @@ __all__ = [
10
10
  "ACE",
11
11
  "ACL",
12
12
  "ATTRIBUTE_TYPE_CODE",
13
+ "NTFS",
14
+ "NTFS_SIGNATURE",
13
15
  "Attribute",
14
16
  "AttributeHeader",
15
17
  "AttributeRecord",
@@ -17,8 +19,6 @@ __all__ = [
17
19
  "IndexEntry",
18
20
  "Mft",
19
21
  "MftRecord",
20
- "NTFS",
21
- "NTFS_SIGNATURE",
22
22
  "Secure",
23
23
  "SecurityDescriptor",
24
24
  "UsnJrnl",