comparison env/lib/python3.9/site-packages/planemo/shed_lint.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 """Logic related to linting shed repositories."""
2 from __future__ import absolute_import
3
4 import os
5 import xml.etree.ElementTree as ET
6
7 import yaml
8 from galaxy.tool_util.lint import lint_tool_source_with
9 from galaxy.tool_util.linters.help import rst_invalid
10 from galaxy.util import unicodify
11
12 from planemo.io import info
13 from planemo.lint import (
14 handle_lint_complete,
15 lint_urls,
16 lint_xsd,
17 setup_lint,
18 )
19 from planemo.shed import (
20 CURRENT_CATEGORIES,
21 REPO_TYPE_SUITE,
22 REPO_TYPE_TOOL_DEP,
23 REPO_TYPE_UNRESTRICTED,
24 validate_repo_name,
25 validate_repo_owner,
26 )
27 from planemo.shed2tap import base
28 from planemo.tool_lint import (
29 build_tool_lint_args,
30 handle_tool_load_error,
31 )
32 from planemo.tools import yield_tool_sources
33 from planemo.xml import XSDS_PATH
34
35
36 TOOL_DEPENDENCIES_XSD = os.path.join(XSDS_PATH, "tool_dependencies.xsd")
37 REPO_DEPENDENCIES_XSD = os.path.join(XSDS_PATH, "repository_dependencies.xsd")
38
39
40 VALID_REPOSITORY_TYPES = [
41 REPO_TYPE_UNRESTRICTED,
42 REPO_TYPE_TOOL_DEP,
43 REPO_TYPE_SUITE,
44 ]
45
46 SHED_METADATA = [
47 "description",
48 "long_description",
49 "remote_repository_url",
50 "homepage_url",
51 "categories",
52 ]
53
54
55 def lint_repository(ctx, realized_repository, **kwds):
56 """Lint a realized shed repository.
57
58 See :mod:`planemo.shed` for details on constructing a realized
59 repository data structure.
60 """
61 failed = False
62 path = realized_repository.real_path
63 info("Linting repository %s" % path)
64 lint_args = build_tool_lint_args(ctx, **kwds)
65 lint_args, lint_ctx = setup_lint(ctx, lint_args=lint_args, **kwds)
66 lint_ctx.lint(
67 "lint_expansion",
68 lint_expansion,
69 realized_repository,
70 )
71 lint_ctx.lint(
72 "lint_expected_files",
73 lint_expected_files,
74 realized_repository,
75 )
76 lint_ctx.lint(
77 "lint_tool_dependencies_xsd",
78 lint_tool_dependencies_xsd,
79 realized_repository,
80 )
81 lint_ctx.lint(
82 "lint_tool_dependencies_sha256sum",
83 lint_tool_dependencies_sha256sum,
84 realized_repository,
85 )
86 lint_ctx.lint(
87 "lint_tool_dependencies_actions",
88 lint_tool_dependencies_actions,
89 realized_repository,
90 )
91 lint_ctx.lint(
92 "lint_repository_dependencies",
93 lint_repository_dependencies,
94 realized_repository,
95 )
96 lint_ctx.lint(
97 "lint_shed_yaml",
98 lint_shed_yaml,
99 realized_repository,
100 )
101 lint_ctx.lint(
102 "lint_readme",
103 lint_readme,
104 realized_repository,
105 )
106 if kwds["urls"]:
107 lint_ctx.lint(
108 "lint_urls",
109 lint_tool_dependencies_urls,
110 realized_repository,
111 )
112 if kwds["tools"]:
113 tools_failed = lint_repository_tools(ctx, realized_repository, lint_ctx, lint_args)
114 failed = failed or tools_failed
115 if kwds["ensure_metadata"]:
116 lint_ctx.lint(
117 "lint_shed_metadata",
118 lint_shed_metadata,
119 realized_repository,
120 )
121 return handle_lint_complete(lint_ctx, lint_args, failed=failed)
122
123
124 def lint_repository_tools(ctx, realized_repository, lint_ctx, lint_args):
125 path = realized_repository.path
126 for (tool_path, tool_source) in yield_tool_sources(ctx, path,
127 recursive=True):
128 original_path = tool_path.replace(path, realized_repository.real_path)
129 info("+Linting tool %s" % original_path)
130 if handle_tool_load_error(tool_path, tool_source):
131 return True
132 lint_tool_source_with(
133 lint_ctx,
134 tool_source,
135 extra_modules=lint_args["extra_modules"]
136 )
137
138
139 def lint_expansion(realized_repository, lint_ctx):
140 missing = realized_repository.missing
141 if missing:
142 msg = "Failed to expand inclusions %s" % missing
143 lint_ctx.warn(msg)
144 else:
145 lint_ctx.info("Included files all found.")
146
147
148 def lint_shed_metadata(realized_repository, lint_ctx):
149 found_all = True
150 for key in SHED_METADATA:
151 if key not in realized_repository.config:
152 found_all = False
153 lint_ctx.warn(
154 "Missing shed metadata field [%s] for repository" % key
155 )
156 if found_all:
157 lint_ctx.info(
158 "Found all shed metadata fields required for automated repository "
159 "creation and/or updates."
160 )
161
162
163 def lint_readme(realized_repository, lint_ctx):
164 path = realized_repository.real_path
165 readme_rst = os.path.join(path, "README.rst")
166 readme = os.path.join(path, "README")
167 readme_txt = os.path.join(path, "README.txt")
168
169 readme_found = False
170 for readme in [readme_rst, readme, readme_txt]:
171 if os.path.exists(readme):
172 readme_found = readme
173
174 readme_md = os.path.join(path, "README.md")
175 if not readme_found and os.path.exists(readme_md):
176 lint_ctx.warn("Tool Shed doesn't render markdown, "
177 "README.md is invalid readme.")
178 return
179
180 if not readme_found:
181 # TODO: filter on TYPE and make this a warning if
182 # unrestricted repository - need to update iuc standards
183 # first though.
184 lint_ctx.info("No README found skipping.")
185 return
186
187 if readme_found.endswith(".rst"):
188 with open(readme_found, "r") as fh:
189 readme_text = fh.read()
190 invalid_rst = rst_invalid(readme_text)
191 if invalid_rst:
192 template = "Invalid restructured text found in README [%s]."
193 msg = template % invalid_rst
194 lint_ctx.warn(msg)
195 return
196 lint_ctx.info("README found containing valid reStructuredText.")
197 else:
198 lint_ctx.info("README found containing plain text.")
199
200
201 def lint_tool_dependencies_urls(realized_repository, lint_ctx):
202 path = realized_repository.real_path
203 tool_dependencies = os.path.join(path, "tool_dependencies.xml")
204 if not os.path.exists(tool_dependencies):
205 lint_ctx.info("No tool_dependencies.xml, skipping.")
206 return
207
208 root = ET.parse(tool_dependencies).getroot()
209 lint_urls(root, lint_ctx)
210
211
212 def lint_tool_dependencies_sha256sum(realized_repository, lint_ctx):
213 tool_dependencies = os.path.join(realized_repository.real_path, "tool_dependencies.xml")
214 if not os.path.exists(tool_dependencies):
215 lint_ctx.info("No tool_dependencies.xml, skipping.")
216 return
217
218 root = ET.parse(tool_dependencies).getroot()
219
220 count = 0
221 for action in root.findall(".//action"):
222 assert action.tag == "action"
223 if action.attrib.get('type', '') not in ['download_by_url', 'download_file']:
224 continue
225 url = action.text.strip()
226 checksum = action.attrib.get('sha256sum', '')
227 if not checksum:
228 lint_ctx.warn("Missing checksum for %s" % url)
229 elif len(checksum) != 64 or not set("0123456789abcdef").issuperset(checksum.lower()):
230 lint_ctx.error("Invalid checksum %r for %s" % (checksum, url))
231 else:
232 # TODO - See planned --verify option to check it matches
233 # lint_ctx.info("SHA256 checkum listed for %s" % url)
234 count += 1
235 if count:
236 lint_ctx.info("Found %i download action(s) with SHA256 checksums" % count)
237
238
239 def lint_tool_dependencies_xsd(realized_repository, lint_ctx):
240 path = realized_repository.real_path
241 tool_dependencies = os.path.join(path, "tool_dependencies.xml")
242 if not os.path.exists(tool_dependencies):
243 lint_ctx.info("No tool_dependencies.xml, skipping.")
244 return
245 lint_xsd(lint_ctx, TOOL_DEPENDENCIES_XSD, tool_dependencies)
246
247
248 def lint_tool_dependencies_actions(realized_repository, lint_ctx):
249 path = realized_repository.real_path
250 tool_dependencies = os.path.join(path, "tool_dependencies.xml")
251 if not os.path.exists(tool_dependencies):
252 lint_ctx.info("No tool_dependencies.xml, skipping.")
253 return
254 try:
255 base.Dependencies(tool_dependencies)
256 lint_ctx.info("Parsed tool dependencies.")
257 except Exception as e:
258 import sys
259 import traceback
260 exc_type, exc_value, exc_traceback = sys.exc_info()
261 traceback.print_tb(exc_traceback, limit=1, file=sys.stdout)
262 traceback.print_exc()
263 template = "Problem parsing tool_dependenies.xml [%s]"
264 msg = template % unicodify(e)
265 lint_ctx.warn(msg)
266 return
267
268
269 def lint_expected_files(realized_repository, lint_ctx):
270 if realized_repository.is_package:
271 if not os.path.exists(realized_repository.tool_dependencies_path):
272 lint_ctx.warn("Package repository does not contain a "
273 "tool_dependencies.xml file.")
274
275 if realized_repository.is_suite:
276 if not os.path.exists(realized_repository.repo_dependencies_path):
277 lint_ctx.warn("Suite repository does not contain a "
278 "repository_dependencies.xml file.")
279
280
281 def lint_repository_dependencies(realized_repository, lint_ctx):
282 path = realized_repository.real_path
283 repo_dependencies = os.path.join(path, "repository_dependencies.xml")
284 if not os.path.exists(repo_dependencies):
285 lint_ctx.info("No repository_dependencies.xml, skipping.")
286 return
287 lint_xsd(lint_ctx, REPO_DEPENDENCIES_XSD, repo_dependencies)
288
289
290 def lint_shed_yaml(realized_repository, lint_ctx):
291 path = realized_repository.real_path
292 shed_yaml = os.path.join(path, ".shed.yml")
293 if not os.path.exists(shed_yaml):
294 lint_ctx.info("No .shed.yml file found, skipping.")
295 return
296 try:
297 with open(shed_yaml, "r") as fh:
298 yaml.safe_load(fh)
299 except Exception as e:
300 lint_ctx.warn("Failed to parse .shed.yml file [%s]" % unicodify(e))
301 return
302 lint_ctx.info(".shed.yml found and appears to be valid YAML.")
303 _lint_shed_contents(lint_ctx, realized_repository)
304
305
306 def _lint_shed_contents(lint_ctx, realized_repository):
307 config = realized_repository.config
308
309 def _lint_if_present(key, func, *args):
310 value = config.get(key, None)
311 if value is not None:
312 msg = func(value, *args)
313 if msg:
314 lint_ctx.warn(msg)
315
316 _lint_if_present("owner", validate_repo_owner)
317 _lint_if_present("name", validate_repo_name)
318 _lint_if_present("type", _validate_repo_type, config["name"])
319 _lint_if_present("categories", _validate_categories, realized_repository)
320
321
322 def _validate_repo_type(repo_type, name):
323 if repo_type not in VALID_REPOSITORY_TYPES:
324 return "Invalid repository type specified [%s]" % repo_type
325
326 is_dep = repo_type == "tool_dependency_definition"
327 is_suite = repo_type == "repository_suite_definition"
328 if is_dep and not name.startswith("package_"):
329 return ("Tool dependency definition repositories should have names "
330 "starting with package_")
331 if is_suite and not name.startswith("suite_"):
332 return ("Repository suite definition repositories should have names "
333 "starting with suite_")
334 if name.startswith("package_") or name.startswith("suite_"):
335 if repo_type == "unrestricted":
336 return ("Repository name indicated specialized repository type "
337 "but repository is listed as unrestricted.")
338
339
340 def _validate_categories(categories, realized_repository):
341 msg = None
342 if len(categories) == 0:
343 msg = "Repository should specify one or more categories."
344 else:
345 for category in categories:
346 unknown_categories = []
347 if category not in CURRENT_CATEGORIES:
348 unknown_categories.append(category)
349 if unknown_categories:
350 msg = "Categories [%s] unknown." % unknown_categories
351 if realized_repository.is_package:
352 if "Tool Dependency Packages" not in categories:
353 msg = ("Packages should be placed and should only be placed "
354 "in the category 'Tool Dependency Packages'.")
355
356 return msg
357
358
359 __all__ = (
360 "lint_repository",
361 )