check-empty 2.0.0__tar.gz → 3.0.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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: check-empty
3
- Version: 2.0.0
3
+ Version: 3.0.1
4
4
  Summary: Command-line utility, as well as a hook usable by the Python pre-commit framework, and a GitHub Action, to ensure that selected files are empty.
5
5
  Keywords: check,command-line,empty,files,github-action,helper,hook,lightweight,pre-commit,prek,tool,utility
6
6
  Author: Jonathan Dung
@@ -27,7 +27,7 @@ Classifier: Topic :: Utilities
27
27
  Classifier: Typing :: Typed
28
28
  Requires-Dist: py7zr==1.1.3 ; extra == '7z'
29
29
  Requires-Dist: acefile==0.6.14 ; extra == 'ace'
30
- Requires-Dist: check-empty[7z,ace,ar,lzh,rar]==2.0.0 ; extra == 'all'
30
+ Requires-Dist: check-empty[7z,ace,ar,lzh,rar]==3.0.1 ; extra == 'all'
31
31
  Requires-Dist: arpy==2.4.0 ; extra == 'ar'
32
32
  Requires-Dist: lhafile==0.3.1 ; extra == 'lzh'
33
33
  Requires-Dist: rarfile==4.5 ; extra == 'rar'
@@ -42,7 +42,7 @@ Provides-Extra: lzh
42
42
  Provides-Extra: rar
43
43
  Description-Content-Type: text/markdown
44
44
 
45
- # [check-empty](https://pypi.org/p/check-empty)
45
+ # [check-empty](https://pypi.org/project/check-empty/3.0.1)
46
46
 
