comparison mercurial/error.py @ 46899:8b6e36e4b553

typing: add type hints to mercurial/error.py The only slightly unusual things here are that `location` is passed to `ParseError` and both bytes and an int (so this accepts both), and the message passed `ProgrammingError` is immediately converted to str. Therefore it is typed as `AnyStr`, because there are a couple of instances that are already passed as str. There are a couple of places where bytes are being passed to builtin exceptions that might need to be converted to str. Differential Revision: https://phab.mercurial-scm.org/D10274
author Matt Harbison <matt_harbison@yahoo.com>
date Thu, 25 Mar 2021 18:59:14 -0400
parents d4ba4d51f85f
children 14ddb1dca2c0
comparison
equal deleted inserted replaced
46898:e1d75c514ced 46899:8b6e36e4b553
18 # Do not import anything but pycompat here, please 18 # Do not import anything but pycompat here, please
19 from . import pycompat 19 from . import pycompat
20 20
21 if pycompat.TYPE_CHECKING: 21 if pycompat.TYPE_CHECKING:
22 from typing import ( 22 from typing import (
23 Any,
24 AnyStr,
25 Iterable,
26 List,
23 Optional, 27 Optional,
28 Sequence,
29 Union,
24 ) 30 )
25 31
26 32
27 def _tobytes(exc): 33 def _tobytes(exc):
28 """Byte-stringify exception in the same way as BaseException_str()""" 34 """Byte-stringify exception in the same way as BaseException_str()"""
107 113
108 class CommandError(Exception): 114 class CommandError(Exception):
109 """Exception raised on errors in parsing the command line.""" 115 """Exception raised on errors in parsing the command line."""
110 116
111 def __init__(self, command, message): 117 def __init__(self, command, message):
118 # type: (bytes, bytes) -> None
112 self.command = command 119 self.command = command
113 self.message = message 120 self.message = message
114 super(CommandError, self).__init__() 121 super(CommandError, self).__init__()
115 122
116 __bytes__ = _tobytes 123 __bytes__ = _tobytes
118 125
119 class UnknownCommand(Exception): 126 class UnknownCommand(Exception):
120 """Exception raised if command is not in the command table.""" 127 """Exception raised if command is not in the command table."""
121 128
122 def __init__(self, command, all_commands=None): 129 def __init__(self, command, all_commands=None):
130 # type: (bytes, Optional[List[bytes]]) -> None
123 self.command = command 131 self.command = command
124 self.all_commands = all_commands 132 self.all_commands = all_commands
125 super(UnknownCommand, self).__init__() 133 super(UnknownCommand, self).__init__()
126 134
127 __bytes__ = _tobytes 135 __bytes__ = _tobytes
129 137
130 class AmbiguousCommand(Exception): 138 class AmbiguousCommand(Exception):
131 """Exception raised if command shortcut matches more than one command.""" 139 """Exception raised if command shortcut matches more than one command."""
132 140
133 def __init__(self, prefix, matches): 141 def __init__(self, prefix, matches):
142 # type: (bytes, List[bytes]) -> None
134 self.prefix = prefix 143 self.prefix = prefix
135 self.matches = matches 144 self.matches = matches
136 super(AmbiguousCommand, self).__init__() 145 super(AmbiguousCommand, self).__init__()
137 146
138 __bytes__ = _tobytes 147 __bytes__ = _tobytes
140 149
141 class WorkerError(Exception): 150 class WorkerError(Exception):
142 """Exception raised when a worker process dies.""" 151 """Exception raised when a worker process dies."""
143 152
144 def __init__(self, status_code): 153 def __init__(self, status_code):
154 # type: (int) -> None
145 self.status_code = status_code 155 self.status_code = status_code
146 # Pass status code to superclass just so it becomes part of __bytes__ 156 # Pass status code to superclass just so it becomes part of __bytes__
147 super(WorkerError, self).__init__(status_code) 157 super(WorkerError, self).__init__(status_code)
148 158
149 __bytes__ = _tobytes 159 __bytes__ = _tobytes
157 167
158 class ConflictResolutionRequired(InterventionRequired): 168 class ConflictResolutionRequired(InterventionRequired):
159 """Exception raised when a continuable command required merge conflict resolution.""" 169 """Exception raised when a continuable command required merge conflict resolution."""
160 170
161 def __init__(self, opname): 171 def __init__(self, opname):
172 # type: (bytes) -> None
162 from .i18n import _ 173 from .i18n import _
163 174
164 self.opname = opname 175 self.opname = opname
165 InterventionRequired.__init__( 176 InterventionRequired.__init__(
166 self, 177 self,
192 # but do not replace it with encoding.strfromlocal(), which 203 # but do not replace it with encoding.strfromlocal(), which
193 # may raise another exception. 204 # may raise another exception.
194 return pycompat.sysstr(self.__bytes__()) 205 return pycompat.sysstr(self.__bytes__())
195 206
196 def format(self): 207 def format(self):
208 # type: () -> bytes
197 from .i18n import _ 209 from .i18n import _
198 210
199 message = _(b"abort: %s\n") % self.message 211 message = _(b"abort: %s\n") % self.message
200 if self.hint: 212 if self.hint:
201 message += _(b"(%s)\n") % self.hint 213 message += _(b"(%s)\n") % self.hint
245 257
246 class ConfigError(Abort): 258 class ConfigError(Abort):
247 """Exception raised when parsing config files""" 259 """Exception raised when parsing config files"""
248 260
249 def __init__(self, message, location=None, hint=None): 261 def __init__(self, message, location=None, hint=None):
262 # type: (bytes, Optional[bytes], Optional[bytes]) -> None
250 super(ConfigError, self).__init__(message, hint=hint) 263 super(ConfigError, self).__init__(message, hint=hint)
251 self.location = location 264 self.location = location
252 265
253 def format(self): 266 def format(self):
267 # type: () -> bytes
254 from .i18n import _ 268 from .i18n import _
255 269
256 if self.location is not None: 270 if self.location is not None:
257 message = _(b"config error at %s: %s\n") % ( 271 message = _(b"config error at %s: %s\n") % (
258 pycompat.bytestr(self.location), 272 pycompat.bytestr(self.location),
298 312
299 class ParseError(Abort): 313 class ParseError(Abort):
300 """Raised when parsing config files and {rev,file}sets (msg[, pos])""" 314 """Raised when parsing config files and {rev,file}sets (msg[, pos])"""
301 315
302 def __init__(self, message, location=None, hint=None): 316 def __init__(self, message, location=None, hint=None):
317 # type: (bytes, Optional[Union[bytes, int]], Optional[bytes]) -> None
303 super(ParseError, self).__init__(message, hint=hint) 318 super(ParseError, self).__init__(message, hint=hint)
304 self.location = location 319 self.location = location
305 320
306 def format(self): 321 def format(self):
322 # type: () -> bytes
307 from .i18n import _ 323 from .i18n import _
308 324
309 if self.location is not None: 325 if self.location is not None:
310 message = _(b"hg: parse error at %s: %s\n") % ( 326 message = _(b"hg: parse error at %s: %s\n") % (
311 pycompat.bytestr(self.location), 327 pycompat.bytestr(self.location),
321 class PatchError(Exception): 337 class PatchError(Exception):
322 __bytes__ = _tobytes 338 __bytes__ = _tobytes
323 339
324 340
325 def getsimilar(symbols, value): 341 def getsimilar(symbols, value):
342 # type: (Iterable[bytes], bytes) -> List[bytes]
326 sim = lambda x: difflib.SequenceMatcher(None, value, x).ratio() 343 sim = lambda x: difflib.SequenceMatcher(None, value, x).ratio()
327 # The cutoff for similarity here is pretty arbitrary. It should 344 # The cutoff for similarity here is pretty arbitrary. It should
328 # probably be investigated and tweaked. 345 # probably be investigated and tweaked.
329 return [s for s in symbols if sim(s) > 0.6] 346 return [s for s in symbols if sim(s) > 0.6]
330 347
331 348
332 def similarity_hint(similar): 349 def similarity_hint(similar):
350 # type: (List[bytes]) -> Optional[bytes]
333 from .i18n import _ 351 from .i18n import _
334 352
335 if len(similar) == 1: 353 if len(similar) == 1:
336 return _(b"did you mean %s?") % similar[0] 354 return _(b"did you mean %s?") % similar[0]
337 elif similar: 355 elif similar:
343 361
344 class UnknownIdentifier(ParseError): 362 class UnknownIdentifier(ParseError):
345 """Exception raised when a {rev,file}set references an unknown identifier""" 363 """Exception raised when a {rev,file}set references an unknown identifier"""
346 364
347 def __init__(self, function, symbols): 365 def __init__(self, function, symbols):
366 # type: (bytes, Iterable[bytes]) -> None
348 from .i18n import _ 367 from .i18n import _
349 368
350 similar = getsimilar(symbols, function) 369 similar = getsimilar(symbols, function)
351 hint = similarity_hint(similar) 370 hint = similarity_hint(similar)
352 371
377 396
378 class StdioError(IOError): 397 class StdioError(IOError):
379 """Raised if I/O to stdout or stderr fails""" 398 """Raised if I/O to stdout or stderr fails"""
380 399
381 def __init__(self, err): 400 def __init__(self, err):
401 # type: (IOError) -> None
382 IOError.__init__(self, err.errno, err.strerror) 402 IOError.__init__(self, err.errno, err.strerror)
383 403
384 # no __bytes__() because error message is derived from the standard IOError 404 # no __bytes__() because error message is derived from the standard IOError
385 405
386 406
387 class UnsupportedMergeRecords(Abort): 407 class UnsupportedMergeRecords(Abort):
388 def __init__(self, recordtypes): 408 def __init__(self, recordtypes):
409 # type: (Iterable[bytes]) -> None
389 from .i18n import _ 410 from .i18n import _
390 411
391 self.recordtypes = sorted(recordtypes) 412 self.recordtypes = sorted(recordtypes)
392 s = b' '.join(self.recordtypes) 413 s = b' '.join(self.recordtypes)
393 Abort.__init__( 414 Abort.__init__(
402 423
403 class UnknownVersion(Abort): 424 class UnknownVersion(Abort):
404 """generic exception for aborting from an encounter with an unknown version""" 425 """generic exception for aborting from an encounter with an unknown version"""
405 426
406 def __init__(self, msg, hint=None, version=None): 427 def __init__(self, msg, hint=None, version=None):
428 # type: (bytes, Optional[bytes], Optional[bytes]) -> None
407 self.version = version 429 self.version = version
408 super(UnknownVersion, self).__init__(msg, hint=hint) 430 super(UnknownVersion, self).__init__(msg, hint=hint)
409 431
410 432
411 class LockError(IOError): 433 class LockError(IOError):
412 def __init__(self, errno, strerror, filename, desc): 434 def __init__(self, errno, strerror, filename, desc):
435 # TODO: figure out if this should be bytes or str
436 # _type: (int, str, str, bytes) -> None
413 IOError.__init__(self, errno, strerror, filename) 437 IOError.__init__(self, errno, strerror, filename)
414 self.desc = desc 438 self.desc = desc
415 439
416 # no __bytes__() because error message is derived from the standard IOError 440 # no __bytes__() because error message is derived from the standard IOError
417 441
454 478
455 class ProgrammingError(Hint, RuntimeError): 479 class ProgrammingError(Hint, RuntimeError):
456 """Raised if a mercurial (core or extension) developer made a mistake""" 480 """Raised if a mercurial (core or extension) developer made a mistake"""
457 481
458 def __init__(self, msg, *args, **kwargs): 482 def __init__(self, msg, *args, **kwargs):
483 # type: (AnyStr, Any, Any) -> None
459 # On Python 3, turn the message back into a string since this is 484 # On Python 3, turn the message back into a string since this is
460 # an internal-only error that won't be printed except in a 485 # an internal-only error that won't be printed except in a
461 # stack traces. 486 # stack traces.
462 msg = pycompat.sysstr(msg) 487 msg = pycompat.sysstr(msg)
463 super(ProgrammingError, self).__init__(msg, *args, **kwargs) 488 super(ProgrammingError, self).__init__(msg, *args, **kwargs)
497 entries.append(val) 522 entries.append(val)
498 else: 523 else:
499 entries.append(b"%s=%r" % (par, pycompat.maybebytestr(val))) 524 entries.append(b"%s=%r" % (par, pycompat.maybebytestr(val)))
500 if entries: 525 if entries:
501 msg = b'%s - %s' % (msg, b', '.join(entries)) 526 msg = b'%s - %s' % (msg, b', '.join(entries))
502 ValueError.__init__(self, msg) 527 ValueError.__init__(self, msg) # TODO: convert to str?
503 528
504 529
505 class ReadOnlyPartError(RuntimeError): 530 class ReadOnlyPartError(RuntimeError):
506 """error raised when code tries to alter a part being generated""" 531 """error raised when code tries to alter a part being generated"""
507 532
531 556
532 Also contains the tombstone data substituted for the uncensored data. 557 Also contains the tombstone data substituted for the uncensored data.
533 """ 558 """
534 559
535 def __init__(self, filename, node, tombstone): 560 def __init__(self, filename, node, tombstone):
561 # type: (bytes, bytes, bytes) -> None
536 from .node import short 562 from .node import short
537 563
538 StorageError.__init__(self, b'%s:%s' % (filename, short(node))) 564 StorageError.__init__(self, b'%s:%s' % (filename, short(node)))
539 self.tombstone = tombstone 565 self.tombstone = tombstone
540 566
586 612
587 The error is a formatter string and an optional iterable of arguments. 613 The error is a formatter string and an optional iterable of arguments.
588 """ 614 """
589 615
590 def __init__(self, message, args=None): 616 def __init__(self, message, args=None):
617 # type: (bytes, Optional[Sequence[bytes]]) -> None
591 self.message = message 618 self.message = message
592 self.messageargs = args 619 self.messageargs = args