Skip to content

Commit 5cf0666

Browse files
Martin Larraldewillmcgugan
authored andcommitted
Revert back to manual registry installation (fix #228) (#232)
* Make `fs.opener.Registry` able to register `Opener` manually * Remove entry points from `setup.py` * Fix `tests.test_opener` to make sure `fs` extensions are loaded * Run `black` on `tests.test_opener` and `fs.opener` * Fix type annotations in `fs.opener`
1 parent 335e40b commit 5cf0666

File tree

11 files changed

+108
-62
lines changed

11 files changed

+108
-62
lines changed

fs/opener/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@
1010
from .parse import parse_fs_url as parse
1111
from .registry import registry
1212

13+
# Import opener modules so that `registry.install` if called on each opener
14+
from . import appfs, ftpfs, memoryfs, osfs, tarfs, tempfs, zipfs
15+
1316
# Alias functions defined as Registry methods
1417
open_fs = registry.open_fs
1518
open = registry.open

fs/opener/appfs.py

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,8 @@
99
import typing
1010

1111
from .base import Opener
12+
from .registry import registry
1213
from .errors import OpenerError
13-
from ..subfs import ClosingSubFS
14-
from .. import appfs
1514

1615
if False: # typing.TYPE_CHECKING
1716
from typing import Text, Union
@@ -20,20 +19,13 @@
2019
from ..subfs import SubFS
2120

2221

22+
@registry.install
2323
class AppFSOpener(Opener):
2424
"""``AppFS`` opener.
2525
"""
2626

2727
protocols = ["userdata", "userconf", "sitedata", "siteconf", "usercache", "userlog"]
28-
29-
_protocol_mapping = {
30-
"userdata": appfs.UserDataFS,
31-
"userconf": appfs.UserConfigFS,
32-
"sitedata": appfs.SiteDataFS,
33-
"siteconf": appfs.SiteConfigFS,
34-
"usercache": appfs.UserCacheFS,
35-
"userlog": appfs.UserLogFS,
36-
}
28+
_protocol_mapping = None
3729

3830
def open_fs(
3931
self,
@@ -44,6 +36,20 @@ def open_fs(
4436
cwd, # type: Text
4537
):
4638
# type: (...) -> Union[_AppFS, SubFS[_AppFS]]
39+
40+
from ..subfs import ClosingSubFS
41+
from .. import appfs
42+
43+
if self._protocol_mapping is None:
44+
self._protocol_mapping = {
45+
"userdata": appfs.UserDataFS,
46+
"userconf": appfs.UserConfigFS,
47+
"sitedata": appfs.SiteDataFS,
48+
"siteconf": appfs.SiteConfigFS,
49+
"usercache": appfs.UserCacheFS,
50+
"userlog": appfs.UserLogFS,
51+
}
52+
4753
fs_class = self._protocol_mapping[parse_result.protocol]
4854
resource, delim, path = parse_result.resource.partition("/")
4955
tokens = resource.split(":", 3)

fs/opener/ftpfs.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import typing
1212

1313
from .base import Opener
14+
from .registry import registry
1415
from ..errors import FSError, CreateFailed
1516

1617
if False: # typing.TYPE_CHECKING
@@ -20,6 +21,7 @@
2021
from .parse import ParseResult
2122

2223

24+
@registry.install
2325
class FTPOpener(Opener):
2426
"""`FTPFS` opener.
2527
"""

fs/opener/memoryfs.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,15 @@
99
import typing
1010

1111
from .base import Opener
12+
from .registry import registry
1213

1314
if False: # typing.TYPE_CHECKING
1415
from typing import Text
1516
from .parse import ParseResult
1617
from ..memoryfs import MemoryFS
1718

1819

20+
@registry.install
1921
class MemOpener(Opener):
2022
"""`MemoryFS` opener.
2123
"""

fs/opener/osfs.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,15 @@
99
import typing
1010

1111
from .base import Opener
12+
from .registry import registry
1213

1314
if False: # typing.TYPE_CHECKING
1415
from typing import Text
1516
from .parse import ParseResult
1617
from ..osfs import OSFS
1718

1819

20+
@registry.install
1921
class OSFSOpener(Opener):
2022
"""`OSFS` opener.
2123
"""

fs/opener/registry.py

Lines changed: 65 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from __future__ import print_function
77
from __future__ import unicode_literals
88

9+
import collections
910
import contextlib
1011
import typing
1112

@@ -17,42 +18,66 @@
1718
from .parse import parse_fs_url
1819

1920
if False: # typing.TYPE_CHECKING
20-
from typing import Iterator, List, Text, Tuple, Union
21+
from typing import Iterator, List, Optional, Text, Tuple, Union
2122
from ..base import FS
2223

2324

2425
class Registry(object):
2526
"""A registry for `Opener` instances.
2627
"""
2728

28-
def __init__(self, default_opener="osfs"):
29-
# type: (Text) -> None
29+
def __init__(self, default_opener="osfs", load_extern=False):
30+
# type: (Text, bool) -> None
3031
"""Create a registry object.
3132
3233
Arguments:
3334
default_opener (str, optional): The protocol to use, if one
3435
is not supplied. The default is to use 'osfs', so that the
3536
FS URL is treated as a system path if no protocol is given.
37+
load_extern (bool, optional): Set to `True` to load openers from
38+
Pyfilesystem2 extensions. Defaults to `False`.
3639
3740
"""
3841
self.default_opener = default_opener
39-
self._protocols = None # type: Union[None, List[Text]]
42+
self.load_extern = load_extern
43+
self._protocols = {} # type: Mapping[Text, Opener]
4044

4145
def __repr__(self):
4246
# type: () -> Text
4347
return "<fs-registry {!r}>".format(self.protocols)
4448

49+
def install(self, opener):
50+
# type: (Union[Type[Opener], Opener, Callable[[], Opener]]) -> None
51+
"""Install an opener.
52+
53+
Arguments:
54+
opener (`Opener`): an `Opener` instance, or a callable that
55+
returns an opener instance.
56+
57+
Note:
58+
May be used as a class decorator. For example::
59+
registry = Registry()
60+
@registry.install
61+
class ArchiveOpener(Opener):
62+
protocols = ['zip', 'tar']
63+
"""
64+
if not isinstance(opener, Opener):
65+
opener = opener()
66+
assert opener.protocols, "must list one or more protocols"
67+
for protocol in opener.protocols:
68+
self._protocols[protocol] = opener
69+
4570
@property
4671
def protocols(self):
4772
# type: () -> List[Text]
4873
"""`list`: the list of supported protocols.
4974
"""
50-
if self._protocols is None:
51-
self._protocols = [
52-
entry_point.name
53-
for entry_point in pkg_resources.iter_entry_points("fs.opener")
54-
]
55-
return self._protocols
75+
# we use OrderedDict to build an ordered set of protocols
76+
_protocols = collections.OrderedDict((k, None) for k in self._protocols)
77+
if self.load_extern:
78+
for entry_point in pkg_resources.iter_entry_points("fs.opener"):
79+
_protocols[entry_point.name] = None
80+
return list(_protocols.keys())
5681

5782
def get_opener(self, protocol):
5883
# type: (Text) -> Opener
@@ -73,29 +98,41 @@ def get_opener(self, protocol):
7398
7499
"""
75100
protocol = protocol or self.default_opener
76-
entry_point = next(pkg_resources.iter_entry_points("fs.opener", protocol), None)
77101

78-
if entry_point is None:
79-
raise UnsupportedProtocol("protocol '{}' is not supported".format(protocol))
80-
81-
try:
82-
opener = entry_point.load()
83-
except Exception as exception:
84-
six.raise_from(
85-
EntryPointError("could not load entry point; {}".format(exception)),
86-
exception,
102+
if self.load_extern:
103+
entry_point = next(
104+
pkg_resources.iter_entry_points("fs.opener", protocol), None
87105
)
88106
else:
107+
entry_point = None
108+
109+
# If not entry point was loaded from the extensions, try looking
110+
# into the registered protocols
111+
if entry_point is None:
112+
if protocol in self._protocols:
113+
opener_instance = self._protocols[protocol]
114+
else:
115+
raise UnsupportedProtocol(
116+
"protocol '{}' is not supported".format(protocol)
117+
)
118+
119+
# If an entry point was found in an extension, attempt to load it
120+
else:
121+
try:
122+
opener = entry_point.load()
123+
except Exception as exception:
124+
raise EntryPointError(
125+
"could not load entry point; {}".format(exception)
126+
)
89127
if not issubclass(opener, Opener):
90128
raise EntryPointError("entry point did not return an opener")
91129

92-
try:
93-
opener_instance = opener()
94-
except Exception as exception:
95-
six.raise_from(
96-
EntryPointError("could not instantiate opener; {}".format(exception)),
97-
exception,
98-
)
130+
try:
131+
opener_instance = opener()
132+
except Exception as exception:
133+
raise EntryPointError(
134+
"could not instantiate opener; {}".format(exception)
135+
)
99136

100137
return opener_instance
101138

@@ -234,4 +271,4 @@ def manage_fs(
234271
_fs.close()
235272

236273

237-
registry = Registry()
274+
registry = Registry(load_extern=True)

fs/opener/tarfs.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import typing
1010

1111
from .base import Opener
12+
from .registry import registry
1213
from .errors import NotWriteable
1314

1415
if False: # typing.TYPE_CHECKING
@@ -17,6 +18,7 @@
1718
from ..tarfs import TarFS
1819

1920

21+
@registry.install
2022
class TarOpener(Opener):
2123
"""`TarFS` opener.
2224
"""

fs/opener/tempfs.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,15 @@
99
import typing
1010

1111
from .base import Opener
12+
from .registry import registry
1213

1314
if False: # typing.TYPE_CHECKING
1415
from typing import Text
1516
from .parse import ParseResult
1617
from ..tempfs import TempFS
1718

1819

20+
@registry.install
1921
class TempOpener(Opener):
2022
"""`TempFS` opener.
2123
"""

fs/opener/zipfs.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import typing
1010

1111
from .base import Opener
12+
from .registry import registry
1213
from .errors import NotWriteable
1314

1415
if False: # typing.TYPE_CHECKING
@@ -17,6 +18,7 @@
1718
from ..zipfs import ZipFS
1819

1920

21+
@registry.install
2022
class ZipOpener(Opener):
2123
"""`ZipFS` opener.
2224
"""

setup.py

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -39,22 +39,6 @@
3939
":python_version < '3.6'": ['typing~=3.6'],
4040
":python_version < '3.0'": ['backports.os~=0.1']
4141
},
42-
entry_points={'fs.opener': [
43-
'ftp = fs.opener.ftpfs:FTPOpener',
44-
'file = fs.opener.osfs:OSFSOpener',
45-
'osfs = fs.opener.osfs:OSFSOpener',
46-
'mem = fs.opener.memoryfs:MemOpener',
47-
'tar = fs.opener.tarfs:TarOpener',
48-
'temp = fs.opener.tempfs:TempOpener',
49-
'zip = fs.opener.zipfs:ZipOpener',
50-
51-
'userdata = fs.opener.appfs:AppFSOpener',
52-
'userconf = fs.opener.appfs:AppFSOpener',
53-
'sitedata = fs.opener.appfs:AppFSOpener',
54-
'siteconf = fs.opener.appfs:AppFSOpener',
55-
'usercache = fs.opener.appfs:AppFSOpener',
56-
'userlog = fs.opener.appfs:AppFSOpener',
57-
]},
5842
license="MIT",
5943
name='fs',
6044
packages=find_packages(exclude=("tests",)),

0 commit comments

Comments
 (0)