47
47
  [![#StandWithPalestine](https://raw.githubusercontent.com/TheBSD/StandWithPalestine/main/badges/StandWithPalestine.svg)](https://github.com/TheBSD/StandWithPalestine/blob/main/docs/README.md)
48
48
 
@@ -57,7 +57,8 @@ specified.
57
57
 
58
58
  ## Prerequisites
59
59
 
60
- Supports every Python 3.10 runtime. There are no other requirements.
60
+ Supports every Python 3.10 runtime. There are no other requirements unless you want to
61
+ check if files in certain types of (compressed) archives are empty.
61
62
 
62
63
  ## Quickstart
63
64
 
@@ -125,7 +126,7 @@ As a pre-commit hook:
125
126
  # .pre-commit-config.yaml
126
127
  repos:
127
128
  - repo: https://github.com/jonathandung/check-empty
128
- rev: v2.0.0 # repository version
129
+ rev: v3.0.1 # repository version
129
130
  hooks:
130
131
  - id: check-empty # the hook
131
132
  args: # example list of arguments
@@ -141,7 +142,7 @@ equivalent in `prek.toml` format:
141
142
  ```toml
142
143
  [[repos]]
143
144
  repo = "https://github.com/jonathandung/check-empty"
144
- rev = "v2.0.0"
145
+ rev = "v3.0.1"
145
146
 
146
147
  [[repos.hooks]]
147
148
  id = "check-empty"
@@ -165,7 +166,7 @@ or (TOML 1.1+):
165
166
  # using multiline inline tables
166
167
  [[repos]]
167
168
  repo = "https://github.com/jonathandung/check-empty"
168
- rev = "v2.0.0"
169
+ rev = "v3.0.1"
169
170
  hooks = [{
170
171
  id = "check-empty",
171
172
  args = ["-Q"],
@@ -185,7 +186,7 @@ As a GitHub Actions workflow step:
185
186
 
186
187
  ```yaml
187
188
  steps:
188
- - uses: jonathandung/check-empty@v2.0.0 # the latest version on the GitHub Actions
189
+ - uses: jonathandung/check-empty@v3.0.1 # the latest version on the GitHub Actions
189
190
  # marketplace; this step will fail and subsequent jobs will not run if any file is
190
191
  # not empty
191
192
  with:
@@ -1,4 +1,4 @@
1
- # [check-empty](https://pypi.org/p/check-empty)
1
+ # [check-empty](https://pypi.org/project/check-empty/3.0.1)
2
2
 
3
3
  [![#StandWithPalestine](https://raw.githubusercontent.com/TheBSD/StandWithPalestine/main/badges/StandWithPalestine.svg)](https://github.com/TheBSD/StandWithPalestine/blob/main/docs/README.md)
4
4
 
@@ -13,7 +13,8 @@ specified.
13
13
 
14
14
  ## Prerequisites
15
15
 
16
- Supports every Python 3.10 runtime. There are no other requirements.
16
+ Supports every Python 3.10 runtime. There are no other requirements unless you want to
17
+ check if files in certain types of (compressed) archives are empty.
17
18
 
18
19
  ## Quickstart
19
20
 
@@ -81,7 +82,7 @@ As a pre-commit hook:
81
82
  # .pre-commit-config.yaml
82
83
  repos:
83
84
  - repo: https://github.com/jonathandung/check-empty
84
- rev: v2.0.0 # repository version
85
+ rev: v3.0.1 # repository version
85
86
  hooks:
86
87
  - id: check-empty # the hook
87
88
  args: # example list of arguments
@@ -97,7 +98,7 @@ equivalent in `prek.toml` format:
97
98
  ```toml
98
99
  [[repos]]
99
100
  repo = "https://github.com/jonathandung/check-empty"
100
- rev = "v2.0.0"
101
+ rev = "v3.0.1"
101
102
 
102
103
  [[repos.hooks]]
103
104
  id = "check-empty"
@@ -121,7 +122,7 @@ or (TOML 1.1+):
121
122
  # using multiline inline tables
122
123
  [[repos]]
123
124
  repo = "https://github.com/jonathandung/check-empty"
124
- rev = "v2.0.0"
125
+ rev = "v3.0.1"
125
126
  hooks = [{
126
127
  id = "check-empty",
127
128
  args = ["-Q"],
@@ -141,7 +142,7 @@ As a GitHub Actions workflow step:
141
142
 
142
143
  ```yaml
143
144
  steps:
144
- - uses: jonathandung/check-empty@v2.0.0 # the latest version on the GitHub Actions
145
+ - uses: jonathandung/check-empty@v3.0.1 # the latest version on the GitHub Actions
145
146
  # marketplace; this step will fail and subsequent jobs will not run if any file is
146
147
  # not empty
147
148
  with:
@@ -7,26 +7,22 @@ Pre-commit hook, command-line tool and GitHub Action all-in-one.
7
7
 
8
8
  from __future__ import annotations
9
9
 
10
- import os
11
- from contextlib import suppress
12
-
13
- from . import constants, reporter
10
+ from check_empty import constants, reporter
14
11
 
12
+ __all__ = ('Handler', 'check', 'default_reporter')
13
+ __version__ = '3.0.1'
14
+ """The version of the package."""
15
+ default_reporter: reporter.Reporter = reporter.Reporter()
16
+ """The default reporter used by :func:`check`."""
15
17
  TYPE_CHECKING = False
16
18
  if TYPE_CHECKING:
17
19
  from collections.abc import Callable, Iterable
18
- from types import ModuleType, TracebackType
20
+ from types import TracebackType
19
21
  from typing import Any, SupportsIndex
20
22
 
21
23
  from _typeshed import FileDescriptorOrPath
22
24
  from typing_extensions import Self
23
25
 
24
- __all__ = ('Handler', 'check', 'default_reporter')
25
- __version__ = '2.0.0'
26
- """The version of the package."""
27
- default_reporter: reporter.Reporter = reporter.Reporter()
28
- """The default reporter used by :func:`check`."""
29
-
30
26
 
31
27
  class Handler:
32
28
  """Used to handle errors when opening a file."""
@@ -36,22 +32,18 @@ class Handler:
36
32
  """The file descriptor or string path to the file being handled."""
37
33
  c: bool
38
34
  """Whether an I/O error was handled."""
39
- f: Callable[[str], None]
40
- """The function to call when handling an I/O error.
41
-
42
- Should take a string representing the error message as the only argument and
43
- preferably return ``None``.
44
- """
45
- j: Callable[[str], None]
35
+ f: Callable[[OSError], object]
36
+ """The function to call on the I/O error being handled."""
37
+ j: Callable[[str], object]
46
38
  """The function to call when handling the case where the file is missing.
47
39
 
48
40
  Should take the path to the file or a string encapsulating the validity of the file
49
- descriptor as the only argument and preferably return ``None``.
41
+ descriptor as the only argument.
50
42
  """
51
43
  v: str | None
52
44
  """The cached value of :attr:`e`."""
53
45
 
54
- def __init__(self, *a: Any) -> None: # ruff: ignore[any-type]
46
+ def __init__(self, *a: Any): # ruff: ignore[any-type]
55
47
  """Initialize the handler."""
56
48
  self.j, self.f, self.a = a
57
49
  self.v = None
@@ -71,7 +63,7 @@ class Handler:
71
63
  ...
72
64
 
73
65
  @property
74
- def e(self, s: str = 'invalid fd (negative): %d', t: str = 'fd: %d') -> str: # ruff: ignore[property-with-parameters]
66
+ def e(self, s: str = 'invalid fd (negative): %d', t: str = 'fd: %d') -> str:
75
67
  """The file path itself, or the file descriptor coerced to a string."""
76
68
  r = self.v
77
69
  if r is None:
@@ -92,7 +84,7 @@ class Handler:
92
84
  """
93
85
  if t is None or not isinstance(v, OSError):
94
86
  return False
95
- self.j(self.e) if v.errno == constants.ENOENT else self.f(str(v))
87
+ self.j(self.e) if v.errno == constants.ENOENT else self.f(v)
96
88
  self.c = True
97
89
  return True
98
90
 
@@ -104,7 +96,7 @@ def check(
104
96
  may_not_exist: bool = False,
105
97
  verbosity: SupportsIndex = 2,
106
98
  recurse_into: SupportsIndex = constants.RecurseInto.NONE,
107
- reporter: reporter.abc.ReporterABC = default_reporter,
99
+ reporter: reporter.abcdef.ReporterABC = default_reporter,
108
100
  ) -> int:
109
101
  # ruff: ignore[docstring-missing-exception]
110
102
  r"""Check the emptiness of files, recursing into directories if passed.
@@ -130,13 +122,15 @@ def check(
130
122
  The integer exit code calculated by the reporter.
131
123
 
132
124
  """
125
+ from contextlib import suppress
126
+ from os import fsdecode, scandir, stat
127
+
133
128
  files = list(files)
134
129
  if not files:
135
130
  return 0
136
131
  k, j, t, n, o = [0] * 5, [], 0, len(files), files.pop
137
132
  u, e, _ = j.extend, j.pop, lambda v: lambda _, k=k: k.__setitem__(v, k[v] + 1)
138
133
  verbosity, recurse_into = (type(x).__index__(x) for x in (verbosity, recurse_into))
139
- # ruff: disable[magic-value-comparison]
140
134
  if verbosity > 4:
141
135
  b = ['']
142
136
  f = b.append
@@ -149,7 +143,7 @@ def check(
149
143
  v = ra = None
150
144
  x = aa = lambda _: None
151
145
  if verbosity > 2:
152
- z, w = [''], ['']
146
+ z, w = [''], []
153
147
  i = z.append, w.append
154
148
  else:
155
149
  z = w = None
@@ -159,11 +153,10 @@ def check(
159
153
  g = r.append
160
154
  else:
161
155
  r, g = None, _(3)
162
- # ruff: enable[magic-value-comparison]
163
156
  zp, tr, rr, z7, ar, lh, ac = map(recurse_into.__and__, constants.ARCHIVE_FORMATS)
164
157
  an = bool(recurse_into)
165
158
  if an:
166
- from . import archives as av
159
+ from check_empty import archives as av
167
160
  md = 'a' if clear else 'r'
168
161
  if TYPE_CHECKING:
169
162
  import tarfile as tr
@@ -208,20 +201,19 @@ def check(
208
201
  else:
209
202
  f(a)
210
203
 
211
- def v(m: ModuleType, e: str) -> bool:
204
+ def v(f: Callable[[str], Any], e: type[BaseException]) -> bool:
212
205
  nonlocal d
213
- with suppress(getattr(m, e)):
214
- d = m.open(a)
206
+ with suppress(e):
207
+ d = f(a)
215
208
  return True
216
209
  return False
217
210
 
218
- if tr and v(tr, 'TarError'):
211
+ if tr and v(tr.open, tr.TarError):
219
212
  aa(a)
220
213
  for r in d.getmembers():
221
214
  a, s = q = r.name, r.size
222
215
  e()
223
- if clear:
224
- av.purge_tar(d)
216
+ av.purge_tar(d) if clear else d.close()
225
217
  elif zp and zp.is_zipfile(a):
226
218
  with zp.ZipFile(a, md) as d:
227
219
  aa(a)
@@ -248,14 +240,14 @@ def check(
248
240
  for r in d.infolist():
249
241
  a, s = q = r.filename, r.file_size
250
242
  w()
251
- elif ar and v(ar, 'ArchiveFormatError'):
243
+ elif ar and v(ar.Archive, ar.ArchiveFormatError):
252
244
  aa(a)
253
245
  for r in d.infolist():
254
246
  a, s = q = r.name, r.size
255
247
  e()
256
248
  if clear:
257
249
  av.purge_ar(d, ar.HEADER_GNU)
258
- elif ac and v(ac, 'AceError'):
250
+ elif ac and v(ac.open, ac.AceError):
259
251
  aa(a)
260
252
  for r in filter(ac.AceMember.is_reg, d):
261
253
  a, s = q = r.filename, r.size
@@ -278,24 +270,25 @@ def check(
278
270
  try:
279
271
  return an and ha(a)
280
272
  except Exception as e: # ruff: ignore[blind-except]
281
- i[1](str(e))
273
+ i[1](e)
282
274
  return True
283
275
 
284
276
  while files:
285
277
  a, q = o(), None
286
278
  m = isinstance(a, int)
287
279
  if not m:
288
- a = os.fsdecode(a)
280
+ a = fsdecode(a)
289
281
  with Handler(*i, a) as h:
290
- q = os.stat(a) # ruff: ignore[os-stat]
282
+ q = stat(a) # ruff: ignore[os-stat]
291
283
  if q is None:
292
284
  continue
293
285
  c = h.e
294
286
  if q.st_mode & constants.TYPE_MASK == constants.S_IFDIR:
295
287
  if m:
296
288
  raise constants.DIRFD_UNSUPPORTED
297
- x(c)
298
- u(os.scandir(c))
289
+ with h:
290
+ u(scandir(c))
291
+ x(c)
299
292
  continue
300
293
  if isinstance(a, str) and hb(a):
301
294
  continue
@@ -312,8 +305,9 @@ def check(
312
305
  m = e()
313
306
  a = m.path
314
307
  if m.is_dir():
315
- x(a)
316
- u(os.scandir(a))
308
+ with Handler(*i, a):
309
+ u(scandir(a))
310
+ x(a)
317
311
  continue
318
312
  if hb(a):
319
313
  continue
@@ -326,8 +320,7 @@ def check(
326
320
  t += s
327
321
  if clear:
328
322
  Handler(*i, a).o()
329
- del j
330
- x, d = k[1] if z is None else len(z) - 1, k[2] if w is None else len(w) - 1
323
+ x, d = k[1] if z is None else len(z) - 1, k[2] if w is None else len(w)
331
324
  y, p = k[3] if r is None else len(r), k[0] if b is None else len(b) - 1
332
325
  reporter.report(z, w, v, b, r, x, y, d, p, n, t, verbosity, clear)
333
326
  reporter.reset_out()
@@ -4,9 +4,6 @@ from __future__ import annotations
4
4
 
5
5
  import check_empty as c
6
6
 
7
- TYPE_CHECKING = False
8
- if TYPE_CHECKING:
9
- from collections.abc import Iterable
10
7
  __all__ = ('main',)
11
8
  _p = __import__('argparse').ArgumentParser(
12
9
  'check-empty',
@@ -43,6 +40,9 @@ f('-a', '--ar', action='store_true', help='recurse into .a, .ar and .lib archive
43
40
  f('-7', '--7z', action='store_true', dest='z7', help='recurse into .7z archives')
44
41
  f('-l', '--lzh', action='store_true', help='recurse into .lzh and .lha archives')
45
42
  f('-A', '--ace', action='store_true', help='recurse into .ace archives')
43
+ TYPE_CHECKING = False
44
+ if TYPE_CHECKING:
45
+ from collections.abc import Iterable
46
46
 
47
47
 
48
48
  def _r():
@@ -68,15 +68,16 @@ def main(argv: Iterable[str] | None = None) -> int:
68
68
  # ruff: ignore[open-file-with-context-handler]
69
69
  c.default_reporter.to(open(o, 'w', encoding='utf-8'))
70
70
  m = c.constants
71
- r = m.RecurseInto.NONE
72
- for a in m.ARCHIVE_FORMATS:
73
- if getattr(n, a._name_.lower()):
74
- r |= a
71
+ r = m.RecurseInto
75
72
  return c.check(
76
73
  n.filenames,
77
74
  clear=n.clear,
78
75
  may_not_exist=n.may_not_exist,
79
- recurse_into=r,
76
+ recurse_into=__import__('functools').reduce(
77
+ r.__or__,
78
+ filter(lambda a: getattr(n, a.name.lower()), m.ARCHIVE_FORMATS),
79
+ r.NONE,
80
+ ),
80
81
  verbosity=2 + n.verbose - n.quiet,
81
82
  )
82
83
 
@@ -3,23 +3,13 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import os
6
- from itertools import filterfalse
7
- from operator import attrgetter
8
6
  from tempfile import NamedTemporaryFile
9
7
 
10
- TYPE_CHECKING = False
11
- if TYPE_CHECKING:
12
- import tarfile
13
- import zipfile
14
- from collections.abc import Callable, Iterable
15
- from typing import Final, Literal
16
-
17
- import arpy
18
- import py7zr
19
8
  __all__ = (
20
9
  'AR_FS',
21
10
  'AR_MAG',
22
11
  'AR_NAME_SIZE',
12
+ 'AR_SKIP',
23
13
  'clear_file_in_zip',
24
14
  'purge_7z',
25
15
  'purge_ar',
@@ -27,13 +17,26 @@ __all__ = (
27
17
  )
28
18
  _ = __import__('io').BytesIO()
29
19
  AR_FS: Final = b'%-16b0 0 0 100644 %-10d`\n%b'
20
+ """A C-style format string used to create file headers and bodies in ar archives."""
30
21
  AR_MAG: Final = b'!<arch>\n'
22
+ """The magic number for ar archives."""
31
23
  AR_NAME_SIZE: Final = 16
24
+ """The maximum length of a file name in an ar archive before special handling."""
32
25
  AR_SKIP: Final[
33
26
  frozenset[
34
27
  Literal[b'/', b'//', b'/SYM64/', b'__.SYMDEF', b'__.SYMDEF SORTED'] | None
35
28
  ]
36
29
  ] = frozenset((b'/', b'//', b'/SYM64/', b'__.SYMDEF', b'__.SYMDEF SORTED', None))
30
+ """The file names to skip when clearing files in an ar archive."""
31
+ TYPE_CHECKING = False
32
+ if TYPE_CHECKING:
33
+ import tarfile
34
+ import zipfile
35
+ from collections.abc import Callable, Iterable
36
+ from typing import Final, Literal
37
+
38
+ import py7zr
39
+ from arpy import Archive
37
40
 
38
41
 
39
42
  def clear_file_in_zip(z: zipfile.ZipFile, i: zipfile.ZipInfo) -> None:
@@ -71,7 +74,7 @@ def purge_tar(t: tarfile.TarFile) -> None:
71
74
  os.replace(p.name, x)
72
75
 
73
76
 
74
- def purge_ar(a: arpy.Archive, g: int) -> None:
77
+ def purge_ar(a: Archive, g: int) -> None:
75
78
  """Clear the contents of each file in a .a, .ar or .lib archive."""
76
79
  a.read_all_headers()
77
80
  i, n = a.headers, a.file.name
@@ -83,7 +86,9 @@ def purge_ar(a: arpy.Archive, g: int) -> None:
83
86
  with NamedTemporaryFile(suffix='.a', delete=False) as f:
84
87
  w = f.write
85
88
  w(AR_MAG)
86
- q = filterfalse(AR_SKIP.__contains__, map(attrgetter('name'), i))
89
+ q = __import__('itertools').filterfalse(
90
+ AR_SKIP.__contains__, map(__import__('operator').attrgetter('name'), i)
91
+ )
87
92
  _purge_ar_gnu(tuple(q), w) if i[0].type == g else _purge_ar_bsd(q, w)
88
93
  os.replace(f.name, n)
89
94
 
@@ -4,9 +4,6 @@ from __future__ import annotations
4
4
 
5
5
  import os
6
6
 
7
- TYPE_CHECKING = False
8
- if TYPE_CHECKING:
9
- from typing import Final
10
7
  __all__ = (
11
8
  'DIRFD_UNSUPPORTED',
12
9
  'ENOENT',
@@ -15,8 +12,11 @@ __all__ = (
15
12
  'TYPE_MASK',
16
13
  'RecurseInto',
17
14
  )
15
+ TYPE_CHECKING = False
16
+ if TYPE_CHECKING:
17
+ from typing import Final
18
18
  TYPE_MASK: Final = 0xF000
19
- """This mask extracts the type of a file from its mode bits from :func:`os.stat`."""
19
+ """The mask that extracts the type of a file from its mode bits from :func:`os.stat`."""
20
20
  S_IFDIR: Final = 0x4000
21
21
  """Equal to :data:`stat.S_IFDIR`."""
22
22
  ENOENT: Final = 2
@@ -58,7 +58,7 @@ class RecurseInto(__import__('enum').IntFlag):
58
58
  """Recurse into all archive types."""
59
59
 
60
60
 
61
- ARCHIVE_FORMATS: Final = tuple(
61
+ ARCHIVE_FORMATS: Final[tuple[RecurseInto, ...]] = tuple(
62
62
  filter(lambda x: x > 0 == x & (x - 1), RecurseInto)
63
63
  if __import__('sys').version_info < (3, 11)
64
64
  else RecurseInto
@@ -5,19 +5,18 @@ from __future__ import annotations
5
5
  import itertools as i
6
6
  import sys as s
7
7
 
8
- from . import abc
9
-
10
- TYPE_CHECKING = False
11
- if TYPE_CHECKING:
12
- from typing import IO
8
+ from check_empty.reporter import abcdef, dct as dct
13
9
 
14
10
  __all__ = ('DelayedReporter', 'Reporter')
11
+ TYPE_CHECKING = False
15
12
 
16
13
 
17
- class Reporter(abc.ReporterABC):
14
+ class Reporter(abcdef.ReporterABC):
18
15
  """Concrete and default reporter implementation that prints output to a stream."""
19
16
 
20
17
  if TYPE_CHECKING:
18
+ from typing import IO
19
+
21
20
  o: IO[str] | None
22
21
 
23
22
  def to(self, out: IO[str] | None = None) -> IO[str] | None:
@@ -35,7 +34,7 @@ class Reporter(abc.ReporterABC):
35
34
  def report( # ruff: ignore[too-many-arguments, too-many-positional-arguments]
36
35
  self,
37
36
  z: list[str] | None,
38
- w: list[str] | None,
37
+ w: list[OSError] | None,
39
38
  a: list[str] | None,
40
39
  b: list[str] | None,
41
40
  r: list[tuple[str, int]] | None,
@@ -68,7 +67,7 @@ class Reporter(abc.ReporterABC):
68
67
  else '\nAll files were found'
69
68
  )
70
69
  if w is not None:
71
- q('\nError: '.join(w))
70
+ q('\nError: '.join(i.chain(('',), map(str, w))))
72
71
  if d:
73
72
  q(f'\n{d} I/O error{"s" if d > 1 else ""} encountered')
74
73
  if b is not None:
@@ -93,5 +92,8 @@ class Reporter(abc.ReporterABC):
93
92
  q.close()
94
93
 
95
94
 
96
- class DelayedReporter(abc.DelayedReporterMixin, Reporter):
95
+ class DelayedReporter(abcdef.DelayedReporterMixin, Reporter):
97
96
  """The delayed version of the default reporter."""
97
+
98
+
99
+ del TYPE_CHECKING
@@ -1,17 +1,16 @@
1
- """Abstract base classes for reporters."""
1
+ """Abstract base classes definitions ("abcdef") for reporters."""
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
5
  import abc as a
6
6
 
7
- from .exceptions import NoReport
7
+ from check_empty.reporter.exceptions import NoReport
8
8
 
9
+ __all__ = ('DelayedReporterMixin', 'ReporterABC')
9
10
  TYPE_CHECKING = False
10
11
  if TYPE_CHECKING:
11
12
  from typing import Any
12
13
 
13
- __all__ = ('DelayedReporterMixin', 'ReporterABC')
14
-
15
14
 
16
15
  class ReporterABC(metaclass=a.ABCMeta):
17
16
  """Abstract base class for all reporters accepted by :func:`~check_empty.check`."""
@@ -23,7 +22,7 @@ class ReporterABC(metaclass=a.ABCMeta):
23
22
  def report( # ruff: ignore[too-many-arguments, too-many-positional-arguments]
24
23
  self,
25
24
  z: list[str] | None,
26
- w: list[str] | None,
25
+ w: list[OSError] | None,
27
26
  a: list[str] | None,
28
27
  b: list[str] | None,
29
28
  r: list[tuple[str, int]] | None,
@@ -41,7 +40,7 @@ class ReporterABC(metaclass=a.ABCMeta):
41
40
 
42
41
  Args:
43
42
  z: list of paths to files that were not found.
44
- w: list of error messages for files that could not be processed.
43
+ w: list of I/O errors when processing non-missing files.
45
44
  a: list of directories that were recursed into.
46
45
  b: list of empty file paths.
47
46
  r: list of 2-tuples representing non-empty files and their sizes.
@@ -127,3 +126,6 @@ class DelayedReporterMixin(ReporterABC):
127
126
  if a is None:
128
127
  raise NoReport(self)
129
128
  super().report(*a)
129
+
130
+
131
+ del TYPE_CHECKING
@@ -4,16 +4,15 @@ from __future__ import annotations
4
4
 
5
5
  import itertools as i
6
6
 
7
- from . import exceptions as _
8
- from .abc import ReporterABC
7
+ from check_empty.reporter import exceptions as _
8
+ from check_empty.reporter.abcdef import ReporterABC
9
9
 
10
+ __all__ = ('DictReporter', 'SelfReporter')
10
11
  TYPE_CHECKING = False
11
12
  if TYPE_CHECKING:
12
13
  from collections.abc import Iterable, MutableMapping
13
14
  from typing import Any, Final
14
15
 
15
- __all__ = ('DictReporter', 'SelfReporter')
16
-
17
16
 
18
17
  class DictReporter(ReporterABC):
19
18
  """Dump the metrics into a dictionary-like object."""
@@ -63,7 +62,7 @@ class DictReporter(ReporterABC):
63
62
  The previous output mapping, or ``None`` if no redirection is in effect.
64
63
  """
65
64
 
66
- def __init__(self, keys: Iterable[str | None] | None = None) -> None:
65
+ def __init__(self, keys: Iterable[str | None] | None = None):
67
66
  """Initialize the reporter.
68
67
 
69
68
  Args:
@@ -139,3 +138,6 @@ class SelfReporter(DictReporter):
139
138
  This is only implemented at type-check time because the exact attributes
140
139
  and their types are not statically known.
141
140
  """
141
+
142
+
143
+ del TYPE_CHECKING
@@ -2,13 +2,6 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- TYPE_CHECKING = False
6
- if TYPE_CHECKING:
7
- from typing import ClassVar
8
-
9
- from .abc import DelayedReporterMixin, ReporterABC
10
- from .dict import DictReporter
11
-
12
5
  __all__ = (
13
6
  'IncorrectKeynames',
14
7
  'IncorrectKeynamesLength',
@@ -17,6 +10,12 @@ __all__ = (
17
10
  'NoReport',
18
11
  'ReporterError',
19
12
  )
13
+ TYPE_CHECKING = False
14
+ if TYPE_CHECKING:
15
+ from typing import ClassVar
16
+
17
+ from check_empty.reporter.abcdef import DelayedReporterMixin, ReporterABC
18
+ from check_empty.reporter.dct import DictReporter
20
19
 
21
20
 
22
21
  class ReporterError(Exception):
@@ -27,7 +26,7 @@ class ReporterError(Exception):
27
26
  message: ClassVar[str]
28
27
  """The message of the exception."""
29
28
 
30
- def __init__(self, reporter: ReporterABC) -> None:
29
+ def __init__(self, reporter: ReporterABC):
31
30
  """Initialize the exception.
32
31
 
33
32
  Args:
@@ -46,7 +45,7 @@ class NoReport(ReporterError):
46
45
 
47
46
 
48
47
  class IncorrectKeynames(ReporterError):
49
- """A :class:`check_empty.reporter.dict.DictReporter` got invalid ``keys``."""
48
+ """A :class:`check_empty.reporter.dct.DictReporter` got invalid ``keys``."""
50
49
 
51
50
  reporter: DictReporter
52
51
  """The reporter concerned."""
@@ -65,6 +64,11 @@ class IncorrectKeynamesType(IncorrectKeynames):
65
64
 
66
65
 
67
66
  class NoOutputMapping(ReporterError):
68
- """A :class:`check_empty.reporter.dict.DictReporter` had no output mapping."""
67
+ """A :class:`check_empty.reporter.dct.DictReporter` had no output mapping."""
69
68
 
69
+ reporter: DictReporter
70
+ """The reporter concerned."""
70
71
  message = 'no mapping to output to'
72
+
73
+
74
+ del TYPE_CHECKING
@@ -1,5 +1,5 @@
1
1
  [build-system]
2
- requires = ["uv_build>=0.12.18"]
2
+ requires = ["uv_build>=0.12.19"]
3
3
  build-backend = "uv_build"
4
4
 
5
5
  [dependency-groups]
@@ -55,7 +55,7 @@ license-files = [
55
55
  name = "check-empty"
56
56
  readme = "README.md"
57
57
  requires-python = ">=3.10"
58
- version = "2.0.0"
58
+ version = "3.0.1"
59
59
 
60
60
  [[project.authors]]
61
61
  name = "Jonathan Dung"
@@ -68,7 +68,7 @@ email = "jonathandung@yahoo.com"
68
68
  [project.optional-dependencies]
69
69
  7z = ["py7zr==1.1.3"]
70
70
  ace = ["acefile==0.6.14"]
71
- all = ["check-empty[7z,ace,ar,lzh,rar]==2.0.0"]
71
+ all = ["check-empty[7z,ace,ar,lzh,rar]==3.0.1"]
72
72
  ar = ["arpy==2.4.0"]
73
73
  lzh = ["lhafile==0.3.1"]
74
74
  rar = ["rarfile==4.5"]
@@ -77,7 +77,7 @@ rar = ["rarfile==4.5"]
77
77
  check-empty = "check_empty.__main__:main"
78
78
 
79
79
  [tool.ruff]
80
- required-version = ">=0.16.8"
80
+ required-version = ">=0.16.9"
81
81
  target-version = "py310"
82
82
 
83
83
  [tool.ruff.analyze]
@@ -95,17 +95,12 @@ ignore = [
95
95
  "builtin-open",
96
96
  "complex-structure",
97
97
  "error-suffix-on-exception-name",
98
- "implicit-namespace-package",
99
98
  "import-outside-top-level",
100
99
  "incorrect-blank-line-before-class",
101
100
  "meta-class-abc-meta",
102
101
  "multi-line-summary-second-line",
103
102
  "non-empty-init-module",
104
103
  "os-replace",
105
- "too-many-branches",
106
- "too-many-locals",
107
- "too-many-statements",
108
- "unnecessary-dunder-call",
109
104
  ]
110
105
  preview = true
111
106
  select = ["ALL"]
@@ -115,6 +110,9 @@ docstring-quotes = "double"
115
110
  inline-quotes = "single"
116
111
  multiline-quotes = "double"
117
112
 
113
+ [tool.ruff.lint.flake8-annotations]
114
+ mypy-init-return = true
115
+
118
116
  [tool.ruff.lint.isort]
119
117
  combine-as-imports = true
120
118
 
@@ -123,20 +121,18 @@ combine-as-imports = true
123
121
  "D",
124
122
  "missing-type-args",
125
123
  ]
124
+ "!check_empty/__init__.py" = ["missing-copyright-notice"]
125
+ "check_empty/__init__.py" = ["PLR"]
126
+ "check_empty/__main__.py" = ["missing-return-type-private-function"]
127
+ "docs/source/conf.py" = ["implicit-namespace-package"]
126
128
  "test_check_empty.py" = [
127
129
  "ANN",
128
130
  "D",
129
131
  ]
130
- "!check_empty/__init__.py" = ["missing-copyright-notice"]
131
- "check_empty/__main__.py" = ["missing-return-type-private-function"]
132
132
 
133
133
  [tool.ruff.lint.pydocstyle]
134
134
  convention = "google"
135
135
 
136
- [tool.ruff.lint.pylint]
137
- max-args = 6
138
- max-nested-blocks = 6
139
-
140
136
  [tool.ty.analysis]
141
137
  respect-type-ignore-comments = false
142
138
 
@@ -157,7 +153,7 @@ redundant-condition = "ignore"
157
153
  unsound-assignment = "ignore"
158
154
 
159
155
  [tool.uv]
160
- required-version = ">=0.12.18"
156
+ required-version = ">=0.12.19"
161
157
 
162
158
  [tool.uv.build-backend]
163
159
  module-name = "check_empty"
@@ -1,5 +1,5 @@
1
1
  [build-system]
2
- requires = ["uv_build>=0.12.18"]
2
+ requires = ["uv_build>=0.12.19"]
3
3
  build-backend = "uv_build"
4
4
 
5
5
  [dependency-groups]
@@ -16,12 +16,12 @@ maintainers = [{name = "Jonathan Dung", email = "jonathandung@yahoo.com"}]
16
16
  name = "check-empty"
17
17
  readme = "README.md"
18
18
  requires-python = ">=3.10"
19
- version = "2.0.0"
19
+ version = "3.0.1"
20
20
 
21
21
  [project.optional-dependencies]
22
22
  "7z" = ["py7zr==1.1.3"]
23
23
  ace = ["acefile==0.6.14"]
24
- all = ["check-empty[7z,ace,ar,lzh,rar]==2.0.0"]
24
+ all = ["check-empty[7z,ace,ar,lzh,rar]==3.0.1"]
25
25
  ar = ["arpy==2.4.0"]
26
26
  lzh = ["lhafile==0.3.1"]
27
27
  rar = ["rarfile==4.5"]
@@ -30,7 +30,7 @@ rar = ["rarfile==4.5"]
30
30
  check-empty = "check_empty.__main__:main"
31
31
 
32
32
  [tool.ruff]
33
- required-version = ">=0.16.8"
33
+ required-version = ">=0.16.9"
34
34
  target-version = "py310"
35
35
 
36
36
  [tool.ruff.analyze]
@@ -40,7 +40,7 @@ type-checking-imports = false
40
40
  quote-style = "single"
41
41
 
42
42
  [tool.ruff.lint]
43
- ignore = ["COM", "FBT", "PT", "bad-quotes-inline-string", "builtin-open", "complex-structure", "error-suffix-on-exception-name", "implicit-namespace-package", "import-outside-top-level", "incorrect-blank-line-before-class", "meta-class-abc-meta", "multi-line-summary-second-line", "non-empty-init-module", "os-replace", "too-many-branches", "too-many-locals", "too-many-statements", "unnecessary-dunder-call"]
43
+ ignore = ["COM", "FBT", "PT", "bad-quotes-inline-string", "builtin-open", "complex-structure", "error-suffix-on-exception-name", "import-outside-top-level", "incorrect-blank-line-before-class", "meta-class-abc-meta", "multi-line-summary-second-line", "non-empty-init-module", "os-replace"]
44
44
  preview = true
45
45
  select = ["ALL"]
46
46
 
@@ -49,22 +49,23 @@ docstring-quotes = "double"
49
49
  inline-quotes = "single"
50
50
  multiline-quotes = "double"
51
51
 
52
+ [tool.ruff.lint.flake8-annotations]
53
+ mypy-init-return = true
54
+
52
55
  [tool.ruff.lint.isort]
53
56
  combine-as-imports = true
54
57
 
55
58
  [tool.ruff.lint.per-file-ignores]
56
59
  "action.py" = ["D", "missing-type-args"]
57
- "test_check_empty.py" = ["ANN", "D"]
58
60
  "!check_empty/__init__.py" = ["missing-copyright-notice"]
61
+ "check_empty/__init__.py" = ["PLR"]
59
62
  "check_empty/__main__.py" = ["missing-return-type-private-function"]
63
+ "docs/source/conf.py" = ["implicit-namespace-package"]
64
+ "test_check_empty.py" = ["ANN", "D"]
60
65
 
61
66
  [tool.ruff.lint.pydocstyle]
62
67
  convention = "google"
63
68
 
64
- [tool.ruff.lint.pylint]
65
- max-args = 6
66
- max-nested-blocks = 6
67
-
68
69
  [tool.ty.analysis]
69
70
  respect-type-ignore-comments = false
70
71
 
@@ -81,7 +82,7 @@ include = ["check_empty/__init__.py"]
81
82
  rules = {possibly-unresolved-reference = "ignore", redundant-condition = "ignore", unsound-assignment = "ignore"}
82
83
 
83
84
  [tool.uv]
84
- required-version = ">=0.12.18"
85
+ required-version = ">=0.12.19"
85
86
 
86
87
  [tool.uv.build-backend]
87
88
  module-name = "check_empty"
File without changes
File without changes