comparison mercurial/cmdutil.py @ 52629:89215c5b714c

cmdutil: switch the `mode` on `cmdutil.makefileobj()` to str I think the typing around whether `open()` returns `IO[bytes]` or `IO[str]` hinges on the content of the mode string. Converting from bytes instead of using a literal can suppress that (though PyCharm currently complains about this). Instead, we can mandate the use of a (vastly reduced) set of mode options. For now, none of the 3 callers provide this argument, so it's not a big deal. Ideally, this would always enforce binary mode. There's a little extra typing required to pull this off. The `_unclosablefile` class can't subclass `typing.BinaryIO`, because there were a bunch of test failures around writing to stdout. Strangely, pytype didn't complain that the abstract methods on `typing.BinaryIO` weren't overridden in this case. Whatever was going on, it's a simple proxy class, so we can just cast to the expected type in the one place it is used.
author Matt Harbison <matt_harbison@yahoo.com>
date Mon, 16 Dec 2024 21:50:24 -0500
parents 9d79ffeed7c0
children 24ee91ba9aa8
comparison
equal deleted inserted replaced
52628:a19f102dd377 52629:89215c5b714c
15 import typing 15 import typing
16 16
17 from typing import ( 17 from typing import (
18 Any, 18 Any,
19 AnyStr, 19 AnyStr,
20 BinaryIO,
20 Dict, 21 Dict,
21 Iterable, 22 Iterable,
23 Literal,
22 Optional, 24 Optional,
23 TYPE_CHECKING, 25 TYPE_CHECKING,
24 cast, 26 cast,
25 ) 27 )
26 28
1358 _(b"invalid format spec '%%%s' in output filename") % c 1360 _(b"invalid format spec '%%%s' in output filename") % c
1359 ) 1361 )
1360 return b''.join(newname) 1362 return b''.join(newname)
1361 1363
1362 1364
1363 def makefilename(ctx, pat, **props): 1365 def makefilename(ctx, pat: bytes, **props):
1364 if not pat: 1366 if not pat:
1365 return pat 1367 return pat
1366 tmpl = _buildfntemplate(pat, **props) 1368 tmpl = _buildfntemplate(pat, **props)
1367 # BUG: alias expansion shouldn't be made against template fragments 1369 # BUG: alias expansion shouldn't be made against template fragments
1368 # rewritten from %-format strings, but we have no easy way to partially 1370 # rewritten from %-format strings, but we have no easy way to partially
1374 """True if the given pat looks like a filename denoting stdin/stdout""" 1376 """True if the given pat looks like a filename denoting stdin/stdout"""
1375 return not pat or pat == b'-' 1377 return not pat or pat == b'-'
1376 1378
1377 1379
1378 class _unclosablefile: 1380 class _unclosablefile:
1379 def __init__(self, fp): 1381 def __init__(self, fp: BinaryIO) -> None:
1380 self._fp = fp 1382 self._fp = fp
1381 1383
1382 def close(self): 1384 def close(self):
1383 pass 1385 pass
1384 1386
1393 1395
1394 def __exit__(self, exc_type, exc_value, exc_tb): 1396 def __exit__(self, exc_type, exc_value, exc_tb):
1395 pass 1397 pass
1396 1398
1397 1399
1398 def makefileobj(ctx, pat, mode=b'wb', **props): 1400 def makefileobj(
1399 writable = mode not in (b'r', b'rb') 1401 ctx, pat: bytes, mode: Literal['rb', 'wb'] = 'wb', **props
1402 ) -> BinaryIO:
1403 writable = mode not in ('r', 'rb')
1400 1404
1401 if isstdiofilename(pat): 1405 if isstdiofilename(pat):
1402 repo = ctx.repo() 1406 repo = ctx.repo()
1403 if writable: 1407 if writable:
1404 fp = repo.ui.fout 1408 fp = repo.ui.fout
1405 else: 1409 else:
1406 fp = repo.ui.fin 1410 fp = repo.ui.fin
1407 return _unclosablefile(fp) 1411 return typing.cast(BinaryIO, _unclosablefile(fp))
1408 fn = makefilename(ctx, pat, **props) 1412 fn = makefilename(ctx, pat, **props)
1409 return open(fn, pycompat.sysstr(mode)) 1413 return open(fn, mode)
1410 1414
1411 1415
1412 def openstorage(repo, cmd, file_, opts, returnrevlog=False): 1416 def openstorage(repo, cmd, file_, opts, returnrevlog=False):
1413 """opens the changelog, manifest, a filelog or a given revlog""" 1417 """opens the changelog, manifest, a filelog or a given revlog"""
1414 cl = opts[b'changelog'] 1418 cl = opts[b'changelog']