comparison env/lib/python3.9/site-packages/cwltool/pathmapper.py @ 0:4f3585e2f14b draft default tip

"planemo upload commit 60cee0fc7c0cda8592644e1aad72851dec82c959"
author shellac
date Mon, 22 Mar 2021 18:12:50 +0000
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:4f3585e2f14b
1 import collections
2 import logging
3 import os
4 import stat
5 import urllib
6 import uuid
7 from typing import Dict, Iterator, List, Optional, Tuple, cast
8
9 from schema_salad.exceptions import ValidationException
10 from schema_salad.ref_resolver import uri_file_path
11 from schema_salad.sourceline import SourceLine
12
13 from .loghandler import _logger
14 from .stdfsaccess import abspath
15 from .utils import CWLObjectType, convert_pathsep_to_unix, dedup, downloadHttpFile
16
17 MapperEnt = collections.namedtuple(
18 "MapperEnt", ["resolved", "target", "type", "staged"]
19 )
20
21
22 class PathMapper:
23 """
24 Mapping of files from relative path provided in the file to a tuple.
25
26 (absolute local path, absolute container path)
27
28 The tao of PathMapper:
29
30 The initializer takes a list of File and Directory objects, a base
31 directory (for resolving relative references) and a staging directory
32 (where the files are mapped to).
33
34 The purpose of the setup method is to determine where each File or
35 Directory should be placed on the target file system (relative to
36 stagedir).
37
38 If separatedirs=True, unrelated files will be isolated in their own
39 directories under stagedir. If separatedirs=False, files and directories
40 will all be placed in stagedir (with the possibility for name
41 collisions...)
42
43 The path map maps the "location" of the input Files and Directory objects
44 to a tuple (resolved, target, type). The "resolved" field is the "real"
45 path on the local file system (after resolving relative paths and
46 traversing symlinks). The "target" is the path on the target file system
47 (under stagedir). The type is the object type (one of File, Directory,
48 CreateFile, WritableFile, CreateWritableFile).
49
50 The latter three (CreateFile, WritableFile, CreateWritableFile) are used by
51 InitialWorkDirRequirement to indicate files that are generated on the fly
52 (CreateFile and CreateWritableFile, in this case "resolved" holds the file
53 contents instead of the path because they file doesn't exist) or copied
54 into the output directory so they can be opened for update ("r+" or "a")
55 (WritableFile and CreateWritableFile).
56
57 """
58
59 def __init__(
60 self,
61 referenced_files: List[CWLObjectType],
62 basedir: str,
63 stagedir: str,
64 separateDirs: bool = True,
65 ) -> None:
66 """Initialize the PathMapper."""
67 self._pathmap = {} # type: Dict[str, MapperEnt]
68 self.stagedir = stagedir
69 self.separateDirs = separateDirs
70 self.setup(dedup(referenced_files), basedir)
71
72 def visitlisting(
73 self,
74 listing: List[CWLObjectType],
75 stagedir: str,
76 basedir: str,
77 copy: bool = False,
78 staged: bool = False,
79 ) -> None:
80 for ld in listing:
81 self.visit(
82 ld,
83 stagedir,
84 basedir,
85 copy=cast(bool, ld.get("writable", copy)),
86 staged=staged,
87 )
88
89 def visit(
90 self,
91 obj: CWLObjectType,
92 stagedir: str,
93 basedir: str,
94 copy: bool = False,
95 staged: bool = False,
96 ) -> None:
97 stagedir = cast(Optional[str], obj.get("dirname")) or stagedir
98 tgt = convert_pathsep_to_unix(
99 os.path.join(
100 stagedir,
101 cast(str, obj["basename"]),
102 )
103 )
104 if obj["location"] in self._pathmap:
105 return
106 if obj["class"] == "Directory":
107 location = cast(str, obj["location"])
108 if location.startswith("file://"):
109 resolved = uri_file_path(location)
110 else:
111 resolved = location
112 self._pathmap[location] = MapperEnt(
113 resolved, tgt, "WritableDirectory" if copy else "Directory", staged
114 )
115 if location.startswith("file://"):
116 staged = False
117 self.visitlisting(
118 cast(List[CWLObjectType], obj.get("listing", [])),
119 tgt,
120 basedir,
121 copy=copy,
122 staged=staged,
123 )
124 elif obj["class"] == "File":
125 path = cast(str, obj["location"])
126 ab = abspath(path, basedir)
127 if "contents" in obj and path.startswith("_:"):
128 self._pathmap[path] = MapperEnt(
129 obj["contents"],
130 tgt,
131 "CreateWritableFile" if copy else "CreateFile",
132 staged,
133 )
134 else:
135 with SourceLine(
136 obj,
137 "location",
138 ValidationException,
139 _logger.isEnabledFor(logging.DEBUG),
140 ):
141 deref = ab
142 if urllib.parse.urlsplit(deref).scheme in ["http", "https"]:
143 deref = downloadHttpFile(path)
144 else:
145 # Dereference symbolic links
146 st = os.lstat(deref)
147 while stat.S_ISLNK(st.st_mode):
148 rl = os.readlink(deref)
149 deref = (
150 rl
151 if os.path.isabs(rl)
152 else os.path.join(os.path.dirname(deref), rl)
153 )
154 st = os.lstat(deref)
155
156 self._pathmap[path] = MapperEnt(
157 deref, tgt, "WritableFile" if copy else "File", staged
158 )
159 self.visitlisting(
160 cast(List[CWLObjectType], obj.get("secondaryFiles", [])),
161 stagedir,
162 basedir,
163 copy=copy,
164 staged=staged,
165 )
166
167 def setup(self, referenced_files: List[CWLObjectType], basedir: str) -> None:
168
169 # Go through each file and set the target to its own directory along
170 # with any secondary files.
171 stagedir = self.stagedir
172 for fob in referenced_files:
173 if self.separateDirs:
174 stagedir = os.path.join(self.stagedir, "stg%s" % uuid.uuid4())
175 self.visit(
176 fob,
177 stagedir,
178 basedir,
179 copy=cast(bool, fob.get("writable", False)),
180 staged=True,
181 )
182
183 def mapper(self, src: str) -> MapperEnt:
184 if "#" in src:
185 i = src.index("#")
186 p = self._pathmap[src[:i]]
187 return MapperEnt(p.resolved, p.target + src[i:], p.type, p.staged)
188 return self._pathmap[src]
189
190 def files(self) -> List[str]:
191 return list(self._pathmap.keys())
192
193 def items(self) -> List[Tuple[str, MapperEnt]]:
194 return list(self._pathmap.items())
195
196 def reversemap(
197 self,
198 target: str,
199 ) -> Optional[Tuple[str, str]]:
200 """Find the (source, resolved_path) for the given target, if any."""
201 for k, v in self._pathmap.items():
202 if v[1] == target:
203 return (k, v[0])
204 return None
205
206 def update(
207 self, key: str, resolved: str, target: str, ctype: str, stage: bool
208 ) -> MapperEnt:
209 m = MapperEnt(resolved, target, ctype, stage)
210 self._pathmap[key] = m
211 return m
212
213 def __contains__(self, key: str) -> bool:
214 """Test for the presence of the given relative path in this mapper."""
215 return key in self._pathmap
216
217 def __iter__(self) -> Iterator[MapperEnt]:
218 """Get iterator for the maps."""
219 return self._pathmap.values().__iter__()