11from contextlib import suppress
2+ from io import TextIOWrapper
23
34from . import abc
45
@@ -25,32 +26,119 @@ def __init__(self, spec):
2526 self .spec = spec
2627
2728 def get_resource_reader (self , name ):
28- return DegenerateFiles (self .spec )._native ()
29+ return CompatibilityFiles (self .spec )._native ()
2930
3031
31- class DegenerateFiles :
32+ def _io_wrapper (file , mode = 'r' , * args , ** kwargs ):
33+ if mode == 'r' :
34+ return TextIOWrapper (file , * args , ** kwargs )
35+ elif mode == 'rb' :
36+ return file
37+ raise ValueError (
38+ "Invalid mode value '{}', only 'r' and 'rb' are supported" .format (mode )
39+ )
40+
41+
42+ class CompatibilityFiles :
3243 """
3344 Adapter for an existing or non-existant resource reader
34- to provide a degenerate .files().
45+ to provide a compability .files().
3546 """
3647
37- class Path (abc .Traversable ):
48+ class SpecPath (abc .Traversable ):
49+ """
50+ Path tied to a module spec.
51+ Can be read and exposes the resource reader children.
52+ """
53+
54+ def __init__ (self , spec , reader ):
55+ self ._spec = spec
56+ self ._reader = reader
57+
58+ def iterdir (self ):
59+ if not self ._reader :
60+ return iter (())
61+ return iter (
62+ CompatibilityFiles .ChildPath (self ._reader , path )
63+ for path in self ._reader .contents ()
64+ )
65+
66+ def is_file (self ):
67+ return False
68+
69+ is_dir = is_file
70+
71+ def joinpath (self , other ):
72+ if not self ._reader :
73+ return CompatibilityFiles .OrphanPath (other )
74+ return CompatibilityFiles .ChildPath (self ._reader , other )
75+
76+ @property
77+ def name (self ):
78+ return self ._spec .name
79+
80+ def open (self , mode = 'r' , * args , ** kwargs ):
81+ return _io_wrapper (self ._reader .open_resource (None ), mode , * args , ** kwargs )
82+
83+ class ChildPath (abc .Traversable ):
84+ """
85+ Path tied to a resource reader child.
86+ Can be read but doesn't expose any meaningfull children.
87+ """
88+
89+ def __init__ (self , reader , name ):
90+ self ._reader = reader
91+ self ._name = name
92+
3893 def iterdir (self ):
3994 return iter (())
4095
96+ def is_file (self ):
97+ return self ._reader .is_resource (self .name )
98+
4199 def is_dir (self ):
100+ return not self .is_file ()
101+
102+ def joinpath (self , other ):
103+ return CompatibilityFiles .OrphanPath (self .name , other )
104+
105+ @property
106+ def name (self ):
107+ return self ._name
108+
109+ def open (self , mode = 'r' , * args , ** kwargs ):
110+ return _io_wrapper (
111+ self ._reader .open_resource (self .name ), mode , * args , ** kwargs
112+ )
113+
114+ class OrphanPath (abc .Traversable ):
115+ """
116+ Orphan path, not tied to a module spec or resource reader.
117+ Can't be read and doesn't expose any meaningful children.
118+ """
119+
120+ def __init__ (self , * path_parts ):
121+ if len (path_parts ) < 1 :
122+ raise ValueError ('Need at least one path part to construct a path' )
123+ self ._path = path_parts
124+
125+ def iterdir (self ):
126+ return iter (())
127+
128+ def is_file (self ):
42129 return False
43130
44- is_file = exists = is_dir # type: ignore
131+ is_dir = is_file
45132
46133 def joinpath (self , other ):
47- return DegenerateFiles . Path ( )
134+ return CompatibilityFiles . OrphanPath ( * self . _path , other )
48135
136+ @property
49137 def name (self ):
50- return ''
138+ return self . _path [ - 1 ]
51139
52- def open (self ):
53- raise ValueError ( )
140+ def open (self , mode = 'r' , * args , ** kwargs ):
141+ raise FileNotFoundError ( "Can't open orphan path" )
54142
55143 def __init__ (self , spec ):
56144 self .spec = spec
@@ -71,7 +159,7 @@ def __getattr__(self, attr):
71159 return getattr (self ._reader , attr )
72160
73161 def files (self ):
74- return DegenerateFiles . Path ( )
162+ return CompatibilityFiles . SpecPath ( self . spec , self . _reader )
75163
76164
77165def wrap_spec (package ):
0 commit comments