comparison mercurial/localrepo.py @ 51891:ee7e106b372b

typing: make the localrepo classes known to pytype 9d4ad05bc91c and 1b17309cdaab both mentioned making `bundlerepository` and `unionrepository` subclass `localrepository` during the type checking phase, but that didn't apply to pytype in practice. See bcaa5d408657 and friends for how the zope interfaces confuse pytype, and end up converting the classes they decorate into `Any`. This commit is slightly more complex though, because `localrepository` has mixin classes applied to it when it is instantiated. Specifically, `RevlogFileStorage` is added, which adds `def file(f)` (which isn't defined on `localrepository`). Therefore a list of `localrepository` superclasses is provided during type checking to account for the mixins. Without this, the `bundlerepository` class gets flagged when it attempts to call its superclass implementation of `file()`. Note that pytype doesn't understand these mixin superclasses (it marks the superclass of `localrepository` as `Any`, because they are zope interfaces it doesn't understand), but that's enough to get it to not flag `bundlerepository`. PyCharm also stops flagging it as a missing function, though it seems like it is able to handle the zope interfaces.
author Matt Harbison <matt_harbison@yahoo.com>
date Fri, 20 Sep 2024 21:31:58 -0400
parents f4733654f144
children f0e07efc199f
comparison
equal deleted inserted replaced
51890:992fcf6b2473 51891:ee7e106b372b
12 import os 12 import os
13 import random 13 import random
14 import re 14 import re
15 import sys 15 import sys
16 import time 16 import time
17 import typing
17 import weakref 18 import weakref
18 19
19 from concurrent import futures 20 from concurrent import futures
20 from typing import ( 21 from typing import (
21 Optional, 22 Optional,
253 b'unbundle', 254 b'unbundle',
254 } 255 }
255 legacycaps = moderncaps.union({b'changegroupsubset'}) 256 legacycaps = moderncaps.union({b'changegroupsubset'})
256 257
257 258
258 @interfaceutil.implementer(repository.ipeercommandexecutor) 259 class LocalCommandExecutor:
259 class localcommandexecutor:
260 def __init__(self, peer): 260 def __init__(self, peer):
261 self._peer = peer 261 self._peer = peer
262 self._sent = False 262 self._sent = False
263 self._closed = False 263 self._closed = False
264 264
299 299
300 def close(self): 300 def close(self):
301 self._closed = True 301 self._closed = True
302 302
303 303
304 @interfaceutil.implementer(repository.ipeercommands) 304 localcommandexecutor = interfaceutil.implementer(
305 class localpeer(repository.peer): 305 repository.ipeercommandexecutor
306 )(LocalCommandExecutor)
307
308 if typing.TYPE_CHECKING:
309 # Help pytype by hiding the interface stuff that confuses it.
310 localcommandexecutor = LocalCommandExecutor
311
312
313 class LocalPeer(repository.peer):
306 '''peer for a local repo; reflects only the most recent API''' 314 '''peer for a local repo; reflects only the most recent API'''
307 315
308 def __init__(self, repo, caps=None, path=None, remotehidden=False): 316 def __init__(self, repo, caps=None, path=None, remotehidden=False):
309 super(localpeer, self).__init__( 317 super(LocalPeer, self).__init__(
310 repo.ui, path=path, remotehidden=remotehidden 318 repo.ui, path=path, remotehidden=remotehidden
311 ) 319 )
312 320
313 if caps is None: 321 if caps is None:
314 caps = moderncaps.copy() 322 caps = moderncaps.copy()
454 return localcommandexecutor(self) 462 return localcommandexecutor(self)
455 463
456 # End of peer interface. 464 # End of peer interface.
457 465
458 466
459 @interfaceutil.implementer(repository.ipeerlegacycommands) 467 localpeer = interfaceutil.implementer(repository.ipeercommands)(LocalPeer)
460 class locallegacypeer(localpeer): 468
469 if typing.TYPE_CHECKING:
470 # Help pytype by hiding the interface stuff that confuses it.
471 localpeer = LocalPeer
472
473
474 class LocalLegacyPeer(localpeer):
461 """peer extension which implements legacy methods too; used for tests with 475 """peer extension which implements legacy methods too; used for tests with
462 restricted capabilities""" 476 restricted capabilities"""
463 477
464 def __init__(self, repo, path=None, remotehidden=False): 478 def __init__(self, repo, path=None, remotehidden=False):
465 super(locallegacypeer, self).__init__( 479 super(LocalLegacyPeer, self).__init__(
466 repo, caps=legacycaps, path=path, remotehidden=remotehidden 480 repo, caps=legacycaps, path=path, remotehidden=remotehidden
467 ) 481 )
468 482
469 # Begin of baselegacywirecommands interface. 483 # Begin of baselegacywirecommands interface.
470 484
486 ) 500 )
487 return changegroup.makechangegroup(self._repo, outgoing, b'01', source) 501 return changegroup.makechangegroup(self._repo, outgoing, b'01', source)
488 502
489 # End of baselegacywirecommands interface. 503 # End of baselegacywirecommands interface.
490 504
505
506 locallegacypeer = interfaceutil.implementer(repository.ipeerlegacycommands)(
507 LocalLegacyPeer
508 )
509
510 if typing.TYPE_CHECKING:
511 # Help pytype by hiding the interface stuff that confuses it.
512 locallegacypeer = LocalLegacyPeer
491 513
492 # Functions receiving (ui, features) that extensions can register to impact 514 # Functions receiving (ui, features) that extensions can register to impact
493 # the ability to load repositories with custom requirements. Only 515 # the ability to load repositories with custom requirements. Only
494 # functions defined in loaded extensions are called. 516 # functions defined in loaded extensions are called.
495 # 517 #
1239 def makemain(**kwargs): 1261 def makemain(**kwargs):
1240 """Produce a type conforming to ``ilocalrepositorymain``.""" 1262 """Produce a type conforming to ``ilocalrepositorymain``."""
1241 return localrepository 1263 return localrepository
1242 1264
1243 1265
1244 @interfaceutil.implementer(repository.ilocalrepositoryfilestorage) 1266 class RevlogFileStorage:
1245 class revlogfilestorage:
1246 """File storage when using revlogs.""" 1267 """File storage when using revlogs."""
1247 1268
1248 def file(self, path): 1269 def file(self, path):
1249 if path.startswith(b'/'): 1270 if path.startswith(b'/'):
1250 path = path[1:] 1271 path = path[1:]
1255 ) 1276 )
1256 1277
1257 return filelog.filelog(self.svfs, path, try_split=try_split) 1278 return filelog.filelog(self.svfs, path, try_split=try_split)
1258 1279
1259 1280
1260 @interfaceutil.implementer(repository.ilocalrepositoryfilestorage) 1281 revlogfilestorage = interfaceutil.implementer(
1261 class revlognarrowfilestorage: 1282 repository.ilocalrepositoryfilestorage
1283 )(RevlogFileStorage)
1284
1285 if typing.TYPE_CHECKING:
1286 # Help pytype by hiding the interface stuff that confuses it.
1287 revlogfilestorage = RevlogFileStorage
1288
1289
1290 class RevlogNarrowFileStorage:
1262 """File storage when using revlogs and narrow files.""" 1291 """File storage when using revlogs and narrow files."""
1263 1292
1264 def file(self, path): 1293 def file(self, path):
1265 if path.startswith(b'/'): 1294 if path.startswith(b'/'):
1266 path = path[1:] 1295 path = path[1:]
1270 or txnutil.mayhavepending(self.root) 1299 or txnutil.mayhavepending(self.root)
1271 ) 1300 )
1272 return filelog.narrowfilelog( 1301 return filelog.narrowfilelog(
1273 self.svfs, path, self._storenarrowmatch, try_split=try_split 1302 self.svfs, path, self._storenarrowmatch, try_split=try_split
1274 ) 1303 )
1304
1305
1306 revlognarrowfilestorage = interfaceutil.implementer(
1307 repository.ilocalrepositoryfilestorage
1308 )(RevlogNarrowFileStorage)
1309
1310 if typing.TYPE_CHECKING:
1311 # Help pytype by hiding the interface stuff that confuses it.
1312 revlognarrowfilestorage = RevlogNarrowFileStorage
1275 1313
1276 1314
1277 def makefilestorage(requirements, features, **kwargs): 1315 def makefilestorage(requirements, features, **kwargs):
1278 """Produce a type conforming to ``ilocalrepositoryfilestorage``.""" 1316 """Produce a type conforming to ``ilocalrepositoryfilestorage``."""
1279 features.add(repository.REPO_FEATURE_REVLOG_FILE_STORAGE) 1317 features.add(repository.REPO_FEATURE_REVLOG_FILE_STORAGE)
1293 REPO_INTERFACES = [ 1331 REPO_INTERFACES = [
1294 (repository.ilocalrepositorymain, lambda: makemain), 1332 (repository.ilocalrepositorymain, lambda: makemain),
1295 (repository.ilocalrepositoryfilestorage, lambda: makefilestorage), 1333 (repository.ilocalrepositoryfilestorage, lambda: makefilestorage),
1296 ] 1334 ]
1297 1335
1298 1336 _localrepo_base_classes = object
1299 @interfaceutil.implementer(repository.ilocalrepositorymain) 1337
1300 class localrepository: 1338 if typing.TYPE_CHECKING:
1339 _localrepo_base_classes = [
1340 repository.ilocalrepositorymain,
1341 repository.ilocalrepositoryfilestorage,
1342 ]
1343
1344
1345 class LocalRepository(_localrepo_base_classes):
1301 """Main class for representing local repositories. 1346 """Main class for representing local repositories.
1302 1347
1303 All local repositories are instances of this class. 1348 All local repositories are instances of this class.
1304 1349
1305 Constructed on its own, instances of this class are not usable as 1350 Constructed on its own, instances of this class are not usable as
3596 raise error.ProgrammingError(msg % category) 3641 raise error.ProgrammingError(msg % category)
3597 self._sidedata_computers.setdefault(kind, {}) 3642 self._sidedata_computers.setdefault(kind, {})
3598 self._sidedata_computers[kind][category] = (keys, computer, flags) 3643 self._sidedata_computers[kind][category] = (keys, computer, flags)
3599 3644
3600 3645
3646 localrepository = interfaceutil.implementer(repository.ilocalrepositorymain)(
3647 LocalRepository
3648 )
3649
3650 if typing.TYPE_CHECKING:
3651 # Help pytype by hiding the interface stuff that confuses it.
3652 localrepository = LocalRepository
3653
3654
3601 def undoname(fn: bytes) -> bytes: 3655 def undoname(fn: bytes) -> bytes:
3602 base, name = os.path.split(fn) 3656 base, name = os.path.split(fn)
3603 assert name.startswith(b'journal') 3657 assert name.startswith(b'journal')
3604 return os.path.join(base, name.replace(b'journal', b'undo', 1)) 3658 return os.path.join(base, name.replace(b'journal', b'undo', 1))
3605 3659