comparison mercurial/vfs.py @ 51880:f79f98733a5b

vfs: use @abstractmethod instead of homebrewing abstract methods The latter confuses PyCharm after adding more type annotations when, for example, `abstractvfs.rename()` calls `_auditpath()`- the latter unconditionally raised an error, so PyCharm thought the code that came after is unreachable. It also tricked pytype into marking the return type as `Never`, which isn't available until Python 3.11 (outside of `typing_extensions`). This also avoid PyCharm warnings that the call to the superclass constructor was missed (it couldn't be called because it raised an error to prevent instantiation). The statichttprepo module needed to be given an override for one of the abstract methods, so that it can be instantiated. In `abstractvfs`, this method is only called by `rename()`, so I think we can leave this empty. We raise an error in case somebody accidentally calls it in the future- it would have raised this same error prior to this change. I couldn't wrangle `import-checker.py` into accepting importing `ABC` and `abstractmethod`- for each subsequent import, it reports something like: stdlib import "contextlib" follows local import: abc I suspect the problem is that near the `if fullname != '__future__'` check, if the module doesn't fall into the error case, `seenlocal` gets set to the module name. That causes it to be treated like a local module on the next iteration, even though it is in `stdlib_modules`.
author Matt Harbison <matt_harbison@yahoo.com>
date Fri, 20 Sep 2024 00:07:39 -0400
parents 1edac12af730
children adbb183c2f27
comparison
equal deleted inserted replaced
51879:1edac12af730 51880:f79f98733a5b
5 # This software may be used and distributed according to the terms of the 5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version. 6 # GNU General Public License version 2 or any later version.
7 7
8 from __future__ import annotations 8 from __future__ import annotations
9 9
10 import abc
10 import contextlib 11 import contextlib
11 import os 12 import os
12 import shutil 13 import shutil
13 import stat 14 import stat
14 import threading 15 import threading
44 # advance mtime (see issue5418) 45 # advance mtime (see issue5418)
45 util.rename(util.mktempcopy(path), path) 46 util.rename(util.mktempcopy(path), path)
46 checkandavoid() 47 checkandavoid()
47 48
48 49
49 class abstractvfs: 50 class abstractvfs(abc.ABC):
50 """Abstract base class; cannot be instantiated""" 51 """Abstract base class; cannot be instantiated"""
51 52
52 # default directory separator for vfs 53 # default directory separator for vfs
53 # 54 #
54 # Other vfs code always use `/` and this works fine because python file API 55 # Other vfs code always use `/` and this works fine because python file API
55 # abstract the use of `/` and make it work transparently. For consistency 56 # abstract the use of `/` and make it work transparently. For consistency
56 # vfs will always use `/` when joining. This avoid some confusion in 57 # vfs will always use `/` when joining. This avoid some confusion in
57 # encoded vfs (see issue6546) 58 # encoded vfs (see issue6546)
58 _dir_sep = b'/' 59 _dir_sep = b'/'
59 60
60 def __init__(self, *args, **kwargs):
61 '''Prevent instantiation; don't call this from subclasses.'''
62 raise NotImplementedError('attempted instantiating ' + str(type(self)))
63
64 # TODO: type return, which is util.posixfile wrapped by a proxy 61 # TODO: type return, which is util.posixfile wrapped by a proxy
62 @abc.abstractmethod
65 def __call__(self, path: bytes, mode: bytes = b'rb', **kwargs): 63 def __call__(self, path: bytes, mode: bytes = b'rb', **kwargs):
66 raise NotImplementedError 64 ...
67 65
66 @abc.abstractmethod
68 def _auditpath(self, path: bytes, mode: bytes): 67 def _auditpath(self, path: bytes, mode: bytes):
69 raise NotImplementedError 68 ...
70 69
70 @abc.abstractmethod
71 def join(self, path: Optional[bytes], *insidef: bytes) -> bytes: 71 def join(self, path: Optional[bytes], *insidef: bytes) -> bytes:
72 raise NotImplementedError 72 ...
73 73
74 def tryread(self, path: bytes) -> bytes: 74 def tryread(self, path: bytes) -> bytes:
75 '''gracefully return an empty string for missing files''' 75 '''gracefully return an empty string for missing files'''
76 try: 76 try:
77 return self.read(path) 77 return self.read(path)
623 623
624 624
625 opener = vfs 625 opener = vfs
626 626
627 627
628 class proxyvfs(abstractvfs): 628 class proxyvfs(abstractvfs, abc.ABC):
629 def __init__(self, vfs: "vfs"): 629 def __init__(self, vfs: "vfs"):
630 self.vfs = vfs 630 self.vfs = vfs
631 631
632 @property 632 @property
633 def createmode(self): 633 def createmode(self):
682 682
683 def join(self, path: Optional[bytes], *insidef: bytes) -> bytes: 683 def join(self, path: Optional[bytes], *insidef: bytes) -> bytes:
684 return self.vfs.join(path, *insidef) 684 return self.vfs.join(path, *insidef)
685 685
686 686
687 class closewrapbase: 687 class closewrapbase(abc.ABC):
688 """Base class of wrapper, which hooks closing 688 """Base class of wrapper, which hooks closing
689 689
690 Do not instantiate outside of the vfs layer. 690 Do not instantiate outside of the vfs layer.
691 """ 691 """
692 692
704 704
705 def __enter__(self): 705 def __enter__(self):
706 self._origfh.__enter__() 706 self._origfh.__enter__()
707 return self 707 return self
708 708
709 @abc.abstractmethod
709 def __exit__(self, exc_type, exc_value, exc_tb): 710 def __exit__(self, exc_type, exc_value, exc_tb):
710 raise NotImplementedError('attempted instantiating ' + str(type(self))) 711 ...
711 712
713 @abc.abstractmethod
712 def close(self): 714 def close(self):
713 raise NotImplementedError('attempted instantiating ' + str(type(self))) 715 ...
714 716
715 717
716 class delayclosedfile(closewrapbase): 718 class delayclosedfile(closewrapbase):
717 """Proxy for a file object whose close is delayed. 719 """Proxy for a file object whose close is delayed.
718 720