66from __future__ import print_function
77from __future__ import unicode_literals
88
9+ import collections
910import contextlib
1011import typing
1112
1718from .parse import parse_fs_url
1819
1920if 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
2425class 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 )
0 commit comments