Merge commit '623a5e165ad79e15321b19b2a2afbf2f9224cc90~1' into release_22.01

This commit is contained in:
mvdbeek
2022-02-11 18:53:39 +01:00
56 changed files with 9079 additions and 7041 deletions
+1 -1
View File
@@ -5,4 +5,4 @@ set -e
flake8 --exclude $(paste -sd, .ci/flake8_ignorelist.txt) .
# Apply stricter rules for the directories shared with Pulsar
flake8 --ignore=D --max-line-length=150 lib/galaxy/jobs/runners/util/
flake8 --ignore=E203,D --max-line-length=150 lib/galaxy/jobs/runners/util/
+1
View File
@@ -43,3 +43,4 @@ jobs:
run: tox -e lint_docstring_include_list
- name: Run mypy checks
run: tox -e mypy
- uses: psf/black@stable
+14
View File
@@ -0,0 +1,14 @@
[settings]
extend_skip=doc/source/conf.py,lib/galaxy/util/jstree.py
force_alphabetical_sort_within_sections=true
# Override force_grid_wrap value from profile=black, but black is still happy
force_grid_wrap=2
# Same line length as for black
line_length=120
no_lines_before=LOCALFOLDER
profile=black
reverse_relative=true
skip_gitignore=true
# Make isort run faster by skipping database
skip_glob=database/*
src_paths=lib
+5 -1
View File
@@ -2,7 +2,7 @@
VENV?=.venv
# Source virtualenv to execute command (flake8, sphinx, twine, etc...)
IN_VENV=if [ -f "$(VENV)/bin/activate" ]; then . "$(VENV)/bin/activate"; fi;
RELEASE_CURR:=22.01
RELEASE_CURR:=22.05
RELEASE_UPSTREAM:=upstream
TARGET_BRANCH=$(RELEASE_UPSTREAM)/dev
CONFIG_MANAGE=$(IN_VENV) python lib/galaxy/config/config_manage.py
@@ -41,6 +41,10 @@ setup-venv:
diff-format:
$(IN_VENV) darker -r $(TARGET_BRANCH)
format:
$(IN_VENV) isort .
$(IN_VENV) black .
list-dependency-updates: setup-venv
$(IN_VENV) pip list --outdated --format=columns
File diff suppressed because it is too large Load Diff
+15 -14
View File
@@ -20,7 +20,7 @@ bdbag==1.6.3; (python_version >= "2.7" and python_full_version < "3.0.0") or (py
beaker==1.11.0
billiard==3.6.4.0; python_version >= "3.7"
bioblend==0.16.0; python_version >= "3.6"
black==22.1.0; python_full_version >= "3.6.2" and python_version >= "3.6"
black==22.1.0; python_full_version >= "3.6.2"
bleach==4.1.0; python_version >= "3.6"
boltons==21.0.0
boto==2.49.0
@@ -30,19 +30,19 @@ cached-property==1.5.2; python_version < "3.8" and python_version >= "3.7"
celery==5.2.3; python_version >= "3.7"
certifi==2021.10.8; python_version >= "3.7" and python_full_version < "3.0.0" and python_version < "4" or python_full_version >= "3.6.0" and python_version < "4" and python_version >= "3.7"
cffi==1.15.0
charset-normalizer==2.0.11; python_full_version >= "3.6.0" and python_version >= "3.6" and python_version < "4"
charset-normalizer==2.0.11; python_full_version >= "3.6.0" and python_version >= "3.7" and python_version < "4"
cheetah3==3.2.6.post1; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.4.0")
circus==0.17.1
click-didyoumean==0.3.0; python_full_version >= "3.6.2" and python_full_version < "4.0.0" and python_version >= "3.7"
click-plugins==1.1.1; python_version >= "3.7"
click-repl==0.2.0; python_version >= "3.7"
click==8.0.3; python_full_version >= "3.6.2" and python_full_version < "4.0.0" and python_version >= "3.7"
click==8.0.3; python_version >= "3.7" and python_full_version >= "3.6.2" and python_full_version < "4.0.0"
cloudauthz==0.6.0
cloudbridge==3.0.0
colorama==0.4.4; sys_platform == "win32" and python_version >= "3.7" and python_full_version >= "3.6.2" and platform_system == "Windows" and python_full_version < "4.0.0" and (python_version >= "2.7" and python_full_version < "3.0.0" and platform_system == "Windows" or python_full_version >= "3.5.0" and platform_system == "Windows") and (python_version >= "3.7" and python_full_version < "3.0.0" and sys_platform == "win32" or sys_platform == "win32" and python_version >= "3.7" and python_full_version >= "3.5.0") and (python_version >= "3.6" and python_full_version < "3.0.0" and sys_platform == "win32" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6") or sys_platform == "win32" and python_version >= "3.6" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6") and python_full_version >= "3.5.0")
coloredlogs==15.0.1; python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "4" or python_version >= "3.6" and python_version < "4" and python_full_version >= "3.5.0"
commonmark==0.9.1; python_full_version >= "3.6.2" and python_full_version < "4.0.0"
coverage==6.3; python_version >= "3.7"
coverage==6.3.1; python_version >= "3.7"
cryptography==36.0.1; python_version >= "3.7" and python_full_version < "3.0.0" and python_version < "4" or python_full_version >= "3.6.0" and python_version < "4" and python_version >= "3.7"
cwltest==2.2.20210901154959; python_version >= "3.6" and python_version < "4"
cwltool==3.1.20211107152837; python_version >= "3.6" and python_version < "4"
@@ -70,16 +70,17 @@ gunicorn==20.1.0; python_version >= "3.5"
gxformat2==0.15.0
h11==0.12.0; python_version >= "3.7" and python_version < "4.0" and python_full_version >= "3.6.1"
h5py==3.6.0; python_version >= "3.7"
httpcore==0.14.5; python_version >= "3.6"
httpcore==0.14.6; python_version >= "3.6"
httpx==0.22.0; python_version >= "3.6"
humanfriendly==10.0; python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "4" or python_version >= "3.6" and python_version < "4" and python_full_version >= "3.5.0"
idna==3.3
imagesize==1.3.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6"
importlib-metadata==4.10.1; python_version == "3.7" and (python_version >= "3.7" and python_full_version < "3.0.0" and python_version < "3.8" or python_full_version >= "3.6.0" and python_version < "3.8" and python_version >= "3.7") and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6")
importlib-metadata==4.10.1; python_version == "3.7" and (python_version >= "3.7" and python_full_version < "3.0.0" and python_version < "3.8" or python_full_version >= "3.6.0" and python_version < "3.8" and python_version >= "3.7") and python_full_version >= "3.6.2" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6")
importlib-resources==5.4.0; python_version >= "3.6"
iniconfig==1.1.1; python_version >= "3.7"
isa-rwval==0.10.10
isodate==0.6.1; python_version >= "3.7" and python_version < "4"
isort==5.10.1; python_full_version >= "3.6.1" and python_version < "4.0"
jinja2==3.0.3; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6"
jsonschema==4.4.0; python_version >= "3.7"
junit-xml==1.9; python_version >= "3.6" and python_version < "4"
@@ -91,7 +92,7 @@ mako==1.1.6; (python_version >= "2.7" and python_full_version < "3.0.0") or (pyt
markdown-it-reporter==0.0.2
markdown==3.3.6; python_version >= "3.6"
markupsafe==2.0.1; python_version >= "3.6"
mercurial==6.0.1
mercurial==6.0.2
mirakuru==2.4.1; python_version >= "3.7"
mistune==0.8.4; python_version >= "3.7" and python_version < "4"
mrcfile==1.3.0
@@ -112,7 +113,7 @@ paste==3.5.0
pastedeploy==2.1.1
pathspec==0.9.0; python_full_version >= "3.6.2" and python_version >= "3.6"
pbr==5.8.0; python_version >= "2.6"
platformdirs==2.4.1; python_full_version >= "3.6.2" and python_version >= "3.7"
platformdirs==2.4.1; python_version >= "3.7" and python_full_version >= "3.6.2"
pluggy==1.0.0; python_version >= "3.7"
port-for==0.6.1; python_version >= "3.7"
prettytable==3.0.0; python_version >= "3.7"
@@ -127,7 +128,7 @@ pycryptodome==3.14.0; (python_version >= "2.7" and python_full_version < "3.0.0"
pydantic==1.9.0; python_full_version >= "3.6.1"
pydot==1.4.2; python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "4" or python_version >= "3.6" and python_version < "4" and python_full_version >= "3.4.0"
pyeventsystem==0.1.0
pyfaidx==0.6.3.1
pyfaidx==0.6.4
pygithub==1.55; python_version >= "3.6"
pygments==2.11.2; python_full_version >= "3.6.2" and python_full_version < "4.0.0" and python_version >= "3.6" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6")
pyjwt==2.3.0; python_version >= "3.6"
@@ -151,7 +152,7 @@ pytest-pythonpath==0.7.3
pytest-shard==0.1.2; python_version >= "3.6"
pytest==6.2.5; python_version >= "3.6"
python-dateutil==2.8.2; python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "4" or python_version >= "3.6" and python_version < "4" and python_full_version >= "3.3.0"
python-irodsclient==1.1.0
python-irodsclient==1.1.1
python-jose==3.3.0
python-multipart==0.0.5
python3-openid==3.2.0; python_version >= "3.0"
@@ -166,7 +167,7 @@ repoze.lru==0.7
requests-oauthlib==1.3.1; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.4.0"
requests-toolbelt==0.9.1; python_version >= "3.6"
requests==2.27.1; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.6.0")
responses==0.17.0; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.5.0")
responses==0.18.0; python_version >= "3.7"
rfc3986==1.5.0; python_version >= "3.6"
rich==11.1.0; python_full_version >= "3.6.2" and python_full_version < "4.0.0"
routes==2.5.1
@@ -177,7 +178,7 @@ schema-salad==8.2.20220103095339; python_version >= "3.7" and python_version < "
selenium==4.1.0; python_version >= "3.7" and python_version < "4.0"
setuptools-scm==5.0.2; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version < "4"
shellescape==3.8.1; python_version >= "3.6" and python_version < "4"
six==1.16.0; python_version >= "3.7" and python_full_version < "3.0.0" and python_version < "4" or python_full_version >= "3.5.0" and python_version >= "3.7" and python_version < "4"
six==1.16.0; python_version >= "3.7" and python_full_version < "3.0.0" and python_version < "4" or python_full_version >= "3.3.0" and python_version >= "3.7" and python_version < "4"
sniffio==1.2.0; python_version >= "3.7" and python_full_version >= "3.6.2" and python_version < "4.0"
snowballstemmer==2.2.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6"
social-auth-core==4.0.3
@@ -206,7 +207,7 @@ testfixtures==6.18.3
tifffile==2021.11.2; python_version >= "3.7"
tinydb==4.6.1; python_version >= "3.6" and python_version < "4.0"
toml==0.10.2; python_version >= "3.7" and python_full_version < "3.0.0" or python_full_version >= "3.3.0" and python_version >= "3.7"
tomli==2.0.0; python_full_version >= "3.6.2" and python_version >= "3.7"
tomli==2.0.0; python_version >= "3.7" and python_full_version >= "3.6.2"
tornado==6.1; python_version >= "3.5"
tqdm==4.62.3; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.4.0"
trio-websocket==0.9.2; python_version >= "3.7" and python_version < "4.0"
@@ -230,5 +231,5 @@ whoosh==2.7.4
wrapt==1.13.3; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6"
wsproto==1.0.0; python_version >= "3.7" and python_version < "4.0" and python_full_version >= "3.6.1"
yacman==0.8.4
zipp==3.7.0; python_version == "3.7" and (python_version >= "3.7" and python_full_version < "3.0.0" and python_version < "3.8" or python_full_version >= "3.6.0" and python_version < "3.8" and python_version >= "3.7")
zipp==3.7.0; python_version == "3.7" and (python_version >= "3.7" and python_full_version < "3.0.0" and python_version < "3.8" or python_full_version >= "3.6.0" and python_version < "3.8" and python_version >= "3.7") and python_full_version >= "3.6.2"
zipstream-new==1.1.8
@@ -1,6 +1,5 @@
flake8
flake8-bugbear
flake8-import-order
mypy==0.910
types-bleach
types-boto
@@ -1,7 +1,6 @@
attrs==21.4.0
flake8==4.0.1
flake8-bugbear==22.1.11
flake8-import-order==0.18.1
importlib-metadata==4.2.0
mccabe==0.6.1
mypy==0.910
@@ -75,7 +75,7 @@ lxml==4.7.1; (python_version >= "2.7" and python_full_version < "3.0.0") or (pyt
mako==1.1.6; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.4.0")
markdown==3.3.6; python_version >= "3.6"
markupsafe==2.0.1; python_version >= "3.6"
mercurial==6.0.1
mercurial==6.0.2
mistune==0.8.4; python_version >= "3.7" and python_version < "4"
mrcfile==1.3.0
msgpack==1.0.3; python_version >= "3.7" and python_version < "4"
@@ -103,7 +103,7 @@ pycryptodome==3.14.0; (python_version >= "2.7" and python_full_version < "3.0.0"
pydantic==1.9.0; python_full_version >= "3.6.1"
pydot==1.4.2; python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "4" or python_version >= "3.6" and python_version < "4" and python_full_version >= "3.4.0"
pyeventsystem==0.1.0
pyfaidx==0.6.3.1
pyfaidx==0.6.4
pygments==2.11.2; python_full_version >= "3.6.2" and python_full_version < "4.0.0" and python_version >= "3.5"
pyjwt==2.3.0; python_version >= "3.6"
pykwalify==1.8.0
File diff suppressed because it is too large Load Diff
+91 -52
View File
@@ -22,7 +22,7 @@ try:
from pulsar.client.staging import COMMAND_VERSION_FILENAME
except ImportError:
# Package unit tests
COMMAND_VERSION_FILENAME = 'COMMAND_VERSION'
COMMAND_VERSION_FILENAME = "COMMAND_VERSION"
import galaxy.datatypes.registry
import galaxy.model.mapping
@@ -67,7 +67,7 @@ logging.basicConfig()
log = logging.getLogger(__name__)
MAX_STDIO_READ_BYTES = 100 * 10 ** 6 # 100 MB
MAX_STDIO_READ_BYTES = 100 * 10**6 # 100 MB
def set_validated_state(dataset_instance):
@@ -81,7 +81,9 @@ def set_validated_state(dataset_instance):
dataset_instance.metadata.__validated_state_message__ = datatype_validation.message
def set_meta_with_tool_provided(dataset_instance, file_dict, set_meta_kwds, datatypes_registry, max_metadata_value_size):
def set_meta_with_tool_provided(
dataset_instance, file_dict, set_meta_kwds, datatypes_registry, max_metadata_value_size
):
# This method is somewhat odd, in that we set the metadata attributes from tool,
# then call set_meta, then set metadata attributes from tool again.
# This is intentional due to interplay of overwrite kwd, the fact that some metadata
@@ -90,7 +92,9 @@ def set_meta_with_tool_provided(dataset_instance, file_dict, set_meta_kwds, data
extension = dataset_instance.extension
if extension == "_sniff_":
try:
extension = sniff.handle_uploaded_dataset_file(dataset_instance.dataset.external_filename, datatypes_registry)
extension = sniff.handle_uploaded_dataset_file(
dataset_instance.dataset.external_filename, datatypes_registry
)
# We need to both set the extension so it is available to set_meta
# and record it in the metadata so it can be reloaded on the server
# side and the model updated (see MetadataCollection.{from,to}_JSON_dict)
@@ -100,10 +104,10 @@ def set_meta_with_tool_provided(dataset_instance, file_dict, set_meta_kwds, data
except Exception:
log.exception("Problem sniffing datatype.")
for metadata_name, metadata_value in file_dict.get('metadata', {}).items():
for metadata_name, metadata_value in file_dict.get("metadata", {}).items():
setattr(dataset_instance.metadata, metadata_name, metadata_value)
dataset_instance.datatype.set_meta(dataset_instance, **set_meta_kwds)
for metadata_name, metadata_value in file_dict.get('metadata', {}).items():
for metadata_name, metadata_value in file_dict.get("metadata", {}).items():
setattr(dataset_instance.metadata, metadata_name, metadata_value)
if max_metadata_value_size:
@@ -153,7 +157,9 @@ def set_metadata_portable():
tool_provided_metadata = load_job_metadata(job_metadata, provided_metadata_style)
def set_meta(new_dataset_instance, file_dict):
set_meta_with_tool_provided(new_dataset_instance, file_dict, set_meta_kwds, datatypes_registry, max_metadata_value_size)
set_meta_with_tool_provided(
new_dataset_instance, file_dict, set_meta_kwds, datatypes_registry, max_metadata_value_size
)
try:
object_store = get_object_store(tool_job_working_directory=tool_job_working_directory)
@@ -178,29 +184,29 @@ def set_metadata_portable():
# TODO: constants...
locations = [
(outputs_directory, 'tool_'),
(tool_job_working_directory, ''),
(outputs_directory, ''), # # Pulsar style output directory? Was this ever used - did this ever work?
(outputs_directory, "tool_"),
(tool_job_working_directory, ""),
(outputs_directory, ""), # # Pulsar style output directory? Was this ever used - did this ever work?
]
for directory, prefix in locations:
if os.path.exists(os.path.join(directory, f"{prefix}stdout")):
with open(os.path.join(directory, f"{prefix}stdout"), 'rb') as f:
with open(os.path.join(directory, f"{prefix}stdout"), "rb") as f:
tool_stdout = f.read(MAX_STDIO_READ_BYTES)
with open(os.path.join(directory, f"{prefix}stderr"), 'rb') as f:
with open(os.path.join(directory, f"{prefix}stderr"), "rb") as f:
tool_stderr = f.read(MAX_STDIO_READ_BYTES)
break
else:
if os.path.exists(os.path.join(tool_job_working_directory, 'task_0')):
if os.path.exists(os.path.join(tool_job_working_directory, "task_0")):
# We have a task splitting job
tool_stdout = b''
tool_stderr = b''
paths = Path(tool_job_working_directory).glob('task_*')
tool_stdout = b""
tool_stderr = b""
paths = Path(tool_job_working_directory).glob("task_*")
for path in paths:
with open(path / 'outputs' / 'tool_stdout', 'rb') as f:
with open(path / "outputs" / "tool_stdout", "rb") as f:
task_stdout = f.read(MAX_STDIO_READ_BYTES)
if task_stdout:
tool_stdout = b"%s[%s stdout]\n%s\n" % (tool_stdout, path.name.encode(), task_stdout)
with open(path / 'outputs' / 'tool_stderr', 'rb') as f:
with open(path / "outputs" / "tool_stderr", "rb") as f:
task_stderr = f.read(MAX_STDIO_READ_BYTES)
if task_stderr:
tool_stderr = b"%s[%s stdout]\n%s\n" % (tool_stderr, path.name.encode(), task_stderr)
@@ -217,26 +223,34 @@ def set_metadata_portable():
exit_code_file = default_exit_code_file(".", job_id_tag)
tool_exit_code = read_exit_code_from(exit_code_file, job_id_tag)
check_output_detected_state, tool_stdout, tool_stderr, job_messages = check_output(stdio_regexes, stdio_exit_codes, tool_stdout, tool_stderr, tool_exit_code, job_id_tag)
check_output_detected_state, tool_stdout, tool_stderr, job_messages = check_output(
stdio_regexes, stdio_exit_codes, tool_stdout, tool_stderr, tool_exit_code, job_id_tag
)
if check_output_detected_state == DETECTED_JOB_STATE.OK and not tool_provided_metadata.has_failed_outputs():
final_job_state = Job.states.OK
else:
final_job_state = Job.states.ERROR
version_string_path = os.path.join('outputs', COMMAND_VERSION_FILENAME)
version_string_path = os.path.join("outputs", COMMAND_VERSION_FILENAME)
version_string = collect_shrinked_content_from_path(version_string_path)
expression_context = ExpressionContext(dict(stdout=tool_stdout[:255], stderr=tool_stderr[:255]))
# Load outputs.
export_store = store.DirectoryModelExportStore('metadata/outputs_populated', serialize_dataset_objects=True, for_edit=True, strip_metadata_files=False, serialize_jobs=True)
export_store = store.DirectoryModelExportStore(
"metadata/outputs_populated",
serialize_dataset_objects=True,
for_edit=True,
strip_metadata_files=False,
serialize_jobs=True,
)
try:
import_model_store = store.imported_store_for_metadata('metadata/outputs_new', object_store=object_store)
import_model_store = store.imported_store_for_metadata("metadata/outputs_new", object_store=object_store)
except AssertionError:
# Remove in 21.09, this should only happen for jobs that started on <= 20.09 and finish now
import_model_store = None
tool_script_file = os.path.join(tool_job_working_directory, 'tool_script.sh')
tool_script_file = os.path.join(tool_job_working_directory, "tool_script.sh")
job = None
if import_model_store and export_store:
job = next(iter(import_model_store.sa_session.objects[Job].values()))
@@ -257,12 +271,14 @@ def set_metadata_portable():
output_collections = {}
for name, output_collection in metadata_params["output_collections"].items():
# TODO: remove HistoryDatasetCollectionAssociation fallback on 22.01, model_class used to not be serialized prior to 21.09
model_class = output_collection.get('model_class', 'HistoryDatasetCollectionAssociation')
collection = import_model_store.sa_session.query(getattr(galaxy.model, model_class)).find(output_collection["id"])
model_class = output_collection.get("model_class", "HistoryDatasetCollectionAssociation")
collection = import_model_store.sa_session.query(getattr(galaxy.model, model_class)).find(
output_collection["id"]
)
output_collections[name] = collection
output_instances = {}
for name, output in metadata_params["outputs"].items():
klass = getattr(galaxy.model, output.get('model_class', 'HistoryDatasetAssociation'))
klass = getattr(galaxy.model, output.get("model_class", "HistoryDatasetAssociation"))
output_instances[name] = import_model_store.sa_session.query(klass).find(output["id"])
input_ext = json.loads(metadata_params["job_params"].get("__input_ext") or '"data"')
@@ -283,7 +299,7 @@ def set_metadata_portable():
with open(tool_script_file) as command_fh:
command_line_lines = []
for i, line in enumerate(command_fh):
if i == 0 and line.endswith('COMMAND_VERSION 2>&1;'):
if i == 0 and line.endswith("COMMAND_VERSION 2>&1;"):
# Don't record version command as part of command line
continue
command_line_lines.append(line)
@@ -295,16 +311,16 @@ def set_metadata_portable():
destination = unnamed_output_dict["destination"]
elements = unnamed_output_dict["elements"]
destination_type = destination["type"]
if destination_type == 'hdas':
if destination_type == "hdas":
for element in elements:
filename = element.get('filename')
object_id = element.get('object_id')
filename = element.get("filename")
object_id = element.get("object_id")
if filename and object_id:
unnamed_id_to_path[object_id] = os.path.join(job_context.job_working_directory, filename)
for output_name, output_dict in outputs.items():
dataset_instance_id = output_dict["id"]
klass = getattr(galaxy.model, output_dict.get('model_class', 'HistoryDatasetAssociation'))
klass = getattr(galaxy.model, output_dict.get("model_class", "HistoryDatasetAssociation"))
dataset = None
if import_model_store:
dataset = import_model_store.sa_session.query(klass).find(dataset_instance_id)
@@ -312,7 +328,8 @@ def set_metadata_portable():
# legacy check for jobs that started before 21.01, remove on 21.05
filename_in = os.path.join(f"metadata/metadata_in_{output_name}")
import pickle
dataset = pickle.load(open(filename_in, 'rb')) # load DatasetInstance
dataset = pickle.load(open(filename_in, "rb")) # load DatasetInstance
assert dataset is not None
filename_kwds = os.path.join(f"metadata/metadata_kwds_{output_name}")
@@ -324,15 +341,21 @@ def set_metadata_portable():
legacy_object_store_store_by = metadata_params.get("object_store_store_by", "id")
# Same block as below...
set_meta_kwds = stringify_dictionary_keys(json.load(open(filename_kwds))) # load kwds; need to ensure our keywords are not unicode
set_meta_kwds = stringify_dictionary_keys(
json.load(open(filename_kwds))
) # load kwds; need to ensure our keywords are not unicode
try:
external_filename = unnamed_id_to_path.get(dataset_instance_id, dataset_filename_override)
if not os.path.exists(external_filename):
matches = glob.glob(external_filename)
assert len(matches) == 1, f"More than one file matched by output glob '{external_filename}'"
external_filename = matches[0]
assert safe_contains(tool_job_working_directory, external_filename), f"Cannot collect output '{external_filename}' from outside of working directory"
created_from_basename = os.path.relpath(external_filename, os.path.join(tool_job_working_directory, 'working'))
assert safe_contains(
tool_job_working_directory, external_filename
), f"Cannot collect output '{external_filename}' from outside of working directory"
created_from_basename = os.path.relpath(
external_filename, os.path.join(tool_job_working_directory, "working")
)
dataset.dataset.created_from_basename = created_from_basename
# override filename if we're dealing with outputs to working directory and dataset is not linked to
link_data_only = metadata_params.get("link_data_only")
@@ -345,8 +368,8 @@ def set_metadata_portable():
files_path = os.path.abspath(os.path.join(tool_job_working_directory, "working", extra_files_dir_name))
dataset.dataset.external_extra_files_path = files_path
file_dict = tool_provided_metadata.get_dataset_meta(output_name, dataset.dataset.id, dataset.dataset.uuid)
if 'ext' in file_dict:
dataset.extension = file_dict['ext']
if "ext" in file_dict:
dataset.extension = file_dict["ext"]
# Metadata FileParameter types may not be writable on a cluster node, and are therefore temporarily substituted with MetadataTempFiles
override_metadata = json.load(open(override_metadata))
for metadata_name, metadata_file_override in override_metadata:
@@ -374,20 +397,20 @@ def set_metadata_portable():
context = ExpressionContext(meta, expression_context)
else:
context = expression_context
dataset.blurb = 'done'
dataset.peek = 'no peek'
dataset.info = (dataset.info or '')
if context['stdout'].strip():
dataset.blurb = "done"
dataset.peek = "no peek"
dataset.info = dataset.info or ""
if context["stdout"].strip():
# Ensure white space between entries
dataset.info = f"{dataset.info.rstrip()}\n{context['stdout'].strip()}"
if context['stderr'].strip():
if context["stderr"].strip():
# Ensure white space between entries
dataset.info = f"{dataset.info.rstrip()}\n{context['stderr'].strip()}"
dataset.tool_version = version_string
if 'uuid' in context:
dataset.dataset.uuid = context['uuid']
if "uuid" in context:
dataset.dataset.uuid = context["uuid"]
if not final_job_state == Job.states.ERROR:
line_count = context.get('line_count', None)
line_count = context.get("line_count", None)
try:
# Certain datatype's set_peek methods contain a line_count argument
dataset.set_peek(line_count=line_count)
@@ -406,9 +429,13 @@ def set_metadata_portable():
else:
dataset.metadata.to_JSON_dict(filename_out) # write out results of set_meta
json.dump((True, 'Metadata has been set successfully'), open(filename_results_code, 'wt+')) # setting metadata has succeeded
json.dump(
(True, "Metadata has been set successfully"), open(filename_results_code, "wt+")
) # setting metadata has succeeded
except Exception:
json.dump((False, traceback.format_exc()), open(filename_results_code, 'wt+')) # setting metadata has failed somehow
json.dump(
(False, traceback.format_exc()), open(filename_results_code, "wt+")
) # setting metadata has failed somehow
if export_store:
export_store._finalize()
@@ -423,10 +450,18 @@ def validate_and_load_datatypes_config(datatypes_config):
datatypes_config = "configs/registry.xml"
if not os.path.exists(datatypes_config):
print(f"Metadata setting failed because registry.xml [{datatypes_config}] could not be found. You may retry setting metadata.")
print(
f"Metadata setting failed because registry.xml [{datatypes_config}] could not be found. You may retry setting metadata."
)
sys.exit(1)
datatypes_registry = galaxy.datatypes.registry.Registry()
datatypes_registry.load_datatypes(root_dir=galaxy_root, config=datatypes_config, use_build_sites=False, use_converters=False, use_display_applications=False)
datatypes_registry.load_datatypes(
root_dir=galaxy_root,
config=datatypes_config,
use_build_sites=False,
use_converters=False,
use_display_applications=False,
)
galaxy.model.set_datatypes_registry(datatypes_registry)
return datatypes_registry
@@ -440,12 +475,16 @@ def write_job_metadata(tool_job_working_directory, job_metadata, set_meta, tool_
filename = file_dict["filename"]
new_dataset_filename = os.path.join(tool_job_working_directory, "working", filename)
new_dataset = Dataset(id=-i, external_filename=new_dataset_filename)
extra_files = file_dict.get('extra_files', None)
extra_files = file_dict.get("extra_files", None)
if extra_files is not None:
new_dataset._extra_files_path = os.path.join(tool_job_working_directory, "working", extra_files)
new_dataset.state = new_dataset.states.OK
new_dataset_instance = HistoryDatasetAssociation(id=-i, dataset=new_dataset, extension=file_dict.get('ext', 'data'))
new_dataset_instance = HistoryDatasetAssociation(
id=-i, dataset=new_dataset, extension=file_dict.get("ext", "data")
)
set_meta(new_dataset_instance, file_dict)
file_dict['metadata'] = json.loads(new_dataset_instance.metadata.to_JSON_dict()) # storing metadata in external form, need to turn back into dict, then later jsonify
file_dict["metadata"] = json.loads(
new_dataset_instance.metadata.to_JSON_dict()
) # storing metadata in external form, need to turn back into dict, then later jsonify
tool_provided_metadata.rewrite()
+2728 -2202
View File
File diff suppressed because it is too large Load Diff
+27 -32
View File
@@ -16,12 +16,12 @@ from sqlalchemy.types import (
CHAR,
LargeBinary,
String,
TypeDecorator
TypeDecorator,
)
from galaxy.util import (
smart_str,
unicodify
unicodify,
)
from galaxy.util.aliaspickler import AliasPickleModule
@@ -52,9 +52,9 @@ def _sniffnfix_pg9_hex(value):
Sniff for and fix postgres 9 hex decoding issue
"""
try:
if value[0] == 'x':
if value[0] == "x":
return binascii.unhexlify(value[1:])
elif smart_str(value).startswith(b'\\x'):
elif smart_str(value).startswith(b"\\x"):
return binascii.unhexlify(value[2:])
else:
return value
@@ -72,10 +72,11 @@ class GalaxyLargeBinary(LargeBinary):
def process(value):
if value is not None:
if isinstance(value, str):
value = bytes(value, encoding='utf-8')
value = bytes(value, encoding="utf-8")
else:
value = bytes(value)
return value
return process
@@ -113,7 +114,7 @@ class JSONType(TypeDecorator):
return copy.deepcopy(value)
def compare_values(self, x, y):
return (x == y)
return x == y
class MutableJSONType(JSONType):
@@ -173,20 +174,20 @@ class MutationObj(Mutable):
def pickle(state, state_dict):
val = state.dict.get(key, None)
if isinstance(val, cls):
if 'ext.mutable.values' not in state_dict:
state_dict['ext.mutable.values'] = []
state_dict['ext.mutable.values'].append(val)
if "ext.mutable.values" not in state_dict:
state_dict["ext.mutable.values"] = []
state_dict["ext.mutable.values"].append(val)
def unpickle(state, state_dict):
if 'ext.mutable.values' in state_dict:
for val in state_dict['ext.mutable.values']:
if "ext.mutable.values" in state_dict:
for val in state_dict["ext.mutable.values"]:
val._parents[state] = key
sqlalchemy.event.listen(parent_cls, 'load', load, raw=True, propagate=True)
sqlalchemy.event.listen(parent_cls, 'refresh', load, raw=True, propagate=True)
sqlalchemy.event.listen(attribute, 'set', set, raw=True, retval=True, propagate=True)
sqlalchemy.event.listen(parent_cls, 'pickle', pickle, raw=True, propagate=True)
sqlalchemy.event.listen(parent_cls, 'unpickle', unpickle, raw=True, propagate=True)
sqlalchemy.event.listen(parent_cls, "load", load, raw=True, propagate=True)
sqlalchemy.event.listen(parent_cls, "refresh", load, raw=True, propagate=True)
sqlalchemy.event.listen(attribute, "set", set, raw=True, retval=True, propagate=True)
sqlalchemy.event.listen(parent_cls, "pickle", pickle, raw=True, propagate=True)
sqlalchemy.event.listen(parent_cls, "unpickle", unpickle, raw=True, propagate=True)
class MutationDict(MutationObj, dict):
@@ -279,13 +280,11 @@ class MutationList(MutationObj, list):
MutationObj.associate_with(MutableJSONType)
metadata_pickler = AliasPickleModule({
("cookbook.patterns", "Bunch"): ("galaxy.util.bunch", "Bunch")
})
metadata_pickler = AliasPickleModule({("cookbook.patterns", "Bunch"): ("galaxy.util.bunch", "Bunch")})
def total_size(o, handlers=None, verbose=False):
""" Returns the approximate memory footprint an object and all of its contents.
"""Returns the approximate memory footprint an object and all of its contents.
Automatically finds the contents of the following builtin containers and
their subclasses: tuple, list, deque, dict, set and frozenset.
@@ -301,18 +300,13 @@ def total_size(o, handlers=None, verbose=False):
def dict_handler(d):
return chain.from_iterable(d.items())
all_handlers = {tuple: iter,
list: iter,
deque: iter,
dict: dict_handler,
set: iter,
frozenset: iter}
all_handlers.update(handlers) # user handlers take precedence
seen = set() # track which object id's have already been seen
default_size = getsizeof(0) # estimate sizeof object without __sizeof__
all_handlers = {tuple: iter, list: iter, deque: iter, dict: dict_handler, set: iter, frozenset: iter}
all_handlers.update(handlers) # user handlers take precedence
seen = set() # track which object id's have already been seen
default_size = getsizeof(0) # estimate sizeof object without __sizeof__
def sizeof(o):
if id(o) in seen: # do not double count the same object
if id(o) in seen: # do not double count the same object
return 0
seen.add(id(o))
s = getsizeof(o, default_size)
@@ -339,7 +333,7 @@ class MetadataType(JSONType):
sz = total_size(v)
if sz > MAX_METADATA_VALUE_SIZE:
del value[k]
log.warning(f'Refusing to bind metadata key {k} due to size ({sz})')
log.warning(f"Refusing to bind metadata key {k} due to size ({sz})")
value = json_encoder.encode(value).encode()
return value
@@ -368,6 +362,7 @@ class UUIDType(TypeDecorator):
CHAR(32), storing as stringified hex values.
"""
impl = CHAR
cache_ok = True
@@ -396,5 +391,5 @@ class TrimmedString(TypeDecorator):
def process_bind_param(self, value, dialect):
"""Automatically truncate string values"""
if self.impl.length and value is not None:
value = unicodify(value)[0:self.impl.length]
value = unicodify(value)[0 : self.impl.length]
return value
+271 -131
View File
@@ -4,16 +4,30 @@ import os
import re
from abc import abstractmethod
from json import dumps
from typing import Any, cast, Dict, List, Set, Union
from typing import (
Any,
cast,
Dict,
List,
Set,
Union,
)
from galaxy import model
from galaxy.exceptions import ItemAccessibilityException
from galaxy.job_execution.actions.post import ActionBox
from galaxy.model import LibraryDatasetDatasetAssociation, WorkflowRequestInputParameter
from galaxy.model import (
LibraryDatasetDatasetAssociation,
WorkflowRequestInputParameter,
)
from galaxy.model.dataset_collections.builder import CollectionBuilder
from galaxy.model.none_like import NoneDataset
from galaxy.tools.parameters import update_dataset_ids
from galaxy.tools.parameters.basic import DataCollectionToolParameter, DataToolParameter, RuntimeValue
from galaxy.tools.parameters.basic import (
DataCollectionToolParameter,
DataToolParameter,
RuntimeValue,
)
from galaxy.tools.parameters.wrapped import WrappedParameters
from galaxy.util import ExecutionTimer
from galaxy.util.template import fill_template
@@ -23,7 +37,7 @@ log = logging.getLogger(__name__)
class ToolExecutionCache:
""" An object mean to cache calculation caused by repeatedly evaluting
"""An object mean to cache calculation caused by repeatedly evaluting
the same tool by the same user with slightly different parameters.
"""
@@ -35,11 +49,15 @@ class ToolExecutionCache:
def get_chrom_info(self, tool_id, input_dbkey):
genome_builds = self.trans.app.genome_builds
custom_build_hack_get_len_from_fasta_conversion = tool_id != 'CONVERTER_fasta_to_len'
custom_build_hack_get_len_from_fasta_conversion = tool_id != "CONVERTER_fasta_to_len"
if custom_build_hack_get_len_from_fasta_conversion and input_dbkey in self.chrom_info:
return self.chrom_info[input_dbkey]
chrom_info_pair = genome_builds.get_chrom_info(input_dbkey, trans=self.trans, custom_build_hack_get_len_from_fasta_conversion=custom_build_hack_get_len_from_fasta_conversion)
chrom_info_pair = genome_builds.get_chrom_info(
input_dbkey,
trans=self.trans,
custom_build_hack_get_len_from_fasta_conversion=custom_build_hack_get_len_from_fasta_conversion,
)
if custom_build_hack_get_len_from_fasta_conversion:
self.chrom_info[input_dbkey] = chrom_info_pair
@@ -59,9 +77,19 @@ class ToolAction:
class DefaultToolAction(ToolAction):
"""Default tool action is to run an external command"""
produces_real_jobs = True
def _collect_input_datasets(self, tool, param_values, trans, history, current_user_roles=None, dataset_collection_elements=None, collection_info=None):
def _collect_input_datasets(
self,
tool,
param_values,
trans,
history,
current_user_roles=None,
dataset_collection_elements=None,
collection_info=None,
):
"""
Collect any dataset inputs from incoming. Returns a mapping from
parameter name to Dataset instance for each tool parameter that is
@@ -78,7 +106,6 @@ class DefaultToolAction(ToolAction):
all_permissions[action].add(role_id)
def visitor(input, value, prefix, parent=None, **kwargs):
def process_dataset(data, formats=None):
if not data or isinstance(data, RuntimeValue):
return None
@@ -98,17 +125,22 @@ class DefaultToolAction(ToolAction):
if collection_info and collection_info.is_mapped_over(input_name):
action_tuples = collection_info.map_over_action_tuples(input_name)
if not trans.app.security_agent.can_access_datasets(current_user_roles, action_tuples):
raise ItemAccessibilityException("User does not have permission to use a dataset provided for input.")
raise ItemAccessibilityException(
"User does not have permission to use a dataset provided for input."
)
for action, role_id in action_tuples:
record_permission(action, role_id)
else:
if not trans.app.security_agent.can_access_dataset(current_user_roles, data.dataset):
raise ItemAccessibilityException(f"User does not have permission to use a dataset ({data.id}) provided for input.")
raise ItemAccessibilityException(
f"User does not have permission to use a dataset ({data.id}) provided for input."
)
permissions = trans.app.security_agent.get_permissions(data.dataset)
for action, roles in permissions.items():
for role in roles:
record_permission(action.action, model.cached_id(role))
return data
if isinstance(input, DataToolParameter):
if isinstance(value, list):
# If there are multiple inputs with the same name, they
@@ -121,22 +153,30 @@ class DefaultToolAction(ToolAction):
input_datasets[prefix + input.name + str(i + 1)] = processed_dataset
conversions = []
for conversion_name, conversion_extensions, conversion_datatypes in input.conversions:
new_data = process_dataset(input_datasets[prefix + input.name + str(i + 1)], conversion_datatypes)
new_data = process_dataset(
input_datasets[prefix + input.name + str(i + 1)], conversion_datatypes
)
if not new_data or new_data.datatype.matches_any(conversion_datatypes):
input_datasets[prefix + conversion_name + str(i + 1)] = new_data
conversions.append((conversion_name, new_data))
else:
raise Exception(f'A path for explicit datatype conversion has not been found: {input_datasets[prefix + input.name + str(i + 1)].extension} --/--> {conversion_extensions}')
raise Exception(
f"A path for explicit datatype conversion has not been found: {input_datasets[prefix + input.name + str(i + 1)].extension} --/--> {conversion_extensions}"
)
if parent:
parent[input.name][i] = input_datasets[prefix + input.name + str(i + 1)]
for conversion_name, conversion_data in conversions:
# allow explicit conversion to be stored in job_parameter table
parent[conversion_name][i] = conversion_data.id # a more robust way to determine JSONable value is desired
parent[conversion_name][
i
] = conversion_data.id # a more robust way to determine JSONable value is desired
else:
param_values[input.name][i] = input_datasets[prefix + input.name + str(i + 1)]
for conversion_name, conversion_data in conversions:
# allow explicit conversion to be stored in job_parameter table
param_values[conversion_name][i] = conversion_data.id # a more robust way to determine JSONable value is desired
param_values[conversion_name][
i
] = conversion_data.id # a more robust way to determine JSONable value is desired
else:
input_datasets[prefix + input.name] = process_dataset(value)
conversions = []
@@ -146,21 +186,25 @@ class DefaultToolAction(ToolAction):
input_datasets[prefix + conversion_name] = new_data
conversions.append((conversion_name, new_data))
else:
raise Exception(f'A path for explicit datatype conversion has not been found: {input_datasets[prefix + input.name].extension} --/--> {conversion_extensions}')
raise Exception(
f"A path for explicit datatype conversion has not been found: {input_datasets[prefix + input.name].extension} --/--> {conversion_extensions}"
)
target_dict = parent
if not target_dict:
target_dict = param_values
target_dict[input.name] = input_datasets[prefix + input.name]
for conversion_name, conversion_data in conversions:
# allow explicit conversion to be stored in job_parameter table
target_dict[conversion_name] = conversion_data.id # a more robust way to determine JSONable value is desired
target_dict[
conversion_name
] = conversion_data.id # a more robust way to determine JSONable value is desired
elif isinstance(input, DataCollectionToolParameter):
if not value:
return
collection = None
child_collection = False
if hasattr(value, 'child_collection'):
if hasattr(value, "child_collection"):
# if we are mapping a collection over a tool, we only require the child_collection
child_collection = True
collection = value.child_collection
@@ -170,7 +214,9 @@ class DefaultToolAction(ToolAction):
action_tuples = collection.dataset_action_tuples
if not trans.app.security_agent.can_access_datasets(current_user_roles, action_tuples):
raise ItemAccessibilityException("User does not have permission to use a dataset provided for input.")
raise ItemAccessibilityException(
"User does not have permission to use a dataset provided for input."
)
for action, role_id in action_tuples:
record_permission(action, role_id)
@@ -191,7 +237,11 @@ class DefaultToolAction(ToolAction):
processed_dataset_dict[v] = processed_dataset
input_datasets[prefix + input.name + str(i + 1)] = processed_dataset or v
if conversion_required:
collection_type_description = trans.app.dataset_collection_manager.collection_type_descriptions.for_collection_type(collection.collection_type)
collection_type_description = (
trans.app.dataset_collection_manager.collection_type_descriptions.for_collection_type(
collection.collection_type
)
)
collection_builder = CollectionBuilder(collection_type_description)
collection_builder.replace_elements_in_collection(
template_collection=collection,
@@ -220,7 +270,9 @@ class DefaultToolAction(ToolAction):
if not isinstance(values, list):
values = [value]
for i, value in enumerate(values):
if isinstance(value, model.HistoryDatasetCollectionAssociation) or isinstance(value, model.DatasetCollectionElement):
if isinstance(value, model.HistoryDatasetCollectionAssociation) or isinstance(
value, model.DatasetCollectionElement
):
append_to_key(input_dataset_collections, prefixed_name, (value, True))
target_dict = parent
if not target_dict:
@@ -248,7 +300,7 @@ class DefaultToolAction(ToolAction):
assert tool.allow_user_access(trans.user), f"User ({trans.user}) is not allowed to access this tool."
def _collect_inputs(self, tool, trans, incoming, history, current_user_roles, collection_info):
""" Collect history as well as input datasets and collections. """
"""Collect history as well as input datasets and collections."""
# Set history.
if not history:
history = tool.get_default_history_by_trans(trans, create=True)
@@ -257,7 +309,14 @@ class DefaultToolAction(ToolAction):
# input datasets can process these normally.
inp_dataset_collections = self.collect_input_dataset_collections(tool, incoming)
# Collect any input datasets from the incoming parameters
inp_data, all_permissions = self._collect_input_datasets(tool, incoming, trans, history=history, current_user_roles=current_user_roles, collection_info=collection_info)
inp_data, all_permissions = self._collect_input_datasets(
tool,
incoming,
trans,
history=history,
current_user_roles=current_user_roles,
collection_info=collection_info,
)
preserved_tags = {}
preserved_hdca_tags = {}
@@ -278,22 +337,23 @@ class DefaultToolAction(ToolAction):
preserved_tags.update(preserved_hdca_tags)
return history, inp_data, inp_dataset_collections, preserved_tags, preserved_hdca_tags, all_permissions
def execute(self,
tool,
trans,
incoming=None,
return_job=False,
set_output_hid=True,
history=None,
job_params=None,
rerun_remap_job_id=None,
execution_cache=None,
dataset_collection_elements=None,
completed_job=None,
collection_info=None,
job_callback=None,
flush_job=True
):
def execute(
self,
tool,
trans,
incoming=None,
return_job=False,
set_output_hid=True,
history=None,
job_params=None,
rerun_remap_job_id=None,
execution_cache=None,
dataset_collection_elements=None,
completed_job=None,
collection_info=None,
job_callback=None,
flush_job=True,
):
"""
Executes a tool, creating job and tool outputs, associating them, and
submitting the job to the job queue. If history is not specified, use
@@ -306,14 +366,21 @@ class DefaultToolAction(ToolAction):
if execution_cache is None:
execution_cache = ToolExecutionCache(trans)
current_user_roles = execution_cache.current_user_roles
history, inp_data, inp_dataset_collections, preserved_tags, preserved_hdca_tags, all_permissions = self._collect_inputs(tool, trans, incoming, history, current_user_roles, collection_info)
(
history,
inp_data,
inp_dataset_collections,
preserved_tags,
preserved_hdca_tags,
all_permissions,
) = self._collect_inputs(tool, trans, incoming, history, current_user_roles, collection_info)
# Build name for output datasets based on tool name and input names
on_text = self._get_on_text(inp_data)
# format='input" previously would give you a random extension from
# the input extensions, now it should just give "input" as the output
# format.
input_ext = 'data' if tool.profile < 16.04 else "input"
input_ext = "data" if tool.profile < 16.04 else "input"
input_dbkey = incoming.get("dbkey", "?")
for name, data in reversed(list(inp_data.items())):
if not data:
@@ -328,7 +395,7 @@ class DefaultToolAction(ToolAction):
if tool.profile < 16.04:
input_ext = data.ext
if data.dbkey not in [None, '?']:
if data.dbkey not in [None, "?"]:
input_dbkey = data.dbkey
identifier = getattr(data, "element_identifier", None)
@@ -377,7 +444,7 @@ class DefaultToolAction(ToolAction):
# datasets first, then create the associations
parent_to_child_pairs = []
child_dataset_names = set()
async_tool = tool.tool_type == 'data_source_async'
async_tool = tool.tool_type == "data_source_async"
def handle_output(name, output, hidden=None):
if output.parent:
@@ -410,7 +477,9 @@ class DefaultToolAction(ToolAction):
dataset = output_dataset.dataset.dataset
break
data = app.model.HistoryDatasetAssociation(extension=ext, dataset=dataset, create_dataset=create_datasets, flush=False)
data = app.model.HistoryDatasetAssociation(
extension=ext, dataset=dataset, create_dataset=create_datasets, flush=False
)
if create_datasets:
from_work_dir = output.from_work_dir
if from_work_dir is not None:
@@ -425,7 +494,9 @@ class DefaultToolAction(ToolAction):
dataset_collection_elements[name].hda = data
trans.sa_session.add(data)
if not completed_job:
trans.app.security_agent.set_all_dataset_permissions(data.dataset, output_permissions, new=True, flush=False)
trans.app.security_agent.set_all_dataset_permissions(
data.dataset, output_permissions, new=True, flush=False
)
data.copy_tags_to(preserved_tags.values())
# This may not be neccesary with the new parent/child associations
@@ -453,7 +524,9 @@ class DefaultToolAction(ToolAction):
else:
data.blurb = "queued"
# Set output label
data.name = self.get_output_name(output, data, tool, on_text, trans, incoming, history, wrapped_params.params, job_params)
data.name = self.get_output_name(
output, data, tool, on_text, trans, incoming, history, wrapped_params.params, job_params
)
# Store output
out_data[name] = data
if output.actions:
@@ -462,7 +535,9 @@ class DefaultToolAction(ToolAction):
output_action_params.update(incoming)
output.actions.apply_action(data, output_action_params)
# Also set the default values of actions of type metadata
self.set_metadata_defaults(output, data, tool, on_text, trans, incoming, history, wrapped_params.params, job_params)
self.set_metadata_defaults(
output, data, tool, on_text, trans, incoming, history, wrapped_params.params, job_params
)
# Flush all datasets at once.
return data
@@ -474,9 +549,7 @@ class DefaultToolAction(ToolAction):
# Output collection is mapped over and has already been copied from original job
continue
collections_manager = app.dataset_collection_manager
element_identifiers: List[
Dict[str, Union[str, List[Dict[str, Union[str, List[Any]]]]]]
] = []
element_identifiers: List[Dict[str, Union[str, List[Dict[str, Union[str, List[Any]]]]]]] = []
# mypy doesn't yet support recursive type definitions
known_outputs = output.known_outputs(input_collections, collections_manager.type_registry)
# Just to echo TODO elsewhere - this should be restructured to allow
@@ -486,33 +559,33 @@ class DefaultToolAction(ToolAction):
current_element_identifiers = element_identifiers
current_collection_type = output.structure.collection_type
for parent_id in (output_part_def.parent_ids or []):
for parent_id in output_part_def.parent_ids or []:
# TODO: replace following line with formal abstractions for doing this.
current_collection_type = ":".join(current_collection_type.split(":")[1:])
name_to_index = {value["name"]: index for (index, value) in enumerate(current_element_identifiers)}
name_to_index = {
value["name"]: index for (index, value) in enumerate(current_element_identifiers)
}
if parent_id not in name_to_index:
if parent_id not in current_element_identifiers:
index = len(current_element_identifiers)
current_element_identifiers.append(dict(
name=parent_id,
collection_type=current_collection_type,
src="new_collection",
element_identifiers=[],
))
current_element_identifiers.append(
dict(
name=parent_id,
collection_type=current_collection_type,
src="new_collection",
element_identifiers=[],
)
)
else:
index = name_to_index[parent_id]
current_element_identifiers = cast(
List[
Dict[
str,
Union[
str, List[Dict[str, Union[str, List[Any]]]]
],
Union[str, List[Dict[str, Union[str, List[Any]]]]],
]
],
current_element_identifiers[index][
"element_identifiers"
],
current_element_identifiers[index]["element_identifiers"],
)
effective_output_name = output_part_def.effective_output_name
@@ -524,10 +597,12 @@ class DefaultToolAction(ToolAction):
# Following hack causes dataset to no be added to history...
child_dataset_names.add(effective_output_name)
trans.sa_session.add(element)
current_element_identifiers.append({
"__object__": element,
"name": output_part_def.element_identifier,
})
current_element_identifiers.append(
{
"__object__": element,
"name": output_part_def.element_identifier,
}
)
if output.dynamic_structure:
assert not element_identifiers # known_outputs must have been empty
@@ -535,10 +610,7 @@ class DefaultToolAction(ToolAction):
else:
element_kwds = dict(element_identifiers=element_identifiers)
output_collections.create_collection(
output=output,
name=name,
completed_job=completed_job,
**element_kwds
output=output, name=name, completed_job=completed_job, **element_kwds
)
log.info(f"Handled collection output named {name} for tool {tool.id} {handle_output_timer}")
else:
@@ -546,12 +618,14 @@ class DefaultToolAction(ToolAction):
log.info(f"Handled output named {name} for tool {tool.id} {handle_output_timer}")
add_datasets_timer = tool.app.execution_timer_factory.get_timer(
'internals.galaxy.tools.actions.add_datasets',
'Added output datasets to history',
"internals.galaxy.tools.actions.add_datasets",
"Added output datasets to history",
)
# Add all the top-level (non-child) datasets to the history unless otherwise specified
for name, data in out_data.items():
if name not in child_dataset_names and name not in incoming: # don't add children; or already existing datasets, i.e. async created
if (
name not in child_dataset_names and name not in incoming
): # don't add children; or already existing datasets, i.e. async created
history.stage_addition(data)
history.add_pending_items(set_output_hid=set_output_hid)
@@ -585,25 +659,27 @@ class DefaultToolAction(ToolAction):
session.flush()
finally:
session.expire_on_commit = True
self._remap_job_on_rerun(trans=trans,
galaxy_session=galaxy_session,
rerun_remap_job_id=rerun_remap_job_id,
current_job=job,
out_data=out_data)
self._remap_job_on_rerun(
trans=trans,
galaxy_session=galaxy_session,
rerun_remap_job_id=rerun_remap_job_id,
current_job=job,
out_data=out_data,
)
log.info(f"Setup for job {job.log_str()} complete, ready to be enqueued {job_setup_timer}")
# Some tools are not really executable, but jobs are still created for them ( for record keeping ).
# Examples include tools that redirect to other applications ( epigraph ). These special tools must
# include something that can be retrieved from the params ( e.g., REDIRECT_URL ) to keep the job
# from being queued.
if 'REDIRECT_URL' in incoming:
if "REDIRECT_URL" in incoming:
# Get the dataset - there should only be 1
for name in inp_data.keys():
dataset = inp_data[name]
redirect_url = tool.parse_redirect_url(dataset, incoming)
# GALAXY_URL should be include in the tool params to enable the external application
# to send back to the current Galaxy instance
GALAXY_URL = incoming.get('GALAXY_URL', None)
GALAXY_URL = incoming.get("GALAXY_URL", None)
assert GALAXY_URL is not None, "GALAXY_URL parameter missing in tool config."
redirect_url += f"&GALAXY_URL={GALAXY_URL}"
# Job should not be queued, so set state to ok
@@ -611,7 +687,9 @@ class DefaultToolAction(ToolAction):
job.info = f"Redirected to: {redirect_url}"
trans.sa_session.add(job)
trans.sa_session.flush()
trans.response.send_redirect(url_for(controller='tool_runner', action='redirect', redirect_url=redirect_url))
trans.response.send_redirect(
url_for(controller="tool_runner", action="redirect", redirect_url=redirect_url)
)
else:
if flush_job:
# Set HID and add to history.
@@ -631,14 +709,20 @@ class DefaultToolAction(ToolAction):
"""
try:
old_job = trans.sa_session.query(trans.app.model.Job).get(rerun_remap_job_id)
assert old_job is not None, f'({rerun_remap_job_id}/{current_job.id}): Old job id is invalid'
assert old_job.tool_id == current_job.tool_id, f'({old_job.id}/{current_job.id}): Old tool id ({old_job.tool_id}) does not match rerun tool id ({current_job.tool_id})'
assert old_job is not None, f"({rerun_remap_job_id}/{current_job.id}): Old job id is invalid"
assert (
old_job.tool_id == current_job.tool_id
), f"({old_job.id}/{current_job.id}): Old tool id ({old_job.tool_id}) does not match rerun tool id ({current_job.tool_id})"
if trans.user is not None:
assert old_job.user_id == trans.user.id, f'({old_job.id}/{current_job.id}): Old user id ({old_job.user_id}) does not match rerun user id ({trans.user.id})'
assert (
old_job.user_id == trans.user.id
), f"({old_job.id}/{current_job.id}): Old user id ({old_job.user_id}) does not match rerun user id ({trans.user.id})"
elif trans.user is None and type(galaxy_session) == trans.model.GalaxySession:
assert old_job.session_id == galaxy_session.id, f'({old_job.id}/{current_job.id}): Old session id ({old_job.session_id}) does not match rerun session id ({galaxy_session.id})'
assert (
old_job.session_id == galaxy_session.id
), f"({old_job.id}/{current_job.id}): Old session id ({old_job.session_id}) does not match rerun session id ({galaxy_session.id})"
else:
raise Exception(f'({old_job.id}/{current_job.id}): Remapping via the API is not (yet) supported')
raise Exception(f"({old_job.id}/{current_job.id}): Remapping via the API is not (yet) supported")
# Start by hiding current job outputs before taking over the old job's (implicit) outputs.
current_job.hide_outputs(flush=False)
# Duplicate PJAs before remap.
@@ -654,13 +738,14 @@ class DefaultToolAction(ToolAction):
if pja.action_type in ActionBox.immediate_actions:
ActionBox.execute(trans.app, trans.sa_session, pja, current_job, replacement_dict)
for p in old_job.parameters:
if p.name.endswith('|__identifier__'):
if p.name.endswith("|__identifier__"):
current_job.parameters.append(p.copy())
remapped_hdas = self.__remap_data_inputs(old_job=old_job, current_job=current_job)
for jtod in old_job.output_datasets:
for (job_to_remap, jtid) in [(jtid.job, jtid) for jtid in jtod.dataset.dependent_jobs]:
if (trans.user is not None and job_to_remap.user_id == trans.user.id) or (
trans.user is None and job_to_remap.session_id == galaxy_session.id):
trans.user is None and job_to_remap.session_id == galaxy_session.id
):
self.__remap_parameters(job_to_remap, jtid, jtod, out_data)
trans.sa_session.add(job_to_remap)
trans.sa_session.add(jtid)
@@ -680,7 +765,7 @@ class DefaultToolAction(ToolAction):
for jtoidca in old_job.output_dataset_collections:
jtoidca.dataset_collection.replace_failed_elements(remapped_hdas)
except Exception:
log.exception('Cannot remap rerun dependencies.')
log.exception("Cannot remap rerun dependencies.")
def __remap_data_inputs(self, old_job, current_job):
"""Record output datasets from old_job and build a dictionary that maps the old output HDAs to the new output HDAs."""
@@ -694,13 +779,13 @@ class DefaultToolAction(ToolAction):
input_values = {p.name: json.loads(p.value) for p in job_to_remap.parameters if p.value is not None}
old_dataset_id = jtod.dataset_id
new_dataset_id = out_data[jtod.name].id
input_values = update_dataset_ids(input_values, {old_dataset_id: new_dataset_id}, src='hda')
input_values = update_dataset_ids(input_values, {old_dataset_id: new_dataset_id}, src="hda")
for p in job_to_remap.parameters:
if p.name in input_values:
p.value = json.dumps(input_values[p.name])
jtid.dataset = out_data[jtod.name]
jtid.dataset.hid = jtod.dataset.hid
log.info(f'Job {job_to_remap.id} input HDA {jtod.dataset.id} remapped to new HDA {jtid.dataset.id}')
log.info(f"Job {job_to_remap.id} input HDA {jtod.dataset.id} remapped to new HDA {jtid.dataset.id}")
def _wrapped_params(self, trans, tool, incoming, input_datasets=None):
wrapped_params = WrappedParameters(trans, tool, incoming, input_datasets=input_datasets)
@@ -710,7 +795,7 @@ class DefaultToolAction(ToolAction):
input_names = []
for data in reversed(list(inp_data.values())):
if getattr(data, "hid", None):
input_names.append(f'data {data.hid}')
input_names.append(f"data {data.hid}")
return on_text_for_names(input_names)
@@ -767,9 +852,9 @@ class DefaultToolAction(ToolAction):
target_dict[input.name] = []
for reduced_collection in reductions[prefixed_name]:
if hasattr(reduced_collection, "child_collection"):
target_dict[input.name].append({'id': model.cached_id(reduced_collection), 'src': 'dce'})
target_dict[input.name].append({"id": model.cached_id(reduced_collection), "src": "dce"})
else:
target_dict[input.name].append({'id': model.cached_id(reduced_collection), 'src': 'hdca'})
target_dict[input.name].append({"id": model.cached_id(reduced_collection), "src": "hdca"})
if reductions:
tool.visit_inputs(incoming, restore_reduction_visitor)
@@ -794,13 +879,33 @@ class DefaultToolAction(ToolAction):
# TODO: figure out why can't pass dataset_id here.
job.add_input_dataset(name, dataset=dataset)
def get_output_name(self, output, dataset=None, tool=None, on_text=None, trans=None, incoming=None, history=None, params=None, job_params=None):
def get_output_name(
self,
output,
dataset=None,
tool=None,
on_text=None,
trans=None,
incoming=None,
history=None,
params=None,
job_params=None,
):
if output.label:
params['tool'] = tool
params['on_string'] = on_text
params["tool"] = tool
params["on_string"] = on_text
return fill_template(output.label, context=params, python_template_version=tool.python_template_version)
else:
return self._get_default_data_name(dataset, tool, on_text=on_text, trans=trans, incoming=incoming, history=history, params=params, job_params=job_params)
return self._get_default_data_name(
dataset,
tool,
on_text=on_text,
trans=trans,
incoming=incoming,
history=history,
params=params,
job_params=job_params,
)
def set_metadata_defaults(self, output, dataset, tool, on_text, trans, incoming, history, params, job_params):
"""
@@ -817,10 +922,14 @@ class DefaultToolAction(ToolAction):
if output.actions:
for action in output.actions.actions:
if action.tag == "metadata" and action.default:
metadata_new_value = fill_template(action.default, context=params, python_template_version=tool.python_template_version).split(",")
metadata_new_value = fill_template(
action.default, context=params, python_template_version=tool.python_template_version
).split(",")
dataset.metadata.__setattr__(str(action.name), metadata_new_value)
def _get_default_data_name(self, dataset, tool, on_text=None, trans=None, incoming=None, history=None, params=None, job_params=None, **kwd):
def _get_default_data_name(
self, dataset, tool, on_text=None, trans=None, incoming=None, history=None, params=None, job_params=None, **kwd
):
name = tool.name
if on_text:
name += f" on {on_text}"
@@ -828,14 +937,28 @@ class DefaultToolAction(ToolAction):
class OutputCollections:
""" Keeps track of collections (DC or HDCA) created by actions.
"""Keeps track of collections (DC or HDCA) created by actions.
Actions do fairly different things depending on whether we are creating
just part of an collection or a whole output collection (mapping_over_collection
parameter).
"""
def __init__(self, trans, history, tool, tool_action, input_collections, dataset_collection_elements, on_text, incoming, params, job_params, tags, hdca_tags):
def __init__(
self,
trans,
history,
tool,
tool_action,
input_collections,
dataset_collection_elements,
on_text,
incoming,
params,
job_params,
tags,
hdca_tags,
):
self.trans = trans
self.tag_handler = trans.app.tag_handler.create_tag_handler_session()
self.history = history
@@ -852,7 +975,9 @@ class OutputCollections:
self.tags = tags # all inherited tags
self.hdca_tags = hdca_tags # only tags inherited from input HDCAs
def create_collection(self, output, name, collection_type=None, completed_job=None, propagate_hda_tags=True, **element_kwds):
def create_collection(
self, output, name, collection_type=None, completed_job=None, propagate_hda_tags=True, **element_kwds
):
input_collections = self.input_collections
collections_manager = self.trans.app.dataset_collection_manager
collection_type = collection_type or output.structure.collection_type
@@ -867,11 +992,11 @@ class OutputCollections:
# Using the collection_type_source string we get the DataCollectionToolParameter
data_param = self.tool.inputs
groups = collection_type_source.split('|')
groups = collection_type_source.split("|")
for group in groups:
values = group.split('_')
values = group.split("_")
if values[-1].isdigit():
key = ("_".join(values[0:-1]))
key = "_".join(values[0:-1])
# We don't care about the repeat index, we just need to find the correct DataCollectionToolParameter
else:
key = group
@@ -879,13 +1004,16 @@ class OutputCollections:
data_param = data_param.get(key)
else:
data_param = data_param.inputs.get(key)
collection_type_description = data_param._history_query(self.trans).can_map_over(input_collections[collection_type_source])
collection_type_description = data_param._history_query(self.trans).can_map_over(
input_collections[collection_type_source]
)
if collection_type_description:
collection_type = collection_type_description.collection_type
else:
collection_type = input_collections[collection_type_source].collection.collection_type
if "elements" in element_kwds:
def check_elements(elements):
if hasattr(elements, "items"): # else it is ELEMENTS_UNINITIALIZED object.
for value in elements.values():
@@ -904,9 +1032,7 @@ class OutputCollections:
if self.dataset_collection_elements is not None:
dc = collections_manager.create_dataset_collection(
self.trans,
collection_type=collection_type,
**element_kwds
self.trans, collection_type=collection_type, **element_kwds
)
if name in self.dataset_collection_elements:
self.dataset_collection_elements[name].child_collection = dc
@@ -935,7 +1061,7 @@ class OutputCollections:
flush=False,
completed_job=completed_job,
output_name=name,
**element_kwds
**element_kwds,
)
# name here is name of the output element - not name
# of the hdca.
@@ -957,11 +1083,11 @@ def on_text_for_names(input_names):
if len(input_names) == 1:
on_text = input_names[0]
elif len(input_names) == 2:
on_text = '%s and %s' % tuple(input_names[0:2])
on_text = "%s and %s" % tuple(input_names[0:2])
elif len(input_names) == 3:
on_text = '%s, %s, and %s' % tuple(input_names[0:3])
on_text = "%s, %s, and %s" % tuple(input_names[0:3])
elif len(input_names) > 3:
on_text = '%s, %s, and others' % tuple(input_names[0:2])
on_text = "%s, %s, and others" % tuple(input_names[0:2])
else:
on_text = ""
return on_text
@@ -973,7 +1099,7 @@ def filter_output(tool, output, incoming):
if not eval(filter.text.strip(), globals(), incoming):
return True # do not create this dataset
except Exception as e:
log.debug(f'Tool {tool.id} output {output.name}: dataset output filter ({filter.text}) failed: {e}')
log.debug(f"Tool {tool.id} output {output.name}: dataset output filter ({filter.text}) failed: {e}")
return False
@@ -987,8 +1113,16 @@ def get_ext_or_implicit_ext(hda):
return hda.ext
def determine_output_format(output, parameter_context, input_datasets, input_dataset_collections, random_input_ext, python_template_version='3', execution_cache=None):
""" Determines the output format for a dataset based on an abstract
def determine_output_format(
output,
parameter_context,
input_datasets,
input_dataset_collections,
random_input_ext,
python_template_version="3",
execution_cache=None,
):
"""Determines the output format for a dataset based on an abstract
description of the output (galaxy.tool_util.parser.ToolOutput), the parameter
wrappers, a map of the input datasets (name => HDA), and the last input
extensions in the tool form.
@@ -999,7 +1133,7 @@ def determine_output_format(output, parameter_context, input_datasets, input_dat
# the type should match the input
ext = output.format
if ext == "input":
if input_datasets and random_input_ext in {'data', 'auto'}:
if input_datasets and random_input_ext in {"data", "auto"}:
# Probably dealing with an implicitly converted dataset
try:
first_input_dataset = next(iter(input_datasets.values()))
@@ -1036,9 +1170,13 @@ def determine_output_format(output, parameter_context, input_datasets, input_dat
input_element = input_collection_collection[element_index]
except KeyError:
if execution_cache:
dataset_elements = execution_cache.cached_collection_elements.get(input_collection_collection.id)
dataset_elements = execution_cache.cached_collection_elements.get(
input_collection_collection.id
)
if dataset_elements is None:
dataset_elements = execution_cache.cached_collection_elements[input_collection_collection.id] = input_collection_collection.dataset_elements
dataset_elements = execution_cache.cached_collection_elements[
input_collection_collection.id
] = input_collection_collection.dataset_elements
else:
dataset_elements = input_collection_collection.dataset_elements
for element in dataset_elements:
@@ -1054,25 +1192,27 @@ def determine_output_format(output, parameter_context, input_datasets, input_dat
if output.change_format is not None:
new_format_set = False
for change_elem in output.change_format:
for when_elem in change_elem.findall('when'):
check = when_elem.get('input', None)
for when_elem in change_elem.findall("when"):
check = when_elem.get("input", None)
if check is not None:
try:
if '$' not in check:
if "$" not in check:
# allow a simple name or more complex specifications
check = '${%s}' % check
if fill_template(check, context=parameter_context, python_template_version=python_template_version) == when_elem.get('value', None):
ext = when_elem.get('format', ext)
check = "${%s}" % check
if fill_template(
check, context=parameter_context, python_template_version=python_template_version
) == when_elem.get("value", None):
ext = when_elem.get("format", ext)
except Exception: # bad tag input value; possibly referencing a param within a different conditional when block or other nonexistent grouping construct
continue
else:
check = when_elem.get('input_dataset', None)
check = when_elem.get("input_dataset", None)
if check is not None:
check = input_datasets.get(check, None)
# At this point check is a HistoryDatasetAssociation object.
check_format = when_elem.get('format', ext)
check_value = when_elem.get('value', None)
check_attribute = when_elem.get('attribute', None)
check_format = when_elem.get("format", ext)
check_value = when_elem.get("value", None)
check_attribute = when_elem.get("attribute", None)
if check is not None and check_value is not None and check_attribute is not None:
# See if the attribute to be checked belongs to the HistoryDatasetAssociation object.
if hasattr(check, check_attribute):
+105 -84
View File
@@ -5,7 +5,13 @@ import shlex
import string
import tempfile
from datetime import datetime
from typing import Any, Callable, Dict, List, Optional
from typing import (
Any,
Callable,
Dict,
List,
Optional,
)
from galaxy import model
from galaxy.job_execution.compute_environment import ComputeEnvironment
@@ -24,7 +30,7 @@ from galaxy.tools.parameters.basic import (
from galaxy.tools.parameters.grouping import (
Conditional,
Repeat,
Section
Section,
)
from galaxy.tools.wrappers import (
DatasetCollectionWrapper,
@@ -55,12 +61,9 @@ class ToolErrorLog:
self.max_errors = 100
def add_error(self, file, phase, exception):
self.error_stack.insert(0, {
"file": file,
"time": str(datetime.now()),
"phase": phase,
"error": unicodify(exception)
})
self.error_stack.insert(
0, {"file": file, "time": str(datetime.now()), "phase": phase, "error": unicodify(exception)}
)
if len(self.error_stack) > self.max_errors:
self.error_stack.pop()
@@ -78,7 +81,7 @@ def global_tool_logs(func, config_file, action_str):
class ToolEvaluator:
""" An abstraction linking together a tool and a job runtime to evaluate
"""An abstraction linking together a tool and a job runtime to evaluate
tool inputs in an isolated, testable manner.
"""
@@ -112,6 +115,7 @@ class ToolEvaluator:
def validate_inputs(input, value, context, **kwargs):
value = input.from_json(value, request_context, context)
input.validate(value, request_context)
visit_input_values(self.tool.inputs, incoming, validate_inputs)
# Restore input / output data lists
@@ -139,8 +143,9 @@ class ToolEvaluator:
# ( this used to be performed in the "exec_before_job" hook, but hooks are deprecated ).
self.tool.exec_before_job(self.app, inp_data, out_data, self.param_dict)
# Run the before queue ("exec_before_job") hook
self.tool.call_hook('exec_before_job', self.app, inp_data=inp_data,
out_data=out_data, tool=self.tool, param_dict=incoming)
self.tool.call_hook(
"exec_before_job", self.app, inp_data=inp_data, out_data=out_data, tool=self.tool, param_dict=incoming
)
def build_param_dict(self, incoming, input_datasets, output_datasets, output_collections):
"""
@@ -159,12 +164,14 @@ class ToolEvaluator:
raise SyntaxError("Unbound variable input.") # Don't let $input hang Python evaluation process.
param_dict["input"] = input
param_dict['__datatypes_config__'] = param_dict['GALAXY_DATATYPES_CONF_FILE'] = os.path.join(job_working_directory, 'registry.xml')
if self.job.tool_id == 'upload1':
param_dict['paramfile'] = os.path.join(job_working_directory, 'upload_params.json')
param_dict["__datatypes_config__"] = param_dict["GALAXY_DATATYPES_CONF_FILE"] = os.path.join(
job_working_directory, "registry.xml"
)
if self.job.tool_id == "upload1":
param_dict["paramfile"] = os.path.join(job_working_directory, "upload_params.json")
if self._history:
param_dict['__history_id__'] = self.app.security.encode_id(self._history.id)
param_dict['__galaxy_url__'] = self.compute_environment.galaxy_url()
param_dict["__history_id__"] = self.app.security.encode_id(self._history.id)
param_dict["__galaxy_url__"] = self.compute_environment.galaxy_url()
param_dict.update(self.tool.template_macro_params)
# All parameters go into the param_dict
param_dict.update(incoming)
@@ -183,7 +190,6 @@ class ToolEvaluator:
return param_dict
def __walk_inputs(self, inputs, input_values, func):
def do_walk(inputs, input_values):
"""
Wraps parameters as neccesary.
@@ -206,19 +212,19 @@ class ToolEvaluator:
do_walk(inputs, input_values)
def __populate_wrappers(self, param_dict, input_datasets, job_working_directory):
def wrap_input(input_values, input):
value = input_values[input.name]
if isinstance(input, DataToolParameter) and input.multiple:
dataset_instances = DatasetListWrapper.to_dataset_instances(value)
input_values[input.name] = \
DatasetListWrapper(job_working_directory,
dataset_instances,
compute_environment=self.compute_environment,
datatypes_registry=self.app.datatypes_registry,
tool=self.tool,
name=input.name,
formats=input.formats)
input_values[input.name] = DatasetListWrapper(
job_working_directory,
dataset_instances,
compute_environment=self.compute_environment,
datatypes_registry=self.app.datatypes_registry,
tool=self.tool,
name=input.name,
formats=input.formats,
)
elif isinstance(input, DataToolParameter):
dataset = input_values[input.name]
@@ -226,35 +232,30 @@ class ToolEvaluator:
datatypes_registry=self.app.datatypes_registry,
tool=self,
name=input.name,
compute_environment=self.compute_environment
compute_environment=self.compute_environment,
)
element_identifier = element_identifier_mapper.identifier(dataset, param_dict)
if element_identifier:
wrapper_kwds["identifier"] = element_identifier
input_values[input.name] = \
DatasetFilenameWrapper(dataset, **wrapper_kwds)
input_values[input.name] = DatasetFilenameWrapper(dataset, **wrapper_kwds)
elif isinstance(input, DataCollectionToolParameter):
dataset_collection = value
wrapper_kwds = dict(
datatypes_registry=self.app.datatypes_registry,
compute_environment=self.compute_environment,
tool=self,
name=input.name
)
wrapper = DatasetCollectionWrapper(
job_working_directory,
dataset_collection,
**wrapper_kwds
name=input.name,
)
wrapper = DatasetCollectionWrapper(job_working_directory, dataset_collection, **wrapper_kwds)
input_values[input.name] = wrapper
elif isinstance(input, SelectToolParameter):
if input.multiple:
value = listify(value)
input_values[input.name] = SelectToolParameterWrapper(
input, value, other_values=param_dict, compute_environment=self.compute_environment)
input, value, other_values=param_dict, compute_environment=self.compute_environment
)
else:
input_values[input.name] = InputValueWrapper(
input, value, param_dict)
input_values[input.name] = InputValueWrapper(input, value, param_dict)
# HACK: only wrap if check_values is not false, this deals with external
# tools where the inputs don't even get passed through. These
@@ -309,15 +310,11 @@ class ToolEvaluator:
wrapper_kwds = dict(
datatypes_registry=self.app.datatypes_registry,
compute_environment=self.compute_environment,
io_type='output',
io_type="output",
tool=tool,
name=name
)
wrapper = DatasetCollectionWrapper(
job_working_directory,
out_collection,
**wrapper_kwds
name=name,
)
wrapper = DatasetCollectionWrapper(job_working_directory, out_collection, **wrapper_kwds)
param_dict[name] = wrapper
# TODO: Handle nested collections...
output_def = tool.output_collections[name]
@@ -331,9 +328,11 @@ class ToolEvaluator:
for name, hda in output_datasets.items():
# Write outputs to the working directory (for security purposes)
# if desired.
param_dict[name] = DatasetFilenameWrapper(hda, compute_environment=self.compute_environment, io_type="output")
if '|__part__|' in name:
unqualified_name = name.split('|__part__|')[-1]
param_dict[name] = DatasetFilenameWrapper(
hda, compute_environment=self.compute_environment, io_type="output"
)
if "|__part__|" in name:
unqualified_name = name.split("|__part__|")[-1]
if unqualified_name not in param_dict:
param_dict[unqualified_name] = param_dict[name]
output_path = str(param_dict[name])
@@ -341,7 +340,7 @@ class ToolEvaluator:
# - may already exist (e.g. symlink output)
# - parent directory might not exist (e.g. Pulsar)
if not os.path.exists(output_path) and os.path.exists(os.path.dirname(output_path)):
open(output_path, 'w').close()
open(output_path, "w").close()
# Provide access to a path to store additional files
# TODO: move compute path logic into compute environment, move setting files_path
@@ -368,30 +367,33 @@ class ToolEvaluator:
if table_name in self.app.tool_data_tables:
return self.app.tool_data_tables[table_name].get_entry(query_attr, query_val, return_attr)
param_dict['__tool_directory__'] = self.compute_environment.tool_directory()
param_dict['__get_data_table_entry__'] = get_data_table_entry
param_dict['__local_working_directory__'] = self.local_working_directory
param_dict["__tool_directory__"] = self.compute_environment.tool_directory()
param_dict["__get_data_table_entry__"] = get_data_table_entry
param_dict["__local_working_directory__"] = self.local_working_directory
# We add access to app here, this allows access to app.config, etc
param_dict['__app__'] = RawObjectWrapper(self.app)
param_dict["__app__"] = RawObjectWrapper(self.app)
# More convienent access to app.config.new_file_path; we don't need to
# wrap a string, but this method of generating additional datasets
# should be considered DEPRECATED
param_dict['__new_file_path__'] = self.compute_environment.new_file_path()
param_dict["__new_file_path__"] = self.compute_environment.new_file_path()
# The following points to location (xxx.loc) files which are pointers
# to locally cached data
param_dict['__tool_data_path__'] = param_dict['GALAXY_DATA_INDEX_DIR'] = self.app.config.tool_data_path
param_dict["__tool_data_path__"] = param_dict["GALAXY_DATA_INDEX_DIR"] = self.app.config.tool_data_path
# For the upload tool, we need to know the root directory and the
# datatypes conf path, so we can load the datatypes registry
param_dict['__root_dir__'] = param_dict['GALAXY_ROOT_DIR'] = os.path.abspath(self.app.config.root)
param_dict['__admin_users__'] = self.app.config.admin_users
param_dict['__user__'] = RawObjectWrapper(param_dict.get('__user__', None))
param_dict["__root_dir__"] = param_dict["GALAXY_ROOT_DIR"] = os.path.abspath(self.app.config.root)
param_dict["__admin_users__"] = self.app.config.admin_users
param_dict["__user__"] = RawObjectWrapper(param_dict.get("__user__", None))
def __populate_unstructured_path_rewrites(self, param_dict):
def rewrite_unstructured_paths(input_values, input):
if isinstance(input, SelectToolParameter):
input_values[input.name] = SelectToolParameterWrapper(
input, input_values[input.name], other_values=param_dict, compute_environment=self.compute_environment)
input,
input_values[input.name],
other_values=param_dict,
compute_environment=self.compute_environment,
)
if not self.tool.check_values and self.compute_environment:
# The tools weren't "wrapped" yet, but need to be in order to get
@@ -403,16 +405,18 @@ class ToolEvaluator:
Populate InteractiveTools templated values.
"""
it = []
for ep in getattr(self.tool, 'ports', []):
for ep in getattr(self.tool, "ports", []):
ep_dict = {}
for key in 'port', 'name', 'url', 'requires_domain':
for key in "port", "name", "url", "requires_domain":
val = ep.get(key, None)
if val is not None and not isinstance(val, bool):
val = fill_template(val, context=self.param_dict, python_template_version=self.tool.python_template_version)
val = fill_template(
val, context=self.param_dict, python_template_version=self.tool.python_template_version
)
clean_val = []
for line in val.split('\n'):
for line in val.split("\n"):
clean_val.append(line.strip())
val = '\n'.join(clean_val)
val = "\n".join(clean_val)
val = val.replace("\n", " ").replace("\r", " ").strip()
ep_dict[key] = val
it.append(ep_dict)
@@ -427,14 +431,16 @@ class ToolEvaluator:
Note: this method follows the style of the similar populate calls, in that param_dict is modified in-place.
"""
# chromInfo is a filename, do not sanitize it.
skip = ['chromInfo'] + list(self.tool.template_macro_params.keys())
skip = ["chromInfo"] + list(self.tool.template_macro_params.keys())
if not self.tool or not self.tool.options or self.tool.options.sanitize:
for key, value in list(param_dict.items()):
if key not in skip:
# Remove key so that new wrapped object will occupy key slot
del param_dict[key]
# And replace with new wrapped key
param_dict[wrap_with_safe_string(key, no_wrap_classes=ToolParameterValueWrapper)] = wrap_with_safe_string(value, no_wrap_classes=ToolParameterValueWrapper)
param_dict[
wrap_with_safe_string(key, no_wrap_classes=ToolParameterValueWrapper)
] = wrap_with_safe_string(value, no_wrap_classes=ToolParameterValueWrapper)
def build(self):
"""
@@ -444,7 +450,7 @@ class ToolEvaluator:
"""
config_file = self.tool.config_file
global_tool_logs(self._build_config_files, config_file, "Building Config Files")
global_tool_logs(self._build_param_file, config_file, 'Building Param File')
global_tool_logs(self._build_param_file, config_file, "Building Param File")
global_tool_logs(self._build_command_line, config_file, "Building Command Line")
global_tool_logs(self._build_version_command, config_file, "Building Version Command Line")
global_tool_logs(self._build_environment_variables, config_file, "Building Environment Variables")
@@ -454,7 +460,7 @@ class ToolEvaluator:
"""
Build command line to invoke this tool given a populated param_dict
"""
command = self.tool.command or ''
command = self.tool.command or ""
param_dict = self.param_dict
interpreter = self.tool.interpreter
command_line = None
@@ -462,12 +468,14 @@ class ToolEvaluator:
return
try:
# Substituting parameters into the command
command_line = fill_template(command, context=param_dict, python_template_version=self.tool.python_template_version)
command_line = fill_template(
command, context=param_dict, python_template_version=self.tool.python_template_version
)
cleaned_command_line = []
# Remove leading and trailing whitespace from each line for readability.
for line in command_line.split('\n'):
for line in command_line.split("\n"):
cleaned_command_line.append(line.strip())
command_line = '\n'.join(cleaned_command_line)
command_line = "\n".join(cleaned_command_line)
# Remove newlines from command line, and any leading/trailing white space
command_line = command_line.replace("\n", " ").replace("\r", " ").strip()
except Exception:
@@ -486,7 +494,9 @@ class ToolEvaluator:
version_string_cmd_raw = self.tool.version_string_cmd
if version_string_cmd_raw:
version_command_template = string.Template(version_string_cmd_raw)
version_command = version_command_template.safe_substitute({"__tool_directory__": self.compute_environment.tool_directory()})
version_command = version_command_template.safe_substitute(
{"__tool_directory__": self.compute_environment.tool_directory()}
)
self.version_command_line = f"{version_command} > {self.compute_environment.version_path()} 2>&1;\n"
def _build_config_files(self):
@@ -522,6 +532,7 @@ class ToolEvaluator:
if inject == "api_key":
if self._user:
from galaxy.managers import api_keys
environment_variable_template = api_keys.ApiKeyManager(self.app).get_or_create_api_key(self._user)
else:
environment_variable_template = ""
@@ -530,10 +541,18 @@ class ToolEvaluator:
is_template = True
with tempfile.NamedTemporaryFile(dir=directory, prefix="tool_env_", delete=False) as temp:
config_filename = temp.name
self.__write_workdir_file(config_filename, environment_variable_template, param_dict, is_template=is_template, strip=environment_variable_def.get("strip", False))
self.__write_workdir_file(
config_filename,
environment_variable_template,
param_dict,
is_template=is_template,
strip=environment_variable_def.get("strip", False),
)
config_file_basename = os.path.basename(config_filename)
# environment setup in job file template happens before `cd $working_directory`
environment_variable["value"] = f'`cat "{self.compute_environment.env_config_directory()}/{config_file_basename}"`'
environment_variable[
"value"
] = f'`cat "{self.compute_environment.env_config_directory()}/{config_file_basename}"`'
environment_variable["raw"] = True
environment_variable["job_directory_path"] = config_filename
environment_variables.append(environment_variable)
@@ -556,14 +575,14 @@ class ToolEvaluator:
directory = self.local_working_directory
command = self.tool.command
if self.tool.profile < 16.04 and command and "$param_file" in command:
with tempfile.NamedTemporaryFile(mode='w', dir=directory, delete=False) as param:
with tempfile.NamedTemporaryFile(mode="w", dir=directory, delete=False) as param:
for key, value in param_dict.items():
# parameters can be strings or lists of strings, coerce to list
if not isinstance(value, list):
value = [value]
for elem in value:
param.write(f'{key}={elem}\n')
self.__register_extra_file('param_file', param.name)
param.write(f"{key}={elem}\n")
self.__register_extra_file("param_file", param.name)
return param.name
else:
return None
@@ -587,10 +606,12 @@ class ToolEvaluator:
else:
raise Exception(f"Unknown config file type {config_type}")
return json.dumps(wrapped_json.json_wrap(self.tool.inputs,
self.param_dict,
self.tool.profile,
handle_files=handle_files)), False
return (
json.dumps(
wrapped_json.json_wrap(self.tool.inputs, self.param_dict, self.tool.profile, handle_files=handle_files)
),
False,
)
def __write_workdir_file(self, config_filename, content, context, is_template=True, strip=False):
parent_dir = os.path.dirname(config_filename)
@@ -602,7 +623,7 @@ class ToolEvaluator:
value = unicodify(content)
if strip:
value = value.strip()
with open(config_filename, "w", encoding='utf-8') as f:
with open(config_filename, "w", encoding="utf-8") as f:
f.write(value)
# For running jobs as the actual user, ensure the config file is globally readable
os.chmod(config_filename, RW_R__R__)
@@ -658,7 +679,7 @@ class RemoteToolEvaluator(ToolEvaluator):
def build(self):
config_file = self.tool.config_file
global_tool_logs(self._build_config_files, config_file, "Building Config Files")
global_tool_logs(self._build_param_file, config_file, 'Building Param File')
global_tool_logs(self._build_param_file, config_file, "Building Param File")
global_tool_logs(self._build_command_line, config_file, "Building Command Line")
global_tool_logs(self._build_version_command, config_file, "Building Version Command Line")
return self.command_line, self.version_command_line, self.extra_filenames, self.environment_variables
File diff suppressed because it is too large Load Diff
+92 -60
View File
@@ -12,11 +12,11 @@ from galaxy.model import (
HistoryDatasetAssociation,
HistoryDatasetCollectionAssociation,
MetadataFile,
User
User,
)
from galaxy.tools.wrappers import (
DatasetFilenameWrapper,
DatasetListWrapper
DatasetListWrapper,
)
from galaxy.util import string_as_bool
from . import validation
@@ -28,10 +28,11 @@ class Filter:
"""
A filter takes the current options list and modifies it.
"""
@classmethod
def from_element(cls, d_option, elem):
"""Loads the proper filter by the type attribute of elem"""
type = elem.get('type', None)
type = elem.get("type", None)
assert type is not None, "Required 'type' attribute missing from filter"
return filter_types[type.strip()](d_option, elem)
@@ -69,7 +70,7 @@ class StaticValueFilter(Filter):
column = elem.get("column", None)
assert column is not None, "Required 'column' attribute missing from filter, when loading from file"
self.column = d_option.column_spec_to_index(column)
self.keep = string_as_bool(elem.get("keep", 'True'))
self.keep = string_as_bool(elem.get("keep", "True"))
def filter_options(self, options, trans, other_values):
rval = []
@@ -105,7 +106,7 @@ class RegexpFilter(Filter):
column = elem.get("column", None)
assert column is not None, "Required 'column' attribute missing from filter, when loading from file"
self.column = d_option.column_spec_to_index(column)
self.keep = string_as_bool(elem.get("keep", 'True'))
self.keep = string_as_bool(elem.get("keep", "True"))
def filter_options(self, options, trans, other_values):
rval = []
@@ -152,7 +153,9 @@ class DataMetaFilter(Filter):
assert self.key is not None, "Required 'key' attribute missing from filter"
self.column = elem.get("column", None)
if self.column is None:
assert self.dynamic_option.file_fields is None and self.dynamic_option.dataset_ref_name is None, "Required 'column' attribute missing from filter, when loading from file"
assert (
self.dynamic_option.file_fields is None and self.dynamic_option.dataset_ref_name is None
), "Required 'column' attribute missing from filter, when loading from file"
else:
self.column = d_option.column_spec_to_index(self.column)
self.multiple = string_as_bool(elem.get("multiple", "False"))
@@ -220,11 +223,7 @@ class DataMetaFilter(Filter):
return rval
else:
if not self.dynamic_option.columns:
self.dynamic_option.columns = {
"name": 0,
"value": 1,
"selected": 2
}
self.dynamic_option.columns = {"name": 0, "value": 1, "selected": 2}
self.dynamic_option.largest_index = 2
for value in meta_value:
options.append((value, value, False))
@@ -257,10 +256,10 @@ class ParamValueFilter(Filter):
column = elem.get("column", None)
assert column is not None, "Required 'column' attribute missing from filter"
self.column = d_option.column_spec_to_index(column)
self.keep = string_as_bool(elem.get("keep", 'True'))
self.keep = string_as_bool(elem.get("keep", "True"))
self.ref_attribute = elem.get("ref_attribute", None)
if self.ref_attribute:
self.ref_attribute = self.ref_attribute.split('.')
self.ref_attribute = self.ref_attribute.split(".")
else:
self.ref_attribute = []
@@ -353,7 +352,7 @@ class MultipleSplitterFilter(Filter):
for fields in options:
for column in self.columns:
for field in fields[column].split(self.separator):
rval.append(fields[0:column] + [field] + fields[column + 1:])
rval.append(fields[0:column] + [field] + fields[column + 1 :])
return rval
@@ -424,8 +423,8 @@ class AdditionalValueFilter(Filter):
add_value = []
for _ in range(self.dynamic_option.largest_index + 1):
add_value.append("")
value_col = self.dynamic_option.columns.get('value', 0)
name_col = self.dynamic_option.columns.get('name', value_col)
value_col = self.dynamic_option.columns.get("value", 0)
name_col = self.dynamic_option.columns.get("name", value_col)
# Set name first, then value, in case they are the same column
add_value[name_col] = self.name
add_value[value_col] = self.value
@@ -459,7 +458,11 @@ class RemoveValueFilter(Filter):
self.ref_name = elem.get("ref", None)
self.meta_ref = elem.get("meta_ref", None)
self.metadata_key = elem.get("key", None)
assert self.value is not None or self.ref_name is not None or (self.meta_ref is not None and self.metadata_key is not None), ValueError("Required 'value', or 'ref', or 'meta_ref' and 'key' attributes missing from filter")
assert (
self.value is not None
or self.ref_name is not None
or (self.meta_ref is not None and self.metadata_key is not None)
), ValueError("Required 'value', or 'ref', or 'meta_ref' and 'key' attributes missing from filter")
self.multiple = string_as_bool(elem.get("multiple", "False"))
self.separator = elem.get("separator", ",")
@@ -488,11 +491,13 @@ class RemoveValueFilter(Filter):
data_ref = other_values.get(self.meta_ref)
if isinstance(data_ref, HistoryDatasetCollectionAssociation):
data_ref = data_ref.to_hda_representative()
if not isinstance(data_ref, HistoryDatasetAssociation) and not isinstance(data_ref, DatasetFilenameWrapper):
if not isinstance(data_ref, HistoryDatasetAssociation) and not isinstance(
data_ref, DatasetFilenameWrapper
):
return options # cannot modify options
value = data_ref.metadata.get(self.metadata_key, None)
# Default to the second column (i.e. 1) since this used to work only on options produced by the data_meta filter
value_col = self.dynamic_option.columns.get('value', 1)
value_col = self.dynamic_option.columns.get("value", 1)
return [option for option in options if not compare_value(option[value_col], value)]
@@ -516,16 +521,18 @@ class SortByColumnFilter(Filter):
return sorted(options, key=lambda x: x[self.column])
filter_types = dict(data_meta=DataMetaFilter,
param_value=ParamValueFilter,
static_value=StaticValueFilter,
regexp=RegexpFilter,
unique_value=UniqueValueFilter,
multiple_splitter=MultipleSplitterFilter,
attribute_value_splitter=AttributeValueSplitterFilter,
add_value=AdditionalValueFilter,
remove_value=RemoveValueFilter,
sort_by=SortByColumnFilter)
filter_types = dict(
data_meta=DataMetaFilter,
param_value=ParamValueFilter,
static_value=StaticValueFilter,
regexp=RegexpFilter,
unique_value=UniqueValueFilter,
multiple_splitter=MultipleSplitterFilter,
attribute_value_splitter=AttributeValueSplitterFilter,
add_value=AdditionalValueFilter,
remove_value=RemoveValueFilter,
sort_by=SortByColumnFilter,
)
class DynamicOptions:
@@ -534,11 +541,12 @@ class DynamicOptions:
def __init__(self, elem, tool_param):
def load_from_parameter(from_parameter, transform_lines=None):
obj = self.tool_param
for field in from_parameter.split('.'):
for field in from_parameter.split("."):
obj = getattr(obj, field)
if transform_lines:
obj = eval(transform_lines, {'self': self, 'obj': obj})
obj = eval(transform_lines, {"self": self, "obj": obj})
return self.parse_file_fields(obj)
self.tool_param = tool_param
self.columns = {}
self.filters = []
@@ -552,14 +560,14 @@ class DynamicOptions:
self.converter_safe = True
# Parse the <options> tag
self.separator = elem.get('separator', '\t')
self.line_startswith = elem.get('startswith', None)
data_file = elem.get('from_file', None)
self.separator = elem.get("separator", "\t")
self.line_startswith = elem.get("startswith", None)
data_file = elem.get("from_file", None)
self.index_file = None
self.missing_index_file = None
dataset_file = elem.get('from_dataset', None)
from_parameter = elem.get('from_parameter', None)
self.tool_data_table_name = elem.get('from_data_table', None)
dataset_file = elem.get("from_dataset", None)
from_parameter = elem.get("from_parameter", None)
self.tool_data_table_name = elem.get("from_data_table", None)
# Options are defined from a data table loaded by the app
self._tool_data_table = None
self.elem = elem
@@ -568,7 +576,9 @@ class DynamicOptions:
# Options are defined by parsing tabular text data from a data file
# on disk, a dataset, or the value of another parameter
if not self.tool_data_table_name and (data_file is not None or dataset_file is not None or from_parameter is not None):
if not self.tool_data_table_name and (
data_file is not None or dataset_file is not None or from_parameter is not None
):
self.parse_column_definitions(elem)
if data_file is not None:
data_file = data_file.strip()
@@ -581,20 +591,20 @@ class DynamicOptions:
else:
self.missing_index_file = data_file
elif dataset_file is not None:
self.meta_file_key = elem.get('meta_file_key', None)
self.meta_file_key = elem.get("meta_file_key", None)
self.dataset_ref_name = dataset_file
self.has_dataset_dependencies = True
self.converter_safe = False
elif from_parameter is not None:
transform_lines = elem.get('transform_lines', None)
transform_lines = elem.get("transform_lines", None)
self.file_fields = list(load_from_parameter(from_parameter, transform_lines))
# Load filters
for filter_elem in elem.findall('filter'):
for filter_elem in elem.findall("filter"):
self.filters.append(Filter.from_element(self, filter_elem))
# Load Validators
for validator in elem.findall('validator'):
for validator in elem.findall("validator"):
self.validators.append(validation.Validator.from_element(self.tool_param, validator))
if self.dataset_ref_name:
@@ -628,24 +638,24 @@ class DynamicOptions:
return None
def parse_column_definitions(self, elem):
for column_elem in elem.findall('column'):
name = column_elem.get('name', None)
for column_elem in elem.findall("column"):
name = column_elem.get("name", None)
assert name is not None, "Required 'name' attribute missing from column def"
index = column_elem.get('index', None)
index = column_elem.get("index", None)
assert index is not None, "Required 'index' attribute missing from column def"
index = int(index)
self.columns[name] = index
if index > self.largest_index:
self.largest_index = index
assert 'value' in self.columns, "Required 'value' column missing from column def"
if 'name' not in self.columns:
self.columns['name'] = self.columns['value']
assert "value" in self.columns, "Required 'value' column missing from column def"
if "name" not in self.columns:
self.columns["name"] = self.columns["value"]
def parse_file_fields(self, reader):
rval = []
field_count = None
for line in reader:
if line.startswith('#') or (self.line_startswith and not line.startswith(self.line_startswith)):
if line.startswith("#") or (self.line_startswith and not line.startswith(self.line_startswith)):
continue
line = line.rstrip("\n\r")
if line:
@@ -659,8 +669,10 @@ class DynamicOptions:
except AttributeError:
name = "a configuration file"
# Perhaps this should be an error, but even a warning is useful.
log.warning("Inconsistent number of fields (%i vs %i) in %s using separator %r, check line: %r" %
(field_count, len(fields), name, self.separator, line))
log.warning(
"Inconsistent number of fields (%i vs %i) in %s using separator %r, check line: %r"
% (field_count, len(fields), name, self.separator, line)
)
rval.append(fields)
return rval
@@ -683,10 +695,14 @@ class DynamicOptions:
try:
datasets = _get_ref_data(other_values, self.dataset_ref_name)
except KeyError: # no such dataset
log.warning(f"Parameter {self.tool_param.name}: could not create dynamic options from_dataset: {self.dataset_ref_name} unknown")
log.warning(
f"Parameter {self.tool_param.name}: could not create dynamic options from_dataset: {self.dataset_ref_name} unknown"
)
return []
except ValueError: # not a valid dataset
log.warning(f"Parameter {self.tool_param.name}: could not create dynamic options from_dataset: {self.dataset_ref_name} not a data or collection parameter")
log.warning(
f"Parameter {self.tool_param.name}: could not create dynamic options from_dataset: {self.dataset_ref_name} not a data or collection parameter"
)
return []
options = []
@@ -695,12 +711,14 @@ class DynamicOptions:
if meta_file_key:
dataset = getattr(dataset.metadata, meta_file_key, None)
if not isinstance(dataset, MetadataFile):
log.warning(f"The meta_file_key `{meta_file_key}` was invalid or the referred object was not a valid file type metadata!")
log.warning(
f"The meta_file_key `{meta_file_key}` was invalid or the referred object was not a valid file type metadata!"
)
continue
if getattr(dataset, 'purged', False) or getattr(dataset, 'deleted', False):
if getattr(dataset, "purged", False) or getattr(dataset, "deleted", False):
log.warning(f"The metadata file inferred from key `{meta_file_key}` was deleted!")
continue
if not hasattr(dataset, 'file_name'):
if not hasattr(dataset, "file_name"):
continue
# Ensure parsing dynamic options does not consume more than a megabyte worth memory.
path = dataset.file_name
@@ -728,7 +746,7 @@ class DynamicOptions:
Return a list of fields with column 'value' matching provided value.
"""
rval = []
val_index = self.columns['value']
val_index = self.columns["value"]
for fields in self.get_fields(trans, other_values):
if fields[val_index] == value:
rval.append(fields)
@@ -753,10 +771,15 @@ class DynamicOptions:
def get_options(self, trans, other_values):
rval = []
if self.file_fields is not None or self.tool_data_table is not None or self.dataset_ref_name is not None or self.missing_index_file:
if (
self.file_fields is not None
or self.tool_data_table is not None
or self.dataset_ref_name is not None
or self.missing_index_file
):
options = self.get_fields(trans, other_values)
for fields in options:
rval.append((fields[self.columns['name']], fields[self.columns['value']], False))
rval.append((fields[self.columns["name"]], fields[self.columns["value"]], False))
else:
for filter in self.filters:
rval = filter.filter_options(rval, trans, other_values)
@@ -782,7 +805,16 @@ def _get_ref_data(other_values, ref_name):
- a ValueError is raised if the element is not of the type DatasetFilenameWrapper, HistoryDatasetAssociation, DatasetListWrapper, HistoryDatasetCollectionAssociation, list
"""
ref = other_values[ref_name]
if not isinstance(ref, (DatasetFilenameWrapper, HistoryDatasetAssociation, DatasetListWrapper, HistoryDatasetCollectionAssociation, list)):
if not isinstance(
ref,
(
DatasetFilenameWrapper,
HistoryDatasetAssociation,
DatasetListWrapper,
HistoryDatasetCollectionAssociation,
list,
),
):
raise ValueError
if isinstance(ref, (DatasetFilenameWrapper, HistoryDatasetAssociation)):
ref = [ref]
+17 -13
View File
@@ -45,7 +45,8 @@ class ToolAppConfig(NamedTuple):
class ToolApp(MinimalToolApp):
"""Dummy App that allows loading tools"""
name = 'tool_app'
name = "tool_app"
def __init__(
self,
@@ -70,22 +71,23 @@ def main(TMPDIR, WORKING_DIRECTORY, IMPORT_STORE_DIRECTORY):
metadata_params = get_metadata_params(WORKING_DIRECTORY)
datatypes_config = metadata_params["datatypes_config"]
if not os.path.exists(datatypes_config):
datatypes_config = os.path.join(WORKING_DIRECTORY, 'configs', datatypes_config)
datatypes_config = os.path.join(WORKING_DIRECTORY, "configs", datatypes_config)
datatypes_registry = validate_and_load_datatypes_config(datatypes_config)
object_store = get_object_store(WORKING_DIRECTORY)
import_store = store.imported_store_for_metadata(IMPORT_STORE_DIRECTORY)
# TODO: clean up random places from which we read files in the working directory
job_io = JobIO.from_json(os.path.join(IMPORT_STORE_DIRECTORY, 'job_io.json'), sa_session=import_store.sa_session)
job_io = JobIO.from_json(os.path.join(IMPORT_STORE_DIRECTORY, "job_io.json"), sa_session=import_store.sa_session)
tool_app_config = ToolAppConfig(
name='tool_app',
name="tool_app",
tool_data_path=job_io.tool_data_path,
galaxy_data_manager_data_path=job_io.galaxy_data_manager_data_path,
nginx_upload_path=TMPDIR,
len_file_path=job_io.len_file_path,
builds_file_path=job_io.builds_file_path,
root=TMPDIR,
is_admin_user=lambda _: job_io.user_context.is_admin)
with open(os.path.join(IMPORT_STORE_DIRECTORY, 'tool_data_tables.json')) as data_tables_json:
is_admin_user=lambda _: job_io.user_context.is_admin,
)
with open(os.path.join(IMPORT_STORE_DIRECTORY, "tool_data_tables.json")) as data_tables_json:
tdtm = ToolDataTableManager.from_dict(json.load(data_tables_json))
app = ToolApp(
sa_session=import_store.sa_session,
@@ -98,9 +100,11 @@ def main(TMPDIR, WORKING_DIRECTORY, IMPORT_STORE_DIRECTORY):
# TODO: could try to serialize just a minimal tool variant instead of the whole thing ?
tool_source = get_tool_source(tool_source_class=job_io.tool_source_class, raw_tool_source=job_io.tool_source)
tool = create_tool_from_source(app, tool_source=tool_source, tool_dir=job_io.tool_dir)
tool_evaluator = evaluation.RemoteToolEvaluator(app=app, tool=tool, job=job_io.job, local_working_directory=WORKING_DIRECTORY)
tool_evaluator = evaluation.RemoteToolEvaluator(
app=app, tool=tool, job=job_io.job, local_working_directory=WORKING_DIRECTORY
)
tool_evaluator.set_compute_environment(compute_environment=SharedComputeEnvironment(job_io=job_io, job=job_io.job))
with open(os.path.join(WORKING_DIRECTORY, 'tool_script.sh'), 'a') as out:
with open(os.path.join(WORKING_DIRECTORY, "tool_script.sh"), "a") as out:
command_line, version_command_line, extra_filenames, environment_variables = tool_evaluator.build()
out.write(f'{version_command_line or ""}{command_line}')
@@ -109,17 +113,17 @@ if __name__ == "__main__":
TMPDIR = tempfile.mkdtemp()
WORKING_DIRECTORY = os.getcwd()
WORKING_PARENT = os.path.join(WORKING_DIRECTORY, os.path.pardir)
if not os.path.isdir("working") and os.path.isdir(os.path.join(WORKING_PARENT, 'working')):
if not os.path.isdir("working") and os.path.isdir(os.path.join(WORKING_PARENT, "working")):
# We're probably in pulsar
WORKING_DIRECTORY = WORKING_PARENT
METADATA_DIRECTORY = os.path.join(WORKING_DIRECTORY, 'metadata')
IMPORT_STORE_DIRECTORY = os.path.join(METADATA_DIRECTORY, 'outputs_new')
EXPORT_STORE_DIRECTORY = os.path.join(METADATA_DIRECTORY, 'outputs_populated')
METADATA_DIRECTORY = os.path.join(WORKING_DIRECTORY, "metadata")
IMPORT_STORE_DIRECTORY = os.path.join(METADATA_DIRECTORY, "outputs_new")
EXPORT_STORE_DIRECTORY = os.path.join(METADATA_DIRECTORY, "outputs_populated")
try:
main(TMPDIR, WORKING_DIRECTORY, IMPORT_STORE_DIRECTORY)
except Exception:
os.makedirs(EXPORT_STORE_DIRECTORY, exist_ok=True)
with open(os.path.join(EXPORT_STORE_DIRECTORY, 'traceback.txt'), 'w') as out:
with open(os.path.join(EXPORT_STORE_DIRECTORY, "traceback.txt"), "w") as out:
out.write(traceback.format_exc())
raise
finally:
+233 -198
View File
@@ -38,24 +38,28 @@ from urllib.parse import (
)
import requests
from boltons.iterutils import (
default_enter,
remap,
)
from requests.adapters import HTTPAdapter
from requests.packages.urllib3.util.retry import Retry
try:
import grp
except ImportError:
# For Pulsar on Windows (which does not use the function that uses grp)
grp = None # type: ignore[assignment]
from boltons.iterutils import (
default_enter,
remap,
)
try:
import uwsgi
except ImportError:
uwsgi = None
LXML_AVAILABLE = True
try:
from lxml import etree
except ImportError:
LXML_AVAILABLE = False
import xml.etree.ElementTree as etree # type: ignore[assignment,no-redef]
from requests.adapters import HTTPAdapter
from requests.packages.urllib3.util.retry import Retry
try:
import docutils.core as docutils_core
import docutils.writers.html4css1 as docutils_html4css1
@@ -63,14 +67,13 @@ except ImportError:
docutils_core = None # type: ignore[assignment]
docutils_html4css1 = None # type: ignore[assignment]
try:
import uwsgi
except ImportError:
uwsgi = None
from .custom_logging import get_logger
from .inflection import Inflector
from .path import safe_contains, safe_makedirs, safe_relpath # noqa: F401
from .path import ( # noqa: F401
safe_contains,
safe_makedirs,
safe_relpath,
)
inflector = Inflector()
@@ -82,16 +85,16 @@ namedtuple = collections.namedtuple
CHUNK_SIZE = 65536 # 64k
DATABASE_MAX_STRING_SIZE = 32768
DATABASE_MAX_STRING_SIZE_PRETTY = '32K'
DATABASE_MAX_STRING_SIZE_PRETTY = "32K"
DEFAULT_SOCKET_TIMEOUT = 600
gzip_magic = b'\x1f\x8b'
bz2_magic = b'BZh'
DEFAULT_ENCODING = os.environ.get('GALAXY_DEFAULT_ENCODING', 'utf-8')
NULL_CHAR = b'\x00'
gzip_magic = b"\x1f\x8b"
bz2_magic = b"BZh"
DEFAULT_ENCODING = os.environ.get("GALAXY_DEFAULT_ENCODING", "utf-8")
NULL_CHAR = b"\x00"
BINARY_CHARS = [NULL_CHAR]
FILENAME_VALID_CHARS = '.,^_-()[]0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
FILENAME_VALID_CHARS = ".,^_-()[]0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
RW_R__R__ = stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH
RWXR_XR_X = stat.S_IRWXU | stat.S_IRGRP | stat.S_IXGRP | stat.S_IROTH | stat.S_IXOTH
@@ -109,23 +112,23 @@ def str_removeprefix(s: str, prefix: str):
if sys.version_info >= (3, 9):
return s.removeprefix(prefix)
if s.startswith(prefix):
return s[len(prefix):]
return s[len(prefix) :]
return s
def remove_protocol_from_url(url):
""" Supplied URL may be null, if not ensure http:// or https://
"""Supplied URL may be null, if not ensure http:// or https://
etc... is stripped off.
"""
if url is None:
return url
# We have a URL
if url.find('://') > 0:
new_url = url.split('://')[1]
if url.find("://") > 0:
new_url = url.split("://")[1]
else:
new_url = url
return new_url.rstrip('/')
return new_url.rstrip("/")
def is_binary(value):
@@ -184,16 +187,16 @@ def directory_hash_id(id):
# Drop the last three digits -- 1000 files per directory
padded = padded[:-3]
# Break into chunks of three
return [padded[i * 3:(i + 1) * 3] for i in range(len(padded) // 3)]
return [padded[i * 3 : (i + 1) * 3] for i in range(len(padded) // 3)]
else:
# assume it is a UUID
return list(iter(s[0:3]))
def get_charset_from_http_headers(headers, default=None):
rval = headers.get('content-type', None)
if rval and 'charset=' in rval:
rval = rval.split('charset=')[-1].split(';')[0].strip()
rval = headers.get("content-type", None)
if rval and "charset=" in rval:
rval = rval.split("charset=")[-1].split(";")[0].strip()
if rval:
return rval
return default
@@ -201,12 +204,14 @@ def get_charset_from_http_headers(headers, default=None):
def synchronized(func):
"""This wrapper will serialize access to 'func' to a single thread. Use it as a decorator."""
def caller(*params, **kparams):
_lock.acquire(True) # Wait
try:
return func(*params, **kparams)
finally:
_lock.release()
return caller
@@ -269,7 +274,7 @@ def parse_xml(fname, strip_whitespace=True, remove_comments=True):
tree = etree.parse(fname, parser=parser)
root = tree.getroot()
if strip_whitespace:
for elem in root.iter('*'):
for elem in root.iter("*"):
if elem.text is not None:
elem.text = elem.text.strip()
if elem.tail is not None:
@@ -289,12 +294,12 @@ def parse_xml_string(xml_string, strip_whitespace=True):
try:
tree = etree.fromstring(xml_string)
except ValueError as e:
if 'strings with encoding declaration are not supported' in unicodify(e):
tree = etree.fromstring(xml_string.encode('utf-8'))
if "strings with encoding declaration are not supported" in unicodify(e):
tree = etree.fromstring(xml_string.encode("utf-8"))
else:
raise e
if strip_whitespace:
for elem in tree.iter('*'):
for elem in tree.iter("*"):
if elem.text is not None:
elem.text = elem.text.strip()
if elem.tail is not None:
@@ -312,18 +317,18 @@ def xml_to_string(elem, pretty=False):
"""
try:
if elem is not None:
xml_str = etree.tostring(elem, encoding='unicode')
xml_str = etree.tostring(elem, encoding="unicode")
else:
xml_str = ''
xml_str = ""
except TypeError as e:
# we assume this is a comment
if hasattr(elem, 'text'):
if hasattr(elem, "text"):
return f"<!-- {elem.text} -->\n"
else:
raise e
if xml_str and pretty:
pretty_string = xml.dom.minidom.parseString(xml_str).toprettyxml(indent=' ')
return "\n".join(line for line in pretty_string.split('\n') if not re.match(r'^[\s\\nb\']*$', line))
pretty_string = xml.dom.minidom.parseString(xml_str).toprettyxml(indent=" ")
return "\n".join(line for line in pretty_string.split("\n") if not re.match(r"^[\s\\nb\']*$", line))
return xml_str
@@ -366,7 +371,7 @@ def xml_element_to_dict(elem):
if elem.text:
text = elem.text.strip()
if text and sub_elems or elem.attrib:
rval[elem.tag]['#text'] = text
rval[elem.tag]["#text"] = text
else:
rval[elem.tag] = text
@@ -374,7 +379,7 @@ def xml_element_to_dict(elem):
def pretty_print_xml(elem, level=0):
pad = ' '
pad = " "
i = "\n" + level * pad
if len(elem):
if not elem.text or not elem.text.strip():
@@ -412,14 +417,16 @@ def get_file_size(value, default=None):
return default
def shrink_stream_by_size(value, size, join_by=b"..", left_larger=True, beginning_on_size_error=False, end_on_size_error=False):
def shrink_stream_by_size(
value, size, join_by=b"..", left_larger=True, beginning_on_size_error=False, end_on_size_error=False
):
"""
Shrinks bytes read from `value` to `size`.
`value` needs to implement tell/seek, so files need to be opened in binary mode.
Returns unicode text with invalid characters replaced.
"""
rval = b''
rval = b""
join_by = smart_str(join_by)
if get_file_size(value) > size:
start = value.tell()
@@ -435,7 +442,9 @@ def shrink_stream_by_size(value, size, join_by=b"..", left_larger=True, beginnin
rval = value.read(size)
value.seek(start)
return rval
raise ValueError('With the provided join_by value (%s), the minimum size value is %i.' % (join_by, min_size))
raise ValueError(
"With the provided join_by value (%s), the minimum size value is %i." % (join_by, min_size)
)
left_index = right_index = int((size - len_join_by) / 2)
if left_index + right_index + len_join_by < size:
if left_larger:
@@ -455,17 +464,17 @@ def shrink_stream_by_size(value, size, join_by=b"..", left_larger=True, beginnin
def shrink_and_unicodify(stream):
stream = unicodify(stream, strip_null=True) or ''
if (len(stream) > DATABASE_MAX_STRING_SIZE):
stream = shrink_string_by_size(stream,
DATABASE_MAX_STRING_SIZE,
join_by="\n..\n",
left_larger=True,
beginning_on_size_error=True)
stream = unicodify(stream, strip_null=True) or ""
if len(stream) > DATABASE_MAX_STRING_SIZE:
stream = shrink_string_by_size(
stream, DATABASE_MAX_STRING_SIZE, join_by="\n..\n", left_larger=True, beginning_on_size_error=True
)
return stream
def shrink_string_by_size(value, size, join_by="..", left_larger=True, beginning_on_size_error=False, end_on_size_error=False):
def shrink_string_by_size(
value, size, join_by="..", left_larger=True, beginning_on_size_error=False, end_on_size_error=False
):
if len(value) > size:
len_join_by = len(join_by)
min_size = len_join_by + 2
@@ -474,7 +483,9 @@ def shrink_string_by_size(value, size, join_by="..", left_larger=True, beginning
return value[:size]
elif end_on_size_error:
return value[-size:]
raise ValueError('With the provided join_by value (%s), the minimum size value is %i.' % (join_by, min_size))
raise ValueError(
"With the provided join_by value (%s), the minimum size value is %i." % (join_by, min_size)
)
left_index = right_index = int((size - len_join_by) / 2)
if left_index + right_index + len_join_by < size:
if left_larger:
@@ -513,7 +524,7 @@ def pretty_print_time_interval(time=False, precise=False, utc=False):
day_diff = diff.days
if day_diff < 0:
return ''
return ""
if precise:
if day_diff == 0:
@@ -562,19 +573,21 @@ def pretty_print_json(json_data, is_json_string=False):
valid_chars = set(string.ascii_letters + string.digits + " -=_.()/+*^,:?!")
# characters that are allowed but need to be escaped
mapped_chars = {'>': '__gt__',
'<': '__lt__',
"'": '__sq__',
'"': '__dq__',
'[': '__ob__',
']': '__cb__',
'{': '__oc__',
'}': '__cc__',
'@': '__at__',
'\n': '__cn__',
'\r': '__cr__',
'\t': '__tc__',
'#': '__pd__'}
mapped_chars = {
">": "__gt__",
"<": "__lt__",
"'": "__sq__",
'"': "__dq__",
"[": "__ob__",
"]": "__cb__",
"{": "__oc__",
"}": "__cc__",
"@": "__at__",
"\n": "__cn__",
"\r": "__cr__",
"\t": "__tc__",
"#": "__pd__",
}
def restore_text(text, character_map=mapped_chars):
@@ -586,19 +599,24 @@ def restore_text(text, character_map=mapped_chars):
return text
def sanitize_text(text, valid_characters=valid_chars, character_map=mapped_chars, invalid_character='X'):
def sanitize_text(text, valid_characters=valid_chars, character_map=mapped_chars, invalid_character="X"):
"""
Restricts the characters that are allowed in text; accepts both strings
and lists of strings; non-string entities will be cast to strings.
"""
if isinstance(text, list):
return [sanitize_text(x, valid_characters=valid_characters, character_map=character_map, invalid_character=invalid_character) for x in text]
return [
sanitize_text(
x, valid_characters=valid_characters, character_map=character_map, invalid_character=invalid_character
)
for x in text
]
if not isinstance(text, str):
text = smart_str(text)
return _sanitize_text_helper(text, valid_characters=valid_characters, character_map=character_map)
def _sanitize_text_helper(text, valid_characters=valid_chars, character_map=mapped_chars, invalid_character='X'):
def _sanitize_text_helper(text, valid_characters=valid_chars, character_map=mapped_chars, invalid_character="X"):
"""Restricts the characters that are allowed in a string"""
out = []
@@ -609,35 +627,48 @@ def _sanitize_text_helper(text, valid_characters=valid_chars, character_map=mapp
out.append(character_map[c])
else:
out.append(invalid_character) # makes debugging easier
return ''.join(out)
return "".join(out)
def sanitize_lists_to_string(values, valid_characters=valid_chars, character_map=mapped_chars, invalid_character='X'):
def sanitize_lists_to_string(values, valid_characters=valid_chars, character_map=mapped_chars, invalid_character="X"):
if isinstance(values, list):
rval = []
for value in values:
rval.append(sanitize_lists_to_string(value,
valid_characters=valid_characters,
character_map=character_map,
invalid_character=invalid_character))
rval.append(
sanitize_lists_to_string(
value,
valid_characters=valid_characters,
character_map=character_map,
invalid_character=invalid_character,
)
)
values = ",".join(rval)
else:
values = sanitize_text(values, valid_characters=valid_characters, character_map=character_map, invalid_character=invalid_character)
values = sanitize_text(
values, valid_characters=valid_characters, character_map=character_map, invalid_character=invalid_character
)
return values
def sanitize_param(value, valid_characters=valid_chars, character_map=mapped_chars, invalid_character='X'):
def sanitize_param(value, valid_characters=valid_chars, character_map=mapped_chars, invalid_character="X"):
"""Clean incoming parameters (strings or lists)"""
if isinstance(value, str):
return sanitize_text(value, valid_characters=valid_characters, character_map=character_map, invalid_character=invalid_character)
return sanitize_text(
value, valid_characters=valid_characters, character_map=character_map, invalid_character=invalid_character
)
elif isinstance(value, list):
return [sanitize_text(x, valid_characters=valid_characters, character_map=character_map, invalid_character=invalid_character) for x in value]
return [
sanitize_text(
x, valid_characters=valid_characters, character_map=character_map, invalid_character=invalid_character
)
for x in value
]
else:
raise Exception(f'Unknown parameter type ({type(value)})')
raise Exception(f"Unknown parameter type ({type(value)})")
valid_filename_chars = set(string.ascii_letters + string.digits + '_.')
invalid_filenames = ['', '.', '..']
valid_filename_chars = set(string.ascii_letters + string.digits + "_.")
invalid_filenames = ["", ".", ".."]
def sanitize_for_filename(text, default=None):
@@ -650,8 +681,8 @@ def sanitize_for_filename(text, default=None):
if c in valid_filename_chars:
out.append(c)
else:
out.append('_')
out = ''.join(out)
out.append("_")
out = "".join(out)
if out in invalid_filenames:
if default is None:
return sanitize_for_filename(str(unique_id()))
@@ -705,13 +736,15 @@ def mask_password_from_url(url):
# This can manipulate the input other than just masking password,
# so the previous string replace method is preferred when the
# password doesn't appear twice in the url
split = split._replace(netloc=split.netloc.replace(f"{split.username}:{split.password}", f'{split.username}:********'))
split = split._replace(
netloc=split.netloc.replace(f"{split.username}:{split.password}", f"{split.username}:********")
)
url = urlunsplit(split)
return url
def ready_name_for_url(raw_name):
""" General method to convert a string (i.e. object name) to a URL-ready
"""General method to convert a string (i.e. object name) to a URL-ready
slug.
>>> ready_name_for_url( "My Cool Object" )
@@ -727,7 +760,7 @@ def ready_name_for_url(raw_name):
# Remove all non-alphanumeric characters.
slug_base = re.sub(r"[^a-zA-Z0-9\-]", "", slug_base)
# Remove trailing '-'.
if slug_base.endswith('-'):
if slug_base.endswith("-"):
slug_base = slug_base[:-1]
return slug_base
@@ -768,7 +801,7 @@ def in_directory(file, directory, local_path_module=os.path):
False
"""
if local_path_module != os.path:
_safe_contains = importlib.import_module(f'galaxy.util.path.{local_path_module.__name__}').safe_contains
_safe_contains = importlib.import_module(f"galaxy.util.path.{local_path_module.__name__}").safe_contains
else:
directory = os.path.realpath(directory)
_safe_contains = safe_contains
@@ -791,9 +824,7 @@ def merge_sorted_iterables(operator, *iterables):
yield from first_iterable
else:
yield from __merge_two_sorted_iterables(
operator,
iter(first_iterable),
merge_sorted_iterables(operator, *iterables[1:])
operator, iter(first_iterable), merge_sorted_iterables(operator, *iterables[1:])
)
@@ -848,7 +879,7 @@ class Params:
"""
# is NEVER_SANITIZE required now that sanitizing for tool parameters can be controlled on a per parameter basis and occurs via InputValueWrappers?
NEVER_SANITIZE = ['file_data', 'url_paste', 'URL', 'filesystem_paths']
NEVER_SANITIZE = ["file_data", "url_paste", "URL", "filesystem_paths"]
def __init__(self, params, sanitize=True):
if sanitize:
@@ -857,9 +888,12 @@ class Params:
# name. Anything relying on NEVER_SANITIZE should be
# changed to not require this and NEVER_SANITIZE should be
# removed.
if (value is not None and key not in self.NEVER_SANITIZE
and True not in [key.endswith(f"|{nonsanitize_parameter}") for
nonsanitize_parameter in self.NEVER_SANITIZE]):
if (
value is not None
and key not in self.NEVER_SANITIZE
and True
not in [key.endswith(f"|{nonsanitize_parameter}") for nonsanitize_parameter in self.NEVER_SANITIZE]
):
self.__dict__[key] = sanitize_param(value)
else:
self.__dict__[key] = value
@@ -887,7 +921,7 @@ class Params:
return self.__dict__.get(key, default)
def __str__(self):
return f'{self.__dict__}'
return f"{self.__dict__}"
def __len__(self):
return len(self.__dict__)
@@ -918,12 +952,12 @@ def rst_to_html(s, error=False):
"template": os.path.join(os.path.dirname(__file__), "docutils_template.txt"),
"warning_stream": FakeStream(),
"doctitle_xform": False, # without option, very different rendering depending on
# number of sections in help content.
# number of sections in help content.
}
return unicodify(docutils_core.publish_string(
s, writer=docutils_html4css1.Writer(),
settings_overrides=settings_overrides))
return unicodify(
docutils_core.publish_string(s, writer=docutils_html4css1.Writer(), settings_overrides=settings_overrides)
)
def xml_text(root, name=None):
@@ -938,10 +972,10 @@ def xml_text(root, name=None):
else:
elem = root
if elem is not None and elem.text:
text = ''.join(elem.text.splitlines())
text = "".join(elem.text.splitlines())
return text.strip()
# No luck, return empty string
return ''
return ""
def parse_resource_parameters(resource_param_file):
@@ -961,8 +995,8 @@ def parse_resource_parameters(resource_param_file):
# asbool implementation pulled from PasteDeploy
truthy = frozenset({'true', 'yes', 'on', 'y', 't', '1'})
falsy = frozenset({'false', 'no', 'off', 'n', 'f', '0'})
truthy = frozenset({"true", "yes", "on", "y", "t", "1"})
falsy = frozenset({"false", "no", "off", "n", "f", "0"})
def asbool(obj):
@@ -978,7 +1012,7 @@ def asbool(obj):
def string_as_bool(string: str) -> bool:
if str(string).lower() in ('true', 'yes', 'on', '1'):
if str(string).lower() in ("true", "yes", "on", "1"):
return True
else:
return False
@@ -995,9 +1029,9 @@ def string_as_bool_or_none(string):
function equivalently.
"""
string = str(string).lower()
if string in ('true', 'yes', 'on'):
if string in ("true", "yes", "on"):
return True
elif string in ['none', 'null']:
elif string in ["none", "null"]:
return None
else:
return False
@@ -1026,18 +1060,18 @@ def listify(item, do_strip=False) -> typing.List[typing.Any]:
return item
elif isinstance(item, tuple):
return list(item)
elif isinstance(item, str) and item.count(','):
elif isinstance(item, str) and item.count(","):
if do_strip:
return [token.strip() for token in item.split(',')]
return [token.strip() for token in item.split(",")]
else:
return item.split(',')
return item.split(",")
else:
return [item]
def commaify(amount):
orig = amount
new = re.sub(r"^(-?\d+)(\d{3})", r'\g<1>,\g<2>', amount)
new = re.sub(r"^(-?\d+)(\d{3})", r"\g<1>,\g<2>", amount)
if orig == new:
return new
else:
@@ -1051,10 +1085,10 @@ def roundify(amount, sfs=2):
if len(amount) <= sfs:
return amount
else:
return amount[0:sfs] + '0' * (len(amount) - sfs)
return amount[0:sfs] + "0" * (len(amount) - sfs)
def unicodify(value, encoding=DEFAULT_ENCODING, error='replace', strip_null=False, log_exception=True):
def unicodify(value, encoding=DEFAULT_ENCODING, error="replace", strip_null=False, log_exception=True):
"""
Returns a Unicode string or None.
@@ -1085,11 +1119,13 @@ def unicodify(value, encoding=DEFAULT_ENCODING, error='replace', strip_null=Fals
log.exception(msg)
raise
if strip_null:
return value.replace('\0', '')
return value.replace("\0", "")
return value
def filesystem_safe_string(s, max_len=255, truncation_chars='..', strip_leading_dot=True, invalid_chars=('/',), replacement_char='_'):
def filesystem_safe_string(
s, max_len=255, truncation_chars="..", strip_leading_dot=True, invalid_chars=("/",), replacement_char="_"
):
"""
Strip unicode null chars, truncate at 255 characters.
Optionally replace additional ``invalid_chars`` with `replacement_char` .
@@ -1099,16 +1135,16 @@ def filesystem_safe_string(s, max_len=255, truncation_chars='..', strip_leading_
"""
sanitized_string = unicodify(s, strip_null=True)
if strip_leading_dot:
sanitized_string = sanitized_string.lstrip('.')
sanitized_string = sanitized_string.lstrip(".")
for invalid_char in invalid_chars:
sanitized_string = sanitized_string.replace(invalid_char, replacement_char)
if len(sanitized_string) > max_len:
sanitized_string = sanitized_string[:max_len - len(truncation_chars)]
sanitized_string = sanitized_string[: max_len - len(truncation_chars)]
sanitized_string = f"{sanitized_string}{truncation_chars}"
return sanitized_string
def smart_str(s, encoding=DEFAULT_ENCODING, strings_only=False, errors='strict'):
def smart_str(s, encoding=DEFAULT_ENCODING, strings_only=False, errors="strict"):
"""
Returns a bytestring version of 's', encoded as specified in 'encoding'.
@@ -1153,7 +1189,7 @@ def string_to_object(s):
return binascii.unhexlify(s)
def clean_multiline_string(multiline_string, sep='\n'):
def clean_multiline_string(multiline_string, sep="\n"):
"""
Dedent, split, remove first and last empty lines, rejoin.
"""
@@ -1163,12 +1199,11 @@ def clean_multiline_string(multiline_string, sep='\n'):
string_list = string_list[1:]
if not string_list[-1]:
string_list = string_list[:-1]
return '\n'.join(string_list) + '\n'
return "\n".join(string_list) + "\n"
class ParamsWithSpecs(collections.defaultdict):
"""
"""
""" """
def __init__(self, specs=None, params=None):
self.specs = specs or dict()
@@ -1176,19 +1211,19 @@ class ParamsWithSpecs(collections.defaultdict):
for name, value in self.params.items():
if name not in self.specs:
self._param_unknown_error(name)
if 'map' in self.specs[name]:
if "map" in self.specs[name]:
try:
self.params[name] = self.specs[name]['map'](value)
self.params[name] = self.specs[name]["map"](value)
except Exception:
self._param_map_error(name, value)
if 'valid' in self.specs[name]:
if not self.specs[name]['valid'](value):
if "valid" in self.specs[name]:
if not self.specs[name]["valid"](value):
self._param_vaildation_error(name, value)
self.update(self.params)
def __missing__(self, name):
return self.specs[name]['default']
return self.specs[name]["default"]
def __getattr__(self, name):
return self[name]
@@ -1216,7 +1251,7 @@ def compare_urls(url1, url2, compare_scheme=True, compare_hostname=True, compare
def read_build_sites(filename, check_builds=True):
""" read db names to ucsc mappings from file, this file should probably be merged with the one above """
"""read db names to ucsc mappings from file, this file should probably be merged with the one above"""
build_sites = []
try:
for line in open(filename):
@@ -1228,9 +1263,9 @@ def read_build_sites(filename, check_builds=True):
site = fields[1]
if check_builds:
site_builds = fields[2].split(",")
site_dict = {'name': site_name, 'url': site, 'builds': site_builds}
site_dict = {"name": site_name, "url": site, "builds": site_builds}
else:
site_dict = {'name': site_name, 'url': site}
site_dict = {"name": site_name, "url": site}
build_sites.append(site_dict)
except Exception:
continue
@@ -1266,7 +1301,7 @@ def stringify_dictionary_keys(in_dict):
return out_dict
def mkstemp_ln(src, prefix='mkstemp_ln_'):
def mkstemp_ln(src, prefix="mkstemp_ln_"):
"""
From tempfile._mkstemp_inner, generate a hard link in the same dir with a
random name. Created so we can persist the underlying file of a
@@ -1279,7 +1314,7 @@ def mkstemp_ln(src, prefix='mkstemp_ln_'):
file = os.path.join(dir, prefix + name)
try:
os.link(src, file)
return (os.path.abspath(file))
return os.path.abspath(file)
except OSError as e:
if e.errno == errno.EEXIST:
continue # try again
@@ -1295,18 +1330,18 @@ def umask_fix_perms(path, umask, unmasked_perms, gid=None):
try:
st = os.stat(path)
except OSError:
log.exception('Unable to set permissions or group on %s', path)
log.exception("Unable to set permissions or group on %s", path)
return
# fix modes
if stat.S_IMODE(st.st_mode) != perms:
try:
os.chmod(path, perms)
except Exception as e:
log.warning('Unable to honor umask ({}) for {}, tried to set: {} but mode remains {}, error was: {}'.format(oct(umask),
path,
oct(perms),
oct(stat.S_IMODE(st.st_mode)),
unicodify(e)))
log.warning(
"Unable to honor umask ({}) for {}, tried to set: {} but mode remains {}, error was: {}".format(
oct(umask), path, oct(perms), oct(stat.S_IMODE(st.st_mode)), unicodify(e)
)
)
# fix group
if gid is not None and st.st_gid != gid:
try:
@@ -1318,16 +1353,17 @@ def umask_fix_perms(path, umask, unmasked_perms, gid=None):
except Exception:
desired_group = gid
current_group = st.st_gid
log.warning('Unable to honor primary group ({}) for {}, group remains {}, error was: {}'.format(desired_group,
path,
current_group,
unicodify(e)))
log.warning(
"Unable to honor primary group ({}) for {}, group remains {}, error was: {}".format(
desired_group, path, current_group, unicodify(e)
)
)
def docstring_trim(docstring):
"""Trimming python doc strings. Taken from: http://www.python.org/dev/peps/pep-0257/"""
if not docstring:
return ''
return ""
# Convert tabs to spaces (following the normal Python rules)
# and split into a list of lines:
lines = docstring.expandtabs().splitlines()
@@ -1348,7 +1384,7 @@ def docstring_trim(docstring):
while trimmed and not trimmed[0]:
trimmed.pop(0)
# Return a single string:
return '\n'.join(trimmed)
return "\n".join(trimmed)
def nice_size(size):
@@ -1364,23 +1400,23 @@ def nice_size(size):
>>> nice_size(100000000)
'95.4 MB'
"""
words = ['bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB']
prefix = ''
words = ["bytes", "KB", "MB", "GB", "TB", "PB", "EB"]
prefix = ""
try:
size = float(size)
if size < 0:
size = abs(size)
prefix = '-'
prefix = "-"
except Exception:
return '??? bytes'
return "??? bytes"
for ind, word in enumerate(words):
step = 1024 ** (ind + 1)
if step > size:
size = size / float(1024 ** ind)
if word == 'bytes': # No decimals for bytes
size = size / float(1024**ind)
if word == "bytes": # No decimals for bytes
return "%s%d bytes" % (prefix, size)
return f"{prefix}{size:.1f} {word}"
return '??? bytes'
return "??? bytes"
def size_to_bytes(size):
@@ -1405,26 +1441,26 @@ def size_to_bytes(size):
122880
"""
# The following number regexp is based on https://stackoverflow.com/questions/385558/extract-float-double-value/385597#385597
size_re = re.compile(r'(?P<number>(\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?)\s*(?P<multiple>[eptgmk]?(b|bytes?)?)?$')
size_re = re.compile(r"(?P<number>(\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?)\s*(?P<multiple>[eptgmk]?(b|bytes?)?)?$")
size_match = size_re.match(size.lower())
if size_match is None:
raise ValueError(f"Could not parse string '{size}'")
number = float(size_match.group("number"))
multiple = size_match.group("multiple")
if multiple == "" or multiple.startswith('b'):
if multiple == "" or multiple.startswith("b"):
return int(number)
elif multiple.startswith('k'):
elif multiple.startswith("k"):
return int(number * 1024)
elif multiple.startswith('m'):
return int(number * 1024 ** 2)
elif multiple.startswith('g'):
return int(number * 1024 ** 3)
elif multiple.startswith('t'):
return int(number * 1024 ** 4)
elif multiple.startswith('p'):
return int(number * 1024 ** 5)
elif multiple.startswith('e'):
return int(number * 1024 ** 6)
elif multiple.startswith("m"):
return int(number * 1024**2)
elif multiple.startswith("g"):
return int(number * 1024**3)
elif multiple.startswith("t"):
return int(number * 1024**4)
elif multiple.startswith("p"):
return int(number * 1024**5)
elif multiple.startswith("e"):
return int(number * 1024**6)
else:
raise ValueError(f"Unknown multiplier '{multiple}' in '{size}'")
@@ -1455,13 +1491,13 @@ def send_mail(frm, to, subject, body, config, html=None):
to = listify(to)
if html:
msg = MIMEMultipart('alternative')
msg = MIMEMultipart("alternative")
else:
msg = MIMEText(body, 'plain', 'utf-8')
msg = MIMEText(body, "plain", "utf-8")
msg['To'] = ', '.join(to)
msg['From'] = frm
msg['Subject'] = subject
msg["To"] = ", ".join(to)
msg["From"] = frm
msg["Subject"] = subject
if config.smtp_server is None:
log.error("Mail is not configured for this Galaxy instance.")
@@ -1469,12 +1505,12 @@ def send_mail(frm, to, subject, body, config, html=None):
return
if html:
mp_text = MIMEText(body, 'plain', 'utf-8')
mp_html = MIMEText(html, 'html', 'utf-8')
mp_text = MIMEText(body, "plain", "utf-8")
mp_html = MIMEText(html, "html", "utf-8")
msg.attach(mp_text)
msg.attach(mp_html)
smtp_ssl = asbool(getattr(config, 'smtp_ssl', False))
smtp_ssl = asbool(getattr(config, "smtp_ssl", False))
if smtp_ssl:
s = smtplib.SMTP_SSL(config.smtp_server)
else:
@@ -1482,15 +1518,15 @@ def send_mail(frm, to, subject, body, config, html=None):
if not smtp_ssl:
try:
s.starttls()
log.debug('Initiated SSL/TLS connection to SMTP server: %s', config.smtp_server)
log.debug("Initiated SSL/TLS connection to SMTP server: %s", config.smtp_server)
except RuntimeError as e:
log.warning('SSL/TLS support is not available to your Python interpreter: %s', unicodify(e))
log.warning("SSL/TLS support is not available to your Python interpreter: %s", unicodify(e))
except smtplib.SMTPHeloError as e:
log.error("The server didn't reply properly to the HELO greeting: %s", unicodify(e))
s.close()
raise
except smtplib.SMTPException as e:
log.warning('The server does not support the STARTTLS extension: %s', unicodify(e))
log.warning("The server does not support the STARTTLS extension: %s", unicodify(e))
if config.smtp_username and config.smtp_password:
try:
s.login(config.smtp_username, config.smtp_password)
@@ -1546,8 +1582,7 @@ def move_merge(source, target):
def safe_str_cmp(a, b):
"""safely compare two strings in a timing-attack-resistant manner
"""
"""safely compare two strings in a timing-attack-resistant manner"""
if len(a) != len(b):
return False
rv = 0
@@ -1558,7 +1593,7 @@ def safe_str_cmp(a, b):
# Don't use these two directly, prefer method version that "works" with packaged Galaxy.
galaxy_root_path = os.path.join(__path__[0], os.pardir, os.pardir, os.pardir) # type: ignore[name-defined]
galaxy_samples_path = os.path.join(__path__[0], os.pardir, 'config', 'sample') # type: ignore[name-defined]
galaxy_samples_path = os.path.join(__path__[0], os.pardir, "config", "sample") # type: ignore[name-defined]
def galaxy_directory():
@@ -1569,7 +1604,7 @@ def galaxy_directory():
def galaxy_samples_directory():
return os.path.join(galaxy_directory(), 'lib', 'galaxy', 'config', 'sample')
return os.path.join(galaxy_directory(), "lib", "galaxy", "config", "sample")
def config_directories_from_setting(directories_setting, galaxy_root=galaxy_root_path):
@@ -1592,10 +1627,10 @@ def config_directories_from_setting(directories_setting, galaxy_root=galaxy_root
for directory in listify(directories_setting):
directory = directory.strip()
if not directory.startswith('/'):
if not directory.startswith("/"):
directory = os.path.join(galaxy_root, directory)
if not os.path.exists(directory):
log.warning('directory not found: %s', directory)
log.warning("directory not found: %s", directory)
continue
directories.append(directory)
return directories
@@ -1640,32 +1675,32 @@ def parse_non_hex_float(s):
"""
f = float(s)
# successfully parsed as float if here - check for format in original string
if 'e' in s and not ('+' in s or '-' in s):
raise ValueError('could not convert string to float: ' + s)
if "e" in s and not ("+" in s or "-" in s):
raise ValueError("could not convert string to float: " + s)
return f
def build_url(base_url, port=80, scheme='http', pathspec=None, params=None, doseq=False):
def build_url(base_url, port=80, scheme="http", pathspec=None, params=None, doseq=False):
if params is None:
params = dict()
if pathspec is None:
pathspec = []
parsed_url = urlparse(base_url)
if scheme != 'http':
if scheme != "http":
parsed_url.scheme = scheme
assert parsed_url.scheme in ('http', 'https', 'ftp'), f'Invalid URL scheme: {scheme}'
assert parsed_url.scheme in ("http", "https", "ftp"), f"Invalid URL scheme: {scheme}"
if port != 80:
url = '%s://%s:%d/%s' % (parsed_url.scheme, parsed_url.netloc.rstrip('/'), int(port), parsed_url.path)
url = "%s://%s:%d/%s" % (parsed_url.scheme, parsed_url.netloc.rstrip("/"), int(port), parsed_url.path)
else:
url = f"{parsed_url.scheme}://{parsed_url.netloc.rstrip('/')}/{parsed_url.path.lstrip('/')}"
if len(pathspec) > 0:
url = f"{url.rstrip('/')}/{'/'.join(pathspec)}"
if parsed_url.query:
for query_parameter in parsed_url.query.split('&'):
key, value = query_parameter.split('=')
for query_parameter in parsed_url.query.split("&"):
key, value = query_parameter.split("=")
params[key] = value
if params:
url += f'?{urlencode(params, doseq=doseq)}'
url += f"?{urlencode(params, doseq=doseq)}"
return url
@@ -1697,15 +1732,17 @@ def is_url(uri, allow_list=None):
return any(uri.startswith(scheme) for scheme in allow_list)
def download_to_file(url, dest_file_path, timeout=30, chunk_size=2 ** 20):
def download_to_file(url, dest_file_path, timeout=30, chunk_size=2**20):
"""Download a URL to a file in chunks."""
with requests.get(url, timeout=timeout, stream=True) as r, open(dest_file_path, 'wb') as f:
with requests.get(url, timeout=timeout, stream=True) as r, open(dest_file_path, "wb") as f:
for chunk in r.iter_content(chunk_size):
if chunk:
f.write(chunk)
def stream_to_open_named_file(stream, fd, filename, source_encoding=None, source_error='strict', target_encoding=None, target_error='strict'):
def stream_to_open_named_file(
stream, fd, filename, source_encoding=None, source_error="strict", target_encoding=None, target_error="strict"
):
"""Writes a stream to the provided file descriptor, returns the file name. Closes file descriptor"""
# signature and behavor is somewhat odd, due to backwards compatibility, but this can/should be done better
CHUNK_SIZE = 1048576
@@ -1740,7 +1777,6 @@ def stream_to_open_named_file(stream, fd, filename, source_encoding=None, source
class classproperty:
def __init__(self, f):
self.f = f
@@ -1749,7 +1785,6 @@ class classproperty:
class ExecutionTimer:
def __init__(self):
self.begin = time.time()
@@ -1758,11 +1793,10 @@ class ExecutionTimer:
@property
def elapsed(self):
return (time.time() - self.begin)
return time.time() - self.begin
class StructuredExecutionTimer:
def __init__(self, timer_id, template, **tags):
self.begin = time.time()
self.timer_id = timer_id
@@ -1782,9 +1816,10 @@ class StructuredExecutionTimer:
@property
def elapsed(self):
return (time.time() - self.begin)
return time.time() - self.begin
if __name__ == '__main__':
if __name__ == "__main__":
import doctest
doctest.testmod(sys.modules[__name__], verbose=False)
+49 -45
View File
@@ -6,17 +6,13 @@ import imp
import logging
import shlex
from functools import partial
try:
from grp import getgrgid
except ImportError:
getgrgid = None # type: ignore[assignment]
from itertools import starmap
from operator import getitem
from os import (
extsep,
makedirs,
stat,
walk
walk,
)
from os.path import (
abspath,
@@ -30,15 +26,20 @@ from os.path import (
pardir,
realpath,
relpath,
sep as separator,
)
from os.path import sep as separator
from pathlib import Path
try:
from grp import getgrgid
except ImportError:
getgrgid = None # type: ignore[assignment]
try:
from pwd import getpwuid
except ImportError:
getpwuid = None # type: ignore[assignment]
import galaxy.util
WALK_MAX_DIRS = 10000
@@ -86,7 +87,6 @@ def safe_contains(prefix, path, allowlist=None, real=None):
class _SafeContainsDirectoryChecker:
def __init__(self, dirpath, prefix, allowlist=None):
self.allowlist = allowlist
self.dirpath = dirpath
@@ -151,8 +151,9 @@ def safe_walk(path, allowlist=None):
if allowlist and i % WALK_MAX_DIRS == 0:
raise RuntimeError(
'Breaking out of walk of %s after %s iterations (most likely infinite symlink recursion) at: %s' %
(path, WALK_MAX_DIRS, dirpath))
"Breaking out of walk of %s after %s iterations (most likely infinite symlink recursion) at: %s"
% (path, WALK_MAX_DIRS, dirpath)
)
_prefix = partial(join, dirpath)
prune = False
@@ -216,9 +217,11 @@ def __path_permission_for_user(path, username):
owner_permissions = int(oct_mode[-3])
group_permissions = int(oct_mode[-2])
other_permissions = int(oct_mode[-1])
if other_permissions >= 4 or \
(file_owner == username and owner_permissions >= 4) or \
(username in group_members and group_permissions >= 4):
if (
other_permissions >= 4
or (file_owner == username and owner_permissions >= 4)
or (username in group_members and group_permissions >= 4)
):
return True
return False
@@ -323,6 +326,7 @@ class Extensions(dict):
The first item in the sequence should match the key and is the "canonicalization".
"""
def __missing__(self, key):
for v in self.values():
if key in v:
@@ -335,11 +339,13 @@ class Extensions(dict):
return self[ext][0]
extensions = Extensions({
'ini': ['ini'],
'json': ['json'],
'yaml': ['yaml', 'yml'],
})
extensions = Extensions(
{
"ini": ["ini"],
"json": ["json"],
"yaml": ["yaml", "yml"],
}
)
def external_chown(path, pwent, external_chown_script, description="file"):
@@ -352,7 +358,7 @@ def external_chown(path, pwent, external_chown_script, description="file"):
"""
try:
if not external_chown_script:
raise ValueError('external_chown_script is not defined')
raise ValueError("external_chown_script is not defined")
if Path(path).owner() == pwent[0]:
return True
@@ -362,13 +368,12 @@ def external_chown(path, pwent, external_chown_script, description="file"):
galaxy.util.commands.execute(cmd)
return True
except galaxy.util.commands.CommandLineException as e:
log.warning(f'Changing ownership of {description} {path} failed: {galaxy.util.unicodify(e)}')
log.warning(f"Changing ownership of {description} {path} failed: {galaxy.util.unicodify(e)}")
return False
def __listify(item):
"""A non-splitting version of :func:`galaxy.util.listify`.
"""
"""A non-splitting version of :func:`galaxy.util.listify`."""
if not item:
return []
elif isinstance(item, list) or isinstance(item, tuple):
@@ -402,7 +407,7 @@ def __ext_strip_sep(ext):
def __splitext_no_sep(path):
path = galaxy.util.unicodify(path)
return (path.rsplit(extsep, 1) + [''])[0:2]
return (path.rsplit(extsep, 1) + [""])[0:2]
def __splitext_ignore(path, ignore=None):
@@ -410,7 +415,7 @@ def __splitext_ignore(path, ignore=None):
ignore = map(__ext_strip_sep, __listify(ignore))
root, ext = __splitext_no_sep(path)
if ext in ignore:
new_path = path[0:(-len(ext) - 1)]
new_path = path[0 : (-len(ext) - 1)]
root, ext = __splitext_no_sep(new_path)
return (root, ext)
@@ -431,10 +436,9 @@ def _build_self(target, path_module):
def __copy_self(names=__name__, parent=None):
"""Returns a copy of this module that can be modified without modifying `galaxy.util.path`` in ``sys.modules``.
"""
"""Returns a copy of this module that can be modified without modifying `galaxy.util.path`` in ``sys.modules``."""
if isinstance(names, str):
names = iter(names.split('.'))
names = iter(names.split("."))
try:
name = next(names)
except StopIteration:
@@ -459,25 +463,25 @@ def __set_fxns_on(target, path_module):
__pathfxns__ = (
'abspath',
'basename',
'exists',
'isabs',
'join',
'normpath',
'pardir',
'realpath',
'relpath',
"abspath",
"basename",
"exists",
"isabs",
"join",
"normpath",
"pardir",
"realpath",
"relpath",
)
__all__ = (
'extensions',
'get_ext',
'has_ext',
'joinext',
'safe_contains',
'safe_makedirs',
'safe_relpath',
'safe_walk',
'unsafe_walk',
"extensions",
"get_ext",
"has_ext",
"joinext",
"safe_contains",
"safe_makedirs",
"safe_relpath",
"safe_walk",
"unsafe_walk",
)
+13 -10
View File
@@ -3,11 +3,12 @@ import os
from collections import OrderedDict
import yaml
from yaml.constructor import ConstructorError
try:
from yaml import CSafeLoader as SafeLoader
except ImportError:
from yaml import SafeLoader # type: ignore[misc]
from yaml.constructor import ConstructorError
log = logging.getLogger(__name__)
@@ -36,6 +37,7 @@ def ordered_load(stream, merge_duplicate_keys=False):
Otherwise, following YAML 1.2 specification which says that "each key is
unique in the association", raise a ConstructionError exception.
"""
def construct_mapping(loader, node, deep=False):
loader.flatten_mapping(node)
mapping = {}
@@ -45,8 +47,12 @@ def ordered_load(stream, merge_duplicate_keys=False):
value = loader.construct_object(value_node, deep=deep)
if key in mapping:
if not merge_duplicate_keys:
raise ConstructorError("while constructing a mapping", node.start_mark,
f"found duplicated key ({key})", key_node.start_mark)
raise ConstructorError(
"while constructing a mapping",
node.start_mark,
f"found duplicated key ({key})",
key_node.start_mark,
)
log.debug("Merging values for duplicate key '%s' into a list", key)
if merged_duplicate.get(key):
mapping[key].append(value)
@@ -57,10 +63,8 @@ def ordered_load(stream, merge_duplicate_keys=False):
mapping[key] = value
return mapping
OrderedLoader.add_constructor(
yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG,
construct_mapping)
OrderedLoader.add_constructor('!include', OrderedLoader.include)
OrderedLoader.add_constructor(yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG, construct_mapping)
OrderedLoader.add_constructor("!include", OrderedLoader.include)
return yaml.load(stream, OrderedLoader)
@@ -70,8 +74,7 @@ def ordered_dump(data, stream=None, Dumper=yaml.Dumper, **kwds):
pass
def _dict_representer(dumper, data):
return dumper.represent_mapping(
yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG,
list(data.items()))
return dumper.represent_mapping(yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG, list(data.items()))
OrderedDumper.add_representer(OrderedDict, _dict_representer)
return yaml.dump(data, stream, OrderedDumper, **kwds)
+3 -3
View File
@@ -1,3 +1,3 @@
VERSION_MAJOR = "22.01"
VERSION_MINOR = "rc1"
VERSION = VERSION_MAJOR + (f".{VERSION_MINOR}" if VERSION_MINOR else '')
VERSION_MAJOR = "22.05"
VERSION_MINOR = "dev0"
VERSION = VERSION_MAJOR + (f".{VERSION_MINOR}" if VERSION_MINOR else "")
+44 -51
View File
@@ -62,17 +62,11 @@ from . import (
log = logging.getLogger(__name__)
router = Router(tags=['datasets'])
router = Router(tags=["datasets"])
DatasetIDPathParam: EncodedDatabaseIdField = Path(
...,
description="The encoded database identifier of the dataset."
)
DatasetIDPathParam: EncodedDatabaseIdField = Path(..., description="The encoded database identifier of the dataset.")
HistoryIDPathParam: EncodedDatabaseIdField = Path(
...,
description="The encoded database identifier of the History."
)
HistoryIDPathParam: EncodedDatabaseIdField = Path(..., description="The encoded database identifier of the History.")
DatasetSourceQueryParam: DatasetSourceType = Query(
default=DatasetSourceType.hda,
@@ -85,15 +79,15 @@ class FastAPIDatasets:
service: DatasetsService = depends(DatasetsService)
@router.get(
'/api/datasets',
summary='Search datasets or collections using a query system.',
"/api/datasets",
summary="Search datasets or collections using a query system.",
)
def index(
self,
trans=DependsOnTrans,
history_id: Optional[EncodedDatabaseIdField] = Query(
default=None,
description="Optional identifier of a History. Use it to restrict the search whithin a particular History."
description="Optional identifier of a History. Use it to restrict the search whithin a particular History.",
),
serialization_params: SerializationParams = Depends(query_serialization_params),
filter_query_params: FilterQueryParams = Depends(get_filter_query_params),
@@ -101,8 +95,8 @@ class FastAPIDatasets:
return self.service.index(trans, history_id, serialization_params, filter_query_params)
@router.get(
'/api/datasets/{dataset_id}/storage',
summary='Display user-facing storage details related to the objectstore a dataset resides in.',
"/api/datasets/{dataset_id}/storage",
summary="Display user-facing storage details related to the objectstore a dataset resides in.",
)
def show_storage(
self,
@@ -113,8 +107,8 @@ class FastAPIDatasets:
return self.service.show_storage(trans, dataset_id, hda_ldda)
@router.get(
'/api/datasets/{dataset_id}/inheritance_chain',
summary='For internal use, this endpoint may change without warning.',
"/api/datasets/{dataset_id}/inheritance_chain",
summary="For internal use, this endpoint may change without warning.",
include_in_schema=True, # Can be changed to False if we don't really want to expose this
)
def show_inheritance_chain(
@@ -126,8 +120,8 @@ class FastAPIDatasets:
return self.service.show_inheritance_chain(trans, dataset_id, hda_ldda)
@router.get(
'/api/datasets/{dataset_id}/get_content_as_text',
summary='Returns dataset content as Text.',
"/api/datasets/{dataset_id}/get_content_as_text",
summary="Returns dataset content as Text.",
)
def get_content_as_text(
self,
@@ -137,8 +131,8 @@ class FastAPIDatasets:
return self.service.get_content_as_text(trans, dataset_id)
@router.get(
'/api/datasets/{dataset_id}/converted/{ext}',
summary='Return information about datasets made by converting this dataset to a new format.',
"/api/datasets/{dataset_id}/converted/{ext}",
summary="Return information about datasets made by converting this dataset to a new format.",
)
def converted_ext(
self,
@@ -160,10 +154,8 @@ class FastAPIDatasets:
return self.service.converted_ext(trans, dataset_id, ext, serialization_params)
@router.get(
'/api/datasets/{dataset_id}/converted',
summary=(
"Return a a map with all the existing converted datasets associated with this instance."
),
"/api/datasets/{dataset_id}/converted",
summary=("Return a a map with all the existing converted datasets associated with this instance."),
)
def converted(
self,
@@ -176,8 +168,8 @@ class FastAPIDatasets:
return self.service.converted(trans, dataset_id)
@router.put(
'/api/datasets/{dataset_id}/permissions',
summary='Set permissions of the given history dataset to the given role ids.',
"/api/datasets/{dataset_id}/permissions",
summary="Set permissions of the given history dataset to the given role ids.",
)
def update_permissions(
self,
@@ -194,8 +186,8 @@ class FastAPIDatasets:
return self.service.update_permissions(trans, dataset_id, update_payload)
@router.get(
'/api/histories/{history_id}/contents/{history_content_id}/extra_files',
summary='Generate list of extra files.',
"/api/histories/{history_id}/contents/{history_content_id}/extra_files",
summary="Generate list of extra files.",
tags=["histories"],
)
def extra_files(
@@ -207,9 +199,9 @@ class FastAPIDatasets:
return self.service.extra_files(trans, history_content_id)
@router.get(
'/api/histories/{history_id}/contents/{history_content_id}/display',
"/api/histories/{history_id}/contents/{history_content_id}/display",
name="history_contents_display",
summary='Displays dataset (preview) content.',
summary="Displays dataset (preview) content.",
tags=["histories"],
response_class=StreamingResponse,
)
@@ -235,7 +227,7 @@ class FastAPIDatasets:
description=(
"The file extension when downloading the display data. Use the value `data` to "
"let the server infer it from the data type."
)
),
),
raw: bool = Query(
default=False,
@@ -248,12 +240,14 @@ class FastAPIDatasets:
):
"""Streams the preview contents of a dataset to be displayed in a browser."""
extra_params = get_query_parameters_from_request_excluding(request, {"preview", "filename", "to_ext", "raw"})
display_data, headers = self.service.display(trans, history_content_id, history_id, preview, filename, to_ext, raw, **extra_params)
display_data, headers = self.service.display(
trans, history_content_id, history_id, preview, filename, to_ext, raw, **extra_params
)
return StreamingResponse(display_data, headers=headers)
@router.get(
'/api/histories/{history_id}/contents/{history_content_id}/metadata_file',
summary='Returns the metadata file associated with this history item.',
"/api/histories/{history_id}/contents/{history_content_id}/metadata_file",
summary="Returns the metadata file associated with this history item.",
tags=["histories"],
response_class=FileResponse,
)
@@ -271,7 +265,7 @@ class FastAPIDatasets:
return FileResponse(path=cast(str, metadata_file_path), headers=headers)
@router.get(
'/api/datasets/{dataset_id}',
"/api/datasets/{dataset_id}",
summary="Displays information about and/or content of a dataset.",
)
def show(
@@ -281,9 +275,7 @@ class FastAPIDatasets:
dataset_id: EncodedDatabaseIdField = DatasetIDPathParam,
hda_ldda: DatasetSourceType = Query(
default=DatasetSourceType.hda,
description=(
"The type of information about the dataset to be requested."
),
description=("The type of information about the dataset to be requested."),
),
data_type: Optional[RequestDataType] = Query(
default=None,
@@ -368,25 +360,25 @@ class DatasetsController(BaseGalaxyAPIController):
filter_parameters = FilterQueryParams(**kwd)
filter_parameters.limit = filter_parameters.limit or limit
filter_parameters.offset = filter_parameters.offset or offset
return self.service.index(
trans, history_id, serialization_params, filter_parameters
)
return self.service.index(trans, history_id, serialization_params, filter_parameters)
@web.expose_api_anonymous_and_sessionless
def show(self, trans, id, hda_ldda='hda', data_type=None, provider=None, **kwd):
def show(self, trans, id, hda_ldda="hda", data_type=None, provider=None, **kwd):
"""
GET /api/datasets/{encoded_dataset_id}
Displays information about and/or content of a dataset.
"""
serialization_params = parse_serialization_params(**kwd)
kwd.update({
"provider": provider,
})
kwd.update(
{
"provider": provider,
}
)
rval = self.service.show(trans, id, hda_ldda, serialization_params, data_type, **kwd)
return rval
@web.expose_api_anonymous
def show_storage(self, trans, dataset_id, hda_ldda='hda', **kwd):
def show_storage(self, trans, dataset_id, hda_ldda="hda", **kwd):
"""
GET /api/datasets/{encoded_dataset_id}/storage
@@ -396,7 +388,7 @@ class DatasetsController(BaseGalaxyAPIController):
return self.service.show_storage(trans, dataset_id, hda_ldda)
@web.expose_api_anonymous
def show_inheritance_chain(self, trans, dataset_id, hda_ldda='hda', **kwd):
def show_inheritance_chain(self, trans, dataset_id, hda_ldda="hda", **kwd):
"""
GET /api/datasets/{dataset_id}/inheritance_chain
@@ -415,7 +407,7 @@ class DatasetsController(BaseGalaxyAPIController):
:rtype: dict
:returns: dictionary containing new permissions
"""
hda_ldda = kwd.pop('hda_ldda', DatasetSourceType.hda)
hda_ldda = kwd.pop("hda_ldda", DatasetSourceType.hda)
if payload:
kwd.update(payload)
update_payload = get_update_permission_payload(kwd)
@@ -430,8 +422,9 @@ class DatasetsController(BaseGalaxyAPIController):
return self.service.extra_files(trans, history_content_id)
@web.expose_api_raw_anonymous_and_sessionless
def display(self, trans, history_content_id, history_id,
preview=False, filename=None, to_ext=None, raw=False, **kwd):
def display(
self, trans, history_content_id, history_id, preview=False, filename=None, to_ext=None, raw=False, **kwd
):
"""
GET /api/histories/{encoded_history_id}/contents/{encoded_content_id}/display
Displays history content (dataset).
@@ -449,7 +442,7 @@ class DatasetsController(BaseGalaxyAPIController):
@web.expose_api
def get_content_as_text(self, trans, dataset_id):
""" Returns item content as Text. """
"""Returns item content as Text."""
return self.service.get_content_as_text(trans, dataset_id)
@web.expose_api_raw_anonymous_and_sessionless
+54 -68
View File
@@ -23,9 +23,7 @@ from pydantic.fields import Field
from pydantic.main import BaseModel
from starlette.responses import FileResponse
from galaxy import (
util
)
from galaxy import util
from galaxy.managers.context import (
ProvidesHistoryContext,
ProvidesUserContext,
@@ -52,9 +50,7 @@ from galaxy.schema.schema import (
SharingStatus,
)
from galaxy.schema.types import LatestLiteral
from galaxy.util import (
string_as_bool
)
from galaxy.util import string_as_bool
from galaxy.web import (
expose_api,
expose_api_anonymous,
@@ -72,27 +68,25 @@ from . import (
depends,
DependsOnTrans,
Router,
try_get_request_body_as_json
try_get_request_body_as_json,
)
log = logging.getLogger(__name__)
router = Router(tags=['histories'])
router = Router(tags=["histories"])
HistoryIDPathParam: EncodedDatabaseIdField = Path(
...,
title="History ID",
description="The encoded database identifier of the History."
..., title="History ID", description="The encoded database identifier of the History."
)
JehaIDPathParam: Union[EncodedDatabaseIdField, LatestLiteral] = Path(
default="latest",
title='Job Export History ID',
title="Job Export History ID",
description=(
'The ID of the specific Job Export History Association or '
'`latest` (default) to download the last generated archive.'
"The ID of the specific Job Export History Association or "
"`latest` (default) to download the last generated archive."
),
example="latest"
example="latest",
)
@@ -106,9 +100,7 @@ class HistoryIndexParams(HistoryFilterQueryParams):
class DeleteHistoryPayload(BaseModel):
purge: bool = Field(
default=False,
title="Purge",
description="Whether to definitely remove this history from disk."
default=False, title="Purge", description="Whether to definitely remove this history from disk."
)
@@ -122,8 +114,8 @@ class FastAPIHistories:
service: HistoriesService = depends(HistoriesService)
@router.get(
'/api/histories',
summary='Returns histories for the current user.',
"/api/histories",
summary="Returns histories for the current user.",
)
def index(
self,
@@ -135,13 +127,13 @@ class FastAPIHistories:
title="Deleted Only",
description="Whether to return only deleted items.",
deprecated=True, # Marked as deprecated as it seems just like '/api/histories/deleted'
)
),
) -> List[AnyHistoryView]:
return self.service.index(trans, serialization_params, params, deleted_only=deleted, all_histories=params.all)
@router.get(
'/api/histories/deleted',
summary='Returns deleted histories for the current user.',
"/api/histories/deleted",
summary="Returns deleted histories for the current user.",
)
def index_deleted(
self,
@@ -152,8 +144,8 @@ class FastAPIHistories:
return self.service.index(trans, serialization_params, params, deleted_only=True, all_histories=params.all)
@router.get(
'/api/histories/published',
summary='Return all histories that are published.',
"/api/histories/published",
summary="Return all histories that are published.",
)
def published(
self,
@@ -164,8 +156,8 @@ class FastAPIHistories:
return self.service.published(trans, serialization_params, filter_params)
@router.get(
'/api/histories/shared_with_me',
summary='Return all histories that are shared with the current user.',
"/api/histories/shared_with_me",
summary="Return all histories that are shared with the current user.",
)
def shared_with_me(
self,
@@ -176,8 +168,8 @@ class FastAPIHistories:
return self.service.shared_with_me(trans, serialization_params, filter_params)
@router.get(
'/api/histories/most_recently_used',
summary='Returns the most recently used history of the user.',
"/api/histories/most_recently_used",
summary="Returns the most recently used history of the user.",
)
def show_recent(
self,
@@ -187,8 +179,8 @@ class FastAPIHistories:
return self.service.show(trans, serialization_params)
@router.get(
'/api/histories/{id}',
summary='Returns the history with the given ID.',
"/api/histories/{id}",
summary="Returns the history with the given ID.",
)
def show(
self,
@@ -199,8 +191,8 @@ class FastAPIHistories:
return self.service.show(trans, serialization_params, id)
@router.get(
'/api/histories/{id}/citations',
summary='Return all the citations for the tools used to produce the datasets in the history.',
"/api/histories/{id}/citations",
summary="Return all the citations for the tools used to produce the datasets in the history.",
)
def citations(
self,
@@ -210,8 +202,8 @@ class FastAPIHistories:
return self.service.citations(trans, id)
@router.post(
'/api/histories',
summary='Creates a new history.',
"/api/histories",
summary="Creates a new history.",
)
def create(
self,
@@ -232,8 +224,8 @@ class FastAPIHistories:
return self.service.create(trans, payload, serialization_params)
@router.delete(
'/api/histories/{id}',
summary='Marks the history with the given ID as deleted.',
"/api/histories/{id}",
summary="Marks the history with the given ID as deleted.",
)
def delete(
self,
@@ -241,14 +233,14 @@ class FastAPIHistories:
id: EncodedDatabaseIdField = HistoryIDPathParam,
serialization_params: SerializationParams = Depends(query_serialization_params),
purge: bool = Query(default=False),
payload: Optional[DeleteHistoryPayload] = Body(default=None)
payload: Optional[DeleteHistoryPayload] = Body(default=None),
) -> AnyHistoryView:
if payload:
purge = payload.purge
return self.service.delete(trans, id, serialization_params, purge)
@router.post(
'/api/histories/deleted/{id}/undelete',
"/api/histories/deleted/{id}/undelete",
summary="Restores a deleted history with the given ID (that hasn't been purged).",
)
def undelete(
@@ -260,7 +252,7 @@ class FastAPIHistories:
return self.service.undelete(trans, id, serialization_params)
@router.put(
'/api/histories/{id}',
"/api/histories/{id}",
summary="Updates the values for the history with the given ID.",
)
def update(
@@ -276,10 +268,8 @@ class FastAPIHistories:
return self.service.update(trans, id, payload, serialization_params)
@router.get(
'/api/histories/{id}/exports',
summary=(
"Get previous history exports (to links). Effectively returns serialized JEHA objects."
),
"/api/histories/{id}/exports",
summary=("Get previous history exports (to links). Effectively returns serialized JEHA objects."),
)
def index_exports(
self,
@@ -290,17 +280,15 @@ class FastAPIHistories:
return JobExportHistoryArchiveCollection.parse_obj(exports)
@router.put( # PUT instead of POST because multiple requests should just result in one object being created.
'/api/histories/{id}/exports',
summary=(
"Start job (if needed) to create history export for corresponding history."
),
"/api/histories/{id}/exports",
summary=("Start job (if needed) to create history export for corresponding history."),
responses={
200: {
"description": "Object containing url to fetch export from.",
},
202: {
"description": "The exported archive file is not ready yet.",
}
},
},
)
def archive_export(
@@ -326,11 +314,9 @@ class FastAPIHistories:
return export_result
@router.get(
'/api/histories/{id}/exports/{jeha_id}',
"/api/histories/{id}/exports/{jeha_id}",
name="history_archive_download",
summary=(
"If ready and available, return raw contents of exported history as a downloadable archive."
),
summary=("If ready and available, return raw contents of exported history as a downloadable archive."),
response_class=FileResponse,
responses={
200: {
@@ -359,7 +345,7 @@ class FastAPIHistories:
)
@router.get(
'/api/histories/{id}/custom_builds_metadata',
"/api/histories/{id}/custom_builds_metadata",
summary="Returns meta data for custom builds.",
)
def get_custom_builds_metadata(
@@ -370,7 +356,7 @@ class FastAPIHistories:
return self.service.get_custom_builds_metadata(trans, id)
@router.get(
'/api/histories/{id}/sharing',
"/api/histories/{id}/sharing",
summary="Get the current sharing status of the given item.",
)
def sharing(
@@ -382,7 +368,7 @@ class FastAPIHistories:
return self.service.shareable_service.sharing(trans, id)
@router.put(
'/api/histories/{id}/enable_link_access',
"/api/histories/{id}/enable_link_access",
summary="Makes this item accessible by a URL link.",
)
def enable_link_access(
@@ -394,7 +380,7 @@ class FastAPIHistories:
return self.service.shareable_service.enable_link_access(trans, id)
@router.put(
'/api/histories/{id}/disable_link_access',
"/api/histories/{id}/disable_link_access",
summary="Makes this item inaccessible by a URL link.",
)
def disable_link_access(
@@ -406,7 +392,7 @@ class FastAPIHistories:
return self.service.shareable_service.disable_link_access(trans, id)
@router.put(
'/api/histories/{id}/publish',
"/api/histories/{id}/publish",
summary="Makes this item public and accessible by a URL link.",
)
def publish(
@@ -418,7 +404,7 @@ class FastAPIHistories:
return self.service.shareable_service.publish(trans, id)
@router.put(
'/api/histories/{id}/unpublish',
"/api/histories/{id}/unpublish",
summary="Removes this item from the published list.",
)
def unpublish(
@@ -430,20 +416,20 @@ class FastAPIHistories:
return self.service.shareable_service.unpublish(trans, id)
@router.put(
'/api/histories/{id}/share_with_users',
"/api/histories/{id}/share_with_users",
summary="Share this item with specific users.",
)
def share_with_users(
self,
trans: ProvidesUserContext = DependsOnTrans,
id: EncodedDatabaseIdField = HistoryIDPathParam,
payload: ShareWithPayload = Body(...)
payload: ShareWithPayload = Body(...),
) -> ShareWithStatus:
"""Shares this item with specific users and return the current sharing status."""
return self.service.shareable_service.share_with_users(trans, id, payload)
@router.put(
'/api/histories/{id}/slug',
"/api/histories/{id}/slug",
summary="Set a new slug for this shared item.",
status_code=status.HTTP_204_NO_CONTENT,
)
@@ -462,7 +448,7 @@ class HistoriesController(BaseGalaxyAPIController):
service: HistoriesService = depends(HistoriesService)
@expose_api_anonymous
def index(self, trans, deleted='False', **kwd):
def index(self, trans, deleted="False", **kwd):
"""
GET /api/histories
@@ -554,13 +540,13 @@ class HistoriesController(BaseGalaxyAPIController):
'order' defaults to 'create_time-dsc'
"""
deleted_only = util.string_as_bool(deleted)
all_histories = util.string_as_bool(kwd.get('all', False))
all_histories = util.string_as_bool(kwd.get("all", False))
serialization_params = parse_serialization_params(**kwd)
filter_parameters = HistoryFilterQueryParams(**kwd)
return self.service.index(trans, serialization_params, filter_parameters, deleted_only, all_histories)
@expose_api_anonymous
def show(self, trans, id, deleted='False', **kwd):
def show(self, trans, id, deleted="False", **kwd):
"""
show( trans, id, deleted='False' )
* GET /api/histories/{id}:
@@ -678,10 +664,10 @@ class HistoriesController(BaseGalaxyAPIController):
"""
history_id = id
# a request body is optional here
purge = string_as_bool(kwd.get('purge', False))
purge = string_as_bool(kwd.get("purge", False))
# for backwards compat, keep the payload sub-dictionary
if kwd.get('payload', None):
purge = string_as_bool(kwd['payload'].get('purge', purge))
if kwd.get("payload", None):
purge = string_as_bool(kwd["payload"].get("purge", purge))
serialization_params = parse_serialization_params(**kwd)
return self.service.delete(trans, history_id, serialization_params, purge)
File diff suppressed because it is too large Load Diff
+68 -56
View File
@@ -70,6 +70,7 @@ DEFAULT_LIMIT = 500
class RequestDataType(str, Enum):
"""Particular pieces of information that can be requested for a dataset."""
state = "state"
converted_datasets_state = "converted_datasets_state"
data = "data"
@@ -140,6 +141,7 @@ class DatasetTextContentDetails(Model):
class ConvertedDatasetsMap(BaseModel):
"""Map of `file extension` -> `converted dataset encoded id`"""
__root__: Dict[str, EncodedDatabaseIdField] # extension -> dataset ID
class Config:
@@ -168,7 +170,6 @@ class BamDataResult(DataResult):
class DatasetsService(ServiceBase, UsesVisualizationMixin):
def __init__(
self,
security: IdEncodingHelper,
@@ -179,7 +180,7 @@ class DatasetsService(ServiceBase, UsesVisualizationMixin):
ldda_manager: LDDAManager,
history_contents_manager: HistoryContentsManager,
history_contents_filters: HistoryContentsFilters,
data_provider_registry: DataProviderRegistry
data_provider_registry: DataProviderRegistry,
):
super().__init__(security)
self.history_manager = history_manager
@@ -193,11 +194,11 @@ class DatasetsService(ServiceBase, UsesVisualizationMixin):
@property
def serializer_by_type(self) -> Dict[str, ModelSerializer]:
return {'dataset': self.hda_serializer, 'dataset_collection': self.hdca_serializer}
return {"dataset": self.hda_serializer, "dataset_collection": self.hdca_serializer}
@property
def dataset_manager_by_type(self) -> Dict[str, DatasetAssociationManager]:
return {'hda': self.hda_manager, 'ldda': self.ldda_manager}
return {"hda": self.hda_manager, "ldda": self.ldda_manager}
def index(
self,
@@ -212,7 +213,7 @@ class DatasetsService(ServiceBase, UsesVisualizationMixin):
"""
user = self.get_authenticated_user(trans)
filters = self.history_contents_filters.parse_query_filters(filter_query_params)
view = serialization_params.view or 'summary'
view = serialization_params.view or "summary"
order_by = self.build_order_by(self.history_contents_manager, filter_query_params.order or "create_time-dsc")
container = None
if history_id:
@@ -226,7 +227,9 @@ class DatasetsService(ServiceBase, UsesVisualizationMixin):
user_id=user.id,
)
return [
self.serializer_by_type[content.history_content_type].serialize_to_view(content, user=user, trans=trans, view=view)
self.serializer_by_type[content.history_content_type].serialize_to_view(
content, user=user, trans=trans, view=view
)
for content in contents
]
@@ -251,7 +254,8 @@ class DatasetsService(ServiceBase, UsesVisualizationMixin):
rval = self._dataset_state(dataset)
elif data_type == RequestDataType.converted_datasets_state:
rval = self._converted_datasets_state(
trans, dataset,
trans,
dataset,
chrom=extra_params.get("chrom", None),
retry=extra_params.get("retry", False),
)
@@ -271,10 +275,7 @@ class DatasetsService(ServiceBase, UsesVisualizationMixin):
# Default: return dataset as dict.
if hda_ldda == DatasetSourceType.hda:
return self.hda_serializer.serialize_to_view(
dataset,
view=serialization_params.view or 'detailed',
user=trans.user,
trans=trans
dataset, view=serialization_params.view or "detailed", user=trans.user, trans=trans
)
else:
dataset_dict = dataset.to_dict()
@@ -362,7 +363,9 @@ class DatasetsService(ServiceBase, UsesVisualizationMixin):
rval = []
for root, directories, files in safe_walk(extra_files_path):
for directory in directories:
rval.append({"class": "Directory", "path": os.path.relpath(os.path.join(root, directory), extra_files_path)})
rval.append(
{"class": "Directory", "path": os.path.relpath(os.path.join(root, directory), extra_files_path)}
)
for file in files:
rval.append({"class": "File", "path": os.path.relpath(os.path.join(root, file), extra_files_path)})
@@ -388,26 +391,25 @@ class DatasetsService(ServiceBase, UsesVisualizationMixin):
"""
decoded_content_id = self.decode_id(history_content_id)
headers = {}
rval: Any = ''
rval: Any = ""
try:
hda = self.hda_manager.get_accessible(decoded_content_id, trans.user)
if raw:
if filename and filename != 'index':
if filename and filename != "index":
object_store = trans.app.object_store
dir_name = hda.dataset.extra_files_path_name
file_path = object_store.get_filename(hda.dataset,
extra_dir=dir_name,
alt_name=filename)
file_path = object_store.get_filename(hda.dataset, extra_dir=dir_name, alt_name=filename)
else:
file_path = hda.file_name
rval = open(file_path, 'rb')
rval = open(file_path, "rb")
else:
rval, headers = hda.datatype.display_data(trans, hda, preview, filename, to_ext, **kwd)
except galaxy_exceptions.MessageException:
raise
except Exception as e:
log.exception("Server error getting display data for dataset (%s) from history (%s)",
history_content_id, history_id)
log.exception(
"Server error getting display data for dataset (%s) from history (%s)", history_content_id, history_id
)
raise galaxy_exceptions.InternalServerError(f"Could not get display data for dataset: {util.unicodify(e)}")
return rval, headers
@@ -416,14 +418,18 @@ class DatasetsService(ServiceBase, UsesVisualizationMixin):
trans: ProvidesHistoryContext,
dataset_id: EncodedDatabaseIdField,
) -> DatasetTextContentDetails:
""" Returns dataset content as Text. """
"""Returns dataset content as Text."""
user = self.get_authenticated_user(trans)
decoded_id = self.decode_id(dataset_id)
hda = self.hda_manager.get_accessible(decoded_id, user)
hda = self.hda_manager.error_if_uploading(hda)
truncated, dataset_data = self.hda_manager.text_data(hda, preview=True)
item_url = web.url_for(
controller='dataset', action='display_by_username_and_slug', username=hda.history.user.username, slug=self.encode_id(hda.id), preview=False
controller="dataset",
action="display_by_username_and_slug",
username=hda.history.user.username,
slug=self.encode_id(hda.id),
preview=False,
)
return DatasetTextContentDetails(
item_data=dataset_data,
@@ -447,13 +453,13 @@ class DatasetsService(ServiceBase, UsesVisualizationMixin):
decoded_content_id = self.decode_id(history_content_id)
hda = self.hda_manager.get_accessible(decoded_content_id, trans.user)
file_ext = hda.metadata.spec.get(metadata_file).get("file_ext", metadata_file)
fname = ''.join(c in util.FILENAME_VALID_CHARS and c or '_' for c in hda.name)[0:150]
fname = "".join(c in util.FILENAME_VALID_CHARS and c or "_" for c in hda.name)[0:150]
headers = {}
headers["Content-Type"] = "application/octet-stream"
headers["Content-Disposition"] = f'attachment; filename="Galaxy{hda.hid}-[{fname}].{file_ext}"'
file_path = hda.metadata.get(metadata_file).file_name
if open_file:
return open(file_path, 'rb'), headers
return open(file_path, "rb"), headers
return file_path, headers
def converted_ext(
@@ -471,10 +477,7 @@ class DatasetsService(ServiceBase, UsesVisualizationMixin):
serialization_params.default_view = "detailed"
converted = self._get_or_create_converted(trans, hda, ext)
return self.hda_serializer.serialize_to_view(
converted,
user=trans.user,
trans=trans,
**serialization_params.dict()
converted, user=trans.user, trans=trans, **serialization_params.dict()
)
def converted(
@@ -488,7 +491,7 @@ class DatasetsService(ServiceBase, UsesVisualizationMixin):
"""
decoded_id = self.decode_id(dataset_id)
hda = self.hda_manager.get_accessible(decoded_id, trans.user)
return self.hda_serializer.serialize_converted_datasets(hda, 'converted')
return self.hda_serializer.serialize_converted_datasets(hda, "converted")
def _get_or_create_converted(self, trans, original: model.DatasetInstance, target_ext: str):
try:
@@ -498,11 +501,9 @@ class DatasetsService(ServiceBase, UsesVisualizationMixin):
except model.NoConverterException:
exc_data = dict(
source=original.ext,
target=target_ext,
available=list(original.get_converter_types().keys())
source=original.ext, target=target_ext, available=list(original.get_converter_types().keys())
)
raise galaxy_exceptions.RequestParameterInvalidException('Conversion not possible', **exc_data)
raise galaxy_exceptions.RequestParameterInvalidException("Conversion not possible", **exc_data)
def _dataset_in_use_state(self, dataset: model.DatasetInstance) -> bool:
"""
@@ -537,7 +538,7 @@ class DatasetsService(ServiceBase, UsesVisualizationMixin):
# Get datasources and check for messages (which indicate errors). Retry if flag is set.
data_sources = dataset.get_datasources(trans)
messages_list = [data_source_dict['message'] for data_source_dict in data_sources.values()]
messages_list = [data_source_dict["message"] for data_source_dict in data_sources.values()]
msg = self._get_highest_priority_msg(messages_list)
if msg:
if retry:
@@ -549,8 +550,9 @@ class DatasetsService(ServiceBase, UsesVisualizationMixin):
# If there is a chrom, check for data on the chrom.
if chrom:
data_provider = self.data_provider_registry.get_data_provider(trans,
original_dataset=dataset, source='index')
data_provider = self.data_provider_registry.get_data_provider(
trans, original_dataset=dataset, source="index"
)
if not data_provider.has_data(chrom):
return dataset.conversion_messages.NO_DATA
@@ -558,7 +560,10 @@ class DatasetsService(ServiceBase, UsesVisualizationMixin):
return {"status": dataset.conversion_messages.DATA, "valid_chroms": None}
def _search_features(
self, trans, dataset: model.DatasetInstance, query: Optional[str],
self,
trans,
dataset: model.DatasetInstance,
query: Optional[str],
) -> List[List[str]]:
"""
Returns features, locations in dataset that match query. Format is a
@@ -602,7 +607,7 @@ class DatasetsService(ServiceBase, UsesVisualizationMixin):
# Get datasources and check for messages.
data_sources = dataset.get_datasources(trans)
messages_list = [data_source_dict['message'] for data_source_dict in data_sources.values()]
messages_list = [data_source_dict["message"] for data_source_dict in data_sources.values()]
return_message = self._get_highest_priority_msg(messages_list)
if return_message:
return return_message
@@ -614,7 +619,7 @@ class DatasetsService(ServiceBase, UsesVisualizationMixin):
# Coverage mode uses index data.
if mode == "Coverage":
# Get summary using minimal cutoffs.
indexer = self.data_provider_registry.get_data_provider(trans, original_dataset=dataset, source='index')
indexer = self.data_provider_registry.get_data_provider(trans, original_dataset=dataset, source="index")
return indexer.get_data(chrom, low, high, **kwargs)
# TODO:
@@ -624,19 +629,19 @@ class DatasetsService(ServiceBase, UsesVisualizationMixin):
# If mode is Auto, need to determine what type of data to return.
if mode == "Auto":
# Get stats from indexer.
indexer = self.data_provider_registry.get_data_provider(trans, original_dataset=dataset, source='index')
indexer = self.data_provider_registry.get_data_provider(trans, original_dataset=dataset, source="index")
stats = indexer.get_data(chrom, low, high, stats=True)
# If stats were requested, return them.
if 'stats' in kwargs:
if stats['data']['max'] == 0:
if "stats" in kwargs:
if stats["data"]["max"] == 0:
return DataResult(dataset_type=indexer.dataset_type, data=None)
else:
return stats
# Stats provides features/base and resolution is bases/pixel, so
# multiplying them yields features/pixel.
features_per_pixel = stats['data']['max'] * float(kwargs['resolution'])
features_per_pixel = stats["data"]["max"] * float(kwargs["resolution"])
# Use heuristic based on features/pixel and region size to determine whether to
# return coverage data. When zoomed out and region is large, features/pixel
@@ -650,7 +655,7 @@ class DatasetsService(ServiceBase, UsesVisualizationMixin):
#
# Get data provider.
data_provider = self.data_provider_registry.get_data_provider(trans, original_dataset=dataset, source='data')
data_provider = self.data_provider_registry.get_data_provider(trans, original_dataset=dataset, source="data")
# Allow max_vals top be data provider set if not passed
if max_vals is None:
@@ -665,24 +670,33 @@ class DatasetsService(ServiceBase, UsesVisualizationMixin):
# FIXME: increase region 1M each way to provide sequence for
# spliced/gapped reads. Probably should provide refseq object
# directly to data provider.
region = trans.app.genomes.reference(trans, dbkey=dataset.dbkey, chrom=chrom,
low=(max(0, int(low) - 1000000)),
high=(int(high) + 1000000))
region = trans.app.genomes.reference(
trans,
dbkey=dataset.dbkey,
chrom=chrom,
low=(max(0, int(low) - 1000000)),
high=(int(high) + 1000000),
)
# Get mean depth.
if not indexer:
indexer = self.data_provider_registry.get_data_provider(trans, original_dataset=dataset, source='index')
indexer = self.data_provider_registry.get_data_provider(trans, original_dataset=dataset, source="index")
stats = indexer.get_data(chrom, low, high, stats=True)
mean_depth = stats['data']['mean']
mean_depth = stats["data"]["mean"]
# Get and return data from data_provider.
result = data_provider.get_data(chrom, int(low), int(high), int(start_val), int(max_vals),
ref_seq=region, mean_depth=mean_depth, **kwargs)
result.update({'dataset_type': data_provider.dataset_type, 'extra_info': extra_info})
result = data_provider.get_data(
chrom, int(low), int(high), int(start_val), int(max_vals), ref_seq=region, mean_depth=mean_depth, **kwargs
)
result.update({"dataset_type": data_provider.dataset_type, "extra_info": extra_info})
return result
def _raw_data(
self, trans, dataset, provider=None, **kwargs,
self,
trans,
dataset,
provider=None,
**kwargs,
) -> Union[model.Dataset.conversion_messages, BamDataResult, DataResult]:
"""
Uses original (raw) dataset to return data. This method is useful
@@ -705,9 +719,7 @@ class DatasetsService(ServiceBase, UsesVisualizationMixin):
elif dataset.datatype.has_dataprovider(provider):
kwargs = dataset.datatype.dataproviders[provider].parse_query_string_settings(kwargs)
# use dictionary to allow more than the data itself to be returned (data totals, other meta, etc.)
return DataResult(
data=list(dataset.datatype.dataprovider(dataset, provider, **kwargs))
)
return DataResult(data=list(dataset.datatype.dataprovider(dataset, provider, **kwargs)))
else:
raise dataproviders.exceptions.NoProviderAvailable(dataset.datatype, provider)
+84 -59
View File
@@ -1,8 +1,6 @@
import time
from requests import (
put
)
from requests import put
from galaxy_test.api.sharable import SharingApiTests
from galaxy_test.base.populators import (
@@ -14,7 +12,6 @@ from ._framework import ApiTestCase
class BaseHistories:
def _show(self, history_id):
return self._get(f"histories/{history_id}").json()
@@ -32,7 +29,6 @@ class BaseHistories:
class HistoriesApiTestCase(ApiTestCase, BaseHistories):
def setUp(self):
super().setUp()
self.dataset_populator = DatasetPopulator(self.galaxy_interactor)
@@ -60,16 +56,23 @@ class HistoriesApiTestCase(ApiTestCase, BaseHistories):
history_id = self._create_history("TestHistoryForShow")["id"]
show_response = self._show(history_id)
self._assert_has_key(
show_response,
'id', 'name', 'annotation', 'size', 'contents_url',
'state', 'state_details', 'state_ids'
show_response, "id", "name", "annotation", "size", "contents_url", "state", "state_details", "state_ids"
)
state_details = show_response["state_details"]
state_ids = show_response["state_ids"]
states = [
'discarded', 'empty', 'error', 'failed_metadata', 'new',
'ok', 'paused', 'queued', 'running', 'setting_metadata', 'upload'
"discarded",
"empty",
"error",
"failed_metadata",
"new",
"ok",
"paused",
"queued",
"running",
"setting_metadata",
"upload",
]
assert isinstance(state_details, dict)
assert isinstance(state_ids, dict)
@@ -114,7 +117,7 @@ class HistoriesApiTestCase(ApiTestCase, BaseHistories):
def test_purge(self):
history_id = self._create_history("TestHistoryForPurge")["id"]
data = {'purge': True}
data = {"purge": True}
self._delete(f"histories/{history_id}", data=data, json=True)
show_response = self._show(history_id)
assert show_response["deleted"]
@@ -134,7 +137,7 @@ class HistoriesApiTestCase(ApiTestCase, BaseHistories):
show_response = self._show(history_id)
assert show_response["name"] == "New Name"
unicode_name = '桜ゲノム'
unicode_name = "桜ゲノム"
self._update(history_id, {"name": unicode_name})
show_response = self._show(history_id)
assert show_response["name"] == unicode_name, show_response
@@ -182,7 +185,7 @@ class HistoriesApiTestCase(ApiTestCase, BaseHistories):
for str_key in ["name", "annotation"]:
assert self._update(history_id, {str_key: False}).status_code == 400
for bool_key in ['deleted', 'importable', 'published']:
for bool_key in ["deleted", "importable", "published"]:
assert self._update(history_id, {bool_key: "a string"}).status_code == 400
assert self._update(history_id, {"tags": "a simple string"}).status_code == 400
@@ -217,29 +220,32 @@ class HistoriesApiTestCase(ApiTestCase, BaseHistories):
def test_copy_history(self):
history_id = self.dataset_populator.new_history()
fetch_response = self.dataset_collection_populator.create_list_in_history(history_id, contents=["Hello", "World"], direct_upload=True)
fetch_response = self.dataset_collection_populator.create_list_in_history(
history_id, contents=["Hello", "World"], direct_upload=True
)
dataset_collection = self.dataset_collection_populator.wait_for_fetched_collection(fetch_response.json())
copied_history_response = self.dataset_populator.copy_history(history_id)
copied_history_response.raise_for_status()
copied_history = copied_history_response.json()
copied_collection = self.dataset_populator.get_history_collection_details(history_id=copied_history['id'], history_content_type="dataset_collection")
assert dataset_collection['name'] == copied_collection['name']
assert dataset_collection['id'] != copied_collection['id']
assert len(dataset_collection['elements']) == len(copied_collection['elements']) == 2
source_element = dataset_collection['elements'][0]
copied_element = copied_collection['elements'][0]
assert source_element['element_identifier'] == copied_element['element_identifier'] == 'data0'
assert source_element['id'] != copied_element['id']
source_hda = source_element['object']
copied_hda = copied_element['object']
assert source_hda['name'] == copied_hda['name'] == 'data0'
assert source_hda['id'] != copied_hda['id']
assert source_hda['history_id'] != copied_hda['history_id']
assert source_hda['hid'] == copied_hda['hid'] == 2
copied_collection = self.dataset_populator.get_history_collection_details(
history_id=copied_history["id"], history_content_type="dataset_collection"
)
assert dataset_collection["name"] == copied_collection["name"]
assert dataset_collection["id"] != copied_collection["id"]
assert len(dataset_collection["elements"]) == len(copied_collection["elements"]) == 2
source_element = dataset_collection["elements"][0]
copied_element = copied_collection["elements"][0]
assert source_element["element_identifier"] == copied_element["element_identifier"] == "data0"
assert source_element["id"] != copied_element["id"]
source_hda = source_element["object"]
copied_hda = copied_element["object"]
assert source_hda["name"] == copied_hda["name"] == "data0"
assert source_hda["id"] != copied_hda["id"]
assert source_hda["history_id"] != copied_hda["history_id"]
assert source_hda["hid"] == copied_hda["hid"] == 2
class ImportExportTests(BaseHistories):
def _set_up_populators(self):
self.dataset_populator = DatasetPopulator(self.galaxy_interactor)
self.dataset_collection_populator = DatasetCollectionPopulator(self.galaxy_interactor)
@@ -258,7 +264,13 @@ class ImportExportTests(BaseHistories):
assert hda["purged"] is True
self._check_imported_dataset(history_id=imported_history_id, hid=1, job_checker=upload_job_check)
self._check_imported_dataset(history_id=imported_history_id, hid=2, has_job=False, hda_checker=check_discarded, job_checker=upload_job_check)
self._check_imported_dataset(
history_id=imported_history_id,
hid=2,
has_job=False,
hda_checker=check_discarded,
job_checker=upload_job_check,
)
imported_content = self.dataset_populator.get_history_dataset_content(
history_id=imported_history_id,
@@ -267,8 +279,8 @@ class ImportExportTests(BaseHistories):
assert imported_content == "1 2 3\n"
def test_import_1901_histories(self):
f = open(self.test_data_resolver.get_filename("exports/1901_two_datasets.tgz"), 'rb')
import_data = dict(archive_source='', archive_file=f)
f = open(self.test_data_resolver.get_filename("exports/1901_two_datasets.tgz"), "rb")
import_data = dict(archive_source="", archive_file=f)
self._import_history_and_wait(import_data, "API Test History", wait_on_history_length=2)
def test_import_export_include_deleted(self):
@@ -278,7 +290,9 @@ class ImportExportTests(BaseHistories):
deleted_hda = self.dataset_populator.new_dataset(history_id, content="1 2 3", wait=True)
self.dataset_populator.delete_dataset(history_id, deleted_hda["id"])
imported_history_id = self._reimport_history(history_id, history_name, wait_on_history_length=2, export_kwds={"include_deleted": "True"})
imported_history_id = self._reimport_history(
history_id, history_name, wait_on_history_length=2, export_kwds={"include_deleted": "True"}
)
self._assert_history_length(imported_history_id, 2)
def upload_job_check(job):
@@ -290,7 +304,9 @@ class ImportExportTests(BaseHistories):
assert hda["purged"] is False, hda
self._check_imported_dataset(history_id=imported_history_id, hid=1, job_checker=upload_job_check)
self._check_imported_dataset(history_id=imported_history_id, hid=2, hda_checker=check_deleted_not_purged, job_checker=upload_job_check)
self._check_imported_dataset(
history_id=imported_history_id, hid=2, hda_checker=check_deleted_not_purged, job_checker=upload_job_check
)
imported_content = self.dataset_populator.get_history_dataset_content(
history_id=imported_history_id,
@@ -302,10 +318,12 @@ class ImportExportTests(BaseHistories):
def test_import_export_failed_job(self):
history_name = "for_export_include_failed_job"
history_id = self.dataset_populator.new_history(name=history_name)
self.dataset_populator.run_tool_raw('job_properties', inputs={'failbool': True}, history_id=history_id)
self.dataset_populator.run_tool_raw("job_properties", inputs={"failbool": True}, history_id=history_id)
self.dataset_populator.wait_for_history(history_id, assert_ok=False)
imported_history_id = self._reimport_history(history_id, history_name, assert_ok=False, wait_on_history_length=4, export_kwds={"include_deleted": "True"})
imported_history_id = self._reimport_history(
history_id, history_name, assert_ok=False, wait_on_history_length=4, export_kwds={"include_deleted": "True"}
)
self._assert_history_length(imported_history_id, 4)
def check_failed(hda_or_job):
@@ -314,12 +332,16 @@ class ImportExportTests(BaseHistories):
self.dataset_populator._summarize_history(imported_history_id)
self._check_imported_dataset(history_id=imported_history_id, hid=1, assert_ok=False, hda_checker=check_failed, job_checker=check_failed)
self._check_imported_dataset(
history_id=imported_history_id, hid=1, assert_ok=False, hda_checker=check_failed, job_checker=check_failed
)
def test_import_metadata_regeneration(self):
history_name = "for_import_metadata_regeneration"
history_id = self.dataset_populator.new_history(name=history_name)
self.dataset_populator.new_dataset(history_id, content=open(self.test_data_resolver.get_filename("1.bam"), 'rb'), file_type='bam', wait=True)
self.dataset_populator.new_dataset(
history_id, content=open(self.test_data_resolver.get_filename("1.bam"), "rb"), file_type="bam", wait=True
)
imported_history_id = self._reimport_history(history_id, history_name)
self._assert_history_length(imported_history_id, 1)
self._check_imported_dataset(history_id=imported_history_id, hid=1)
@@ -345,7 +367,9 @@ class ImportExportTests(BaseHistories):
def test_import_export_collection(self):
history_name = "for_export_with_collections"
history_id = self.dataset_populator.new_history(name=history_name)
self.dataset_collection_populator.create_list_in_history(history_id, contents=["Hello", "World"], direct_upload=True)
self.dataset_collection_populator.create_list_in_history(
history_id, contents=["Hello", "World"], direct_upload=True
)
imported_history_id = self._reimport_history(history_id, history_name, wait_on_history_length=3)
self._assert_history_length(imported_history_id, 3)
@@ -362,7 +386,9 @@ class ImportExportTests(BaseHistories):
assert element0["hid"] == 2
assert element1["hid"] == 3
self._check_imported_collection(imported_history_id, hid=1, collection_type="list", elements_checker=check_elements)
self._check_imported_collection(
imported_history_id, hid=1, collection_type="list", elements_checker=check_elements
)
def test_import_export_nested_collection(self):
history_name = "for_export_with_nested_collections"
@@ -382,15 +408,22 @@ class ImportExportTests(BaseHistories):
assert len(child_elements) == 2
assert element0["collection_type"] == "paired"
self._check_imported_collection(imported_history_id, hid=1, collection_type="list:paired", elements_checker=check_elements)
self._check_imported_collection(
imported_history_id, hid=1, collection_type="list:paired", elements_checker=check_elements
)
def _reimport_history(self, history_id, history_name, wait_on_history_length=None, assert_ok=True, export_kwds=None):
def _reimport_history(
self, history_id, history_name, wait_on_history_length=None, assert_ok=True, export_kwds=None
):
# Ensure the history is ready to go...
export_kwds = export_kwds or {}
self.dataset_populator.wait_for_history(history_id, assert_ok=assert_ok)
return self.dataset_populator.reimport_history(
history_id, history_name, wait_on_history_length=wait_on_history_length, export_kwds=export_kwds,
history_id,
history_name,
wait_on_history_length=wait_on_history_length,
export_kwds=export_kwds,
)
def _import_history_and_wait(self, import_data, history_name, wait_on_history_length=None):
@@ -408,7 +441,9 @@ class ImportExportTests(BaseHistories):
contents = contents_response.json()
assert len(contents) == n, contents
def _check_imported_dataset(self, history_id, hid, assert_ok=True, has_job=True, hda_checker=None, job_checker=None):
def _check_imported_dataset(
self, history_id, hid, assert_ok=True, has_job=True, hda_checker=None, job_checker=None
):
imported_dataset_metadata = self.dataset_populator.get_history_dataset_details(
history_id=history_id,
hid=hid,
@@ -428,8 +463,8 @@ class ImportExportTests(BaseHistories):
job_details = self.dataset_populator.get_job_details(job_id, full=True)
assert job_details.status_code == 200, job_details.content
job = job_details.json()
assert 'history_id' in job, job
assert job['history_id'] == history_id, job
assert "history_id" in job, job
assert job["history_id"] == history_id, job
if job_checker is not None:
job_checker(job)
@@ -451,7 +486,6 @@ class ImportExportTests(BaseHistories):
class ImportExportHistoryTestCase(ApiTestCase, ImportExportTests):
def setUp(self):
super().setUp()
self._set_up_populators()
@@ -495,10 +529,7 @@ class SharingHistoryTestCase(ApiTestCase, BaseHistories, SharingApiTests):
assert not sharing_response["users_shared_with"]
# Now we provide the share_option
payload = {
"user_ids": [target_user_id],
"share_option": "make_accessible_to_shared"
}
payload = {"user_ids": [target_user_id], "share_option": "make_accessible_to_shared"}
sharing_response = self._share_history_with_payload(history_id, payload)
assert sharing_response["users_shared_with"]
assert sharing_response["users_shared_with"][0]["id"] == target_user_id
@@ -531,10 +562,7 @@ class SharingHistoryTestCase(ApiTestCase, BaseHistories, SharingApiTests):
# Trying to change the permissions when sharing should fail
# because we don't have manage permissions
payload = {
"user_ids": [target_user_id],
"share_option": "make_public"
}
payload = {"user_ids": [target_user_id], "share_option": "make_public"}
sharing_response = self._share_history_with_payload(history_id, payload)
assert sharing_response["extra"]
assert sharing_response["extra"]["can_share"] is False
@@ -542,10 +570,7 @@ class SharingHistoryTestCase(ApiTestCase, BaseHistories, SharingApiTests):
assert not sharing_response["users_shared_with"]
# we can share if we don't try to make any permission changes
payload = {
"user_ids": [target_user_id],
"share_option": "no_changes"
}
payload = {"user_ids": [target_user_id], "share_option": "no_changes"}
sharing_response = self._share_history_with_payload(history_id, payload)
assert not sharing_response["errors"]
assert sharing_response["users_shared_with"]
+150 -124
View File
@@ -18,7 +18,6 @@ from ._framework import ApiTestCase
# TODO: Test anonymous access.
class HistoryContentsApiTestCase(ApiTestCase):
def setUp(self):
super().setUp()
self.dataset_populator = DatasetPopulator(self.galaxy_interactor)
@@ -115,7 +114,9 @@ class HistoryContentsApiTestCase(ApiTestCase):
user_id = self.dataset_populator.user_id()
with self._different_user():
different_user_id = self.dataset_populator.user_id()
combined_user_role = self.dataset_populator.create_role([user_id, different_user_id], description="role for testing permissions")
combined_user_role = self.dataset_populator.create_role(
[user_id, different_user_id], description="role for testing permissions"
)
payload = {
"access": [combined_user_role["id"]],
@@ -166,7 +167,7 @@ class HistoryContentsApiTestCase(ApiTestCase):
def _create_copy(self):
hda1 = self.dataset_populator.new_dataset(self.history_id)
create_data = dict(
source='hda',
source="hda",
content=hda1["id"],
)
second_history_id = self.dataset_populator.new_history()
@@ -177,7 +178,7 @@ class HistoryContentsApiTestCase(ApiTestCase):
def test_hda_copy(self):
response = self._create_copy()
assert self.__count_contents(response['history_id']) == 1
assert self.__count_contents(response["history_id"]) == 1
def test_inheritance_chain(self):
response = self._create_copy()
@@ -189,7 +190,7 @@ class HistoryContentsApiTestCase(ApiTestCase):
def test_library_copy(self):
ld = self.library_populator.new_library_dataset("lda_test_library")
create_data = dict(
source='library',
source="library",
content=ld["id"],
)
assert self.__count_contents(self.history_id) == 0
@@ -211,7 +212,7 @@ class HistoryContentsApiTestCase(ApiTestCase):
update_response = self._update(hda1["id"], dict(name="Updated Name"))
assert self.__show(hda1).json()["name"] == "Updated Name"
unicode_name = 'ржевский сапоги'
unicode_name = "ржевский сапоги"
update_response = self._update(hda1["id"], dict(name=unicode_name))
updated_hda = self.__show(hda1).json()
assert updated_hda["name"] == unicode_name, updated_hda
@@ -227,7 +228,7 @@ class HistoryContentsApiTestCase(ApiTestCase):
"dbkey": "?",
"annotation": None,
"info": "my info is",
"operation": "attributes"
"operation": "attributes",
}
update_response = self._set_edit_update(data)
# No key or anything supplied, expect a permission problem.
@@ -282,7 +283,9 @@ class HistoryContentsApiTestCase(ApiTestCase):
assert objects[0]["visible"] is False
# update both flags
payload = dict(items=[{"history_content_type": "dataset_collection", "id": hdca["id"]}], deleted=False, visible=True)
payload = dict(
items=[{"history_content_type": "dataset_collection", "id": hdca["id"]}], deleted=False, visible=True
)
update_response = self._update_batch(payload)
objects = update_response.json()
assert objects[0]["deleted"] is False
@@ -290,7 +293,7 @@ class HistoryContentsApiTestCase(ApiTestCase):
def test_update_type_failures(self):
hda1 = self._wait_for_new_hda()
update_response = self._update(hda1["id"], dict(deleted='not valid'))
update_response = self._update(hda1["id"], dict(deleted="not valid"))
self._assert_status_code_is(update_response, 400)
def _wait_for_new_hda(self):
@@ -325,7 +328,7 @@ class HistoryContentsApiTestCase(ApiTestCase):
def test_delete_anon(self):
with self._different_user(anon=True):
history_id = self._get(urllib.parse.urljoin(self.url, "history/current_history_json")).json()['id']
history_id = self._get(urllib.parse.urljoin(self.url, "history/current_history_json")).json()["id"]
hda1 = self.dataset_populator.new_dataset(history_id)
self.dataset_populator.wait_for_history(history_id)
assert str(self.__show(hda1).json()["deleted"]).lower() == "false"
@@ -338,24 +341,21 @@ class HistoryContentsApiTestCase(ApiTestCase):
with self._different_user(anon=True):
delete_response = self._delete(f"histories/{self.history_id}/contents/{hda1['id']}")
assert delete_response.status_code == 403
assert delete_response.json()['err_msg'] == 'HistoryDatasetAssociation is not owned by user'
assert delete_response.json()["err_msg"] == "HistoryDatasetAssociation is not owned by user"
def test_purge(self):
hda1 = self.dataset_populator.new_dataset(self.history_id)
self.dataset_populator.wait_for_history(self.history_id)
assert str(self.__show(hda1).json()["deleted"]).lower() == "false"
assert str(self.__show(hda1).json()["purged"]).lower() == "false"
data = {'purge': True}
data = {"purge": True}
delete_response = self._delete(f"histories/{self.history_id}/contents/{hda1['id']}", data=data, json=True)
assert delete_response.status_code < 300 # Something in the 200s :).
assert str(self.__show(hda1).json()["deleted"]).lower() == "true"
assert str(self.__show(hda1).json()["purged"]).lower() == "true"
def test_dataset_collection_creation_on_contents(self):
payload = self.dataset_collection_populator.create_pair_payload(
self.history_id,
type="dataset_collection"
)
payload = self.dataset_collection_populator.create_pair_payload(self.history_id, type="dataset_collection")
endpoint = f"histories/{self.history_id}/contents"
self._check_pair_creation(endpoint, payload)
@@ -368,30 +368,27 @@ class HistoryContentsApiTestCase(ApiTestCase):
def test_dataset_collection_create_from_exisiting_datasets_with_new_tags(self):
with self.dataset_populator.test_history() as history_id:
hda_id = self.dataset_populator.new_dataset(history_id, content="1 2 3")['id']
hda2_id = self.dataset_populator.new_dataset(history_id, content="1 2 3")['id']
update_response = self._update(hda2_id, dict(tags=['existing:tag']), history_id=history_id).json()
assert update_response['tags'] == ['existing:tag']
creation_payload = {'collection_type': 'list',
'history_id': history_id,
'element_identifiers': [{'id': hda_id,
'src': 'hda',
'name': 'element_id1',
'tags': ['my_new_tag']},
{'id': hda2_id,
'src': 'hda',
'name': 'element_id2',
'tags': ['another_new_tag']}
],
'type': 'dataset_collection',
'copy_elements': True}
hda_id = self.dataset_populator.new_dataset(history_id, content="1 2 3")["id"]
hda2_id = self.dataset_populator.new_dataset(history_id, content="1 2 3")["id"]
update_response = self._update(hda2_id, dict(tags=["existing:tag"]), history_id=history_id).json()
assert update_response["tags"] == ["existing:tag"]
creation_payload = {
"collection_type": "list",
"history_id": history_id,
"element_identifiers": [
{"id": hda_id, "src": "hda", "name": "element_id1", "tags": ["my_new_tag"]},
{"id": hda2_id, "src": "hda", "name": "element_id2", "tags": ["another_new_tag"]},
],
"type": "dataset_collection",
"copy_elements": True,
}
r = self._post(f"histories/{self.history_id}/contents", creation_payload, json=True).json()
assert r['elements'][0]['object']['id'] != hda_id, "HDA has not been copied"
assert len(r['elements'][0]['object']['tags']) == 1
assert r['elements'][0]['object']['tags'][0] == 'my_new_tag'
assert len(r['elements'][1]['object']['tags']) == 2, r['elements'][1]['object']['tags']
assert r["elements"][0]["object"]["id"] != hda_id, "HDA has not been copied"
assert len(r["elements"][0]["object"]["tags"]) == 1
assert r["elements"][0]["object"]["tags"][0] == "my_new_tag"
assert len(r["elements"][1]["object"]["tags"]) == 2, r["elements"][1]["object"]["tags"]
original_hda = self.dataset_populator.get_history_dataset_details(history_id=history_id, dataset_id=hda_id)
assert len(original_hda['tags']) == 0, original_hda['tags']
assert len(original_hda["tags"]) == 0, original_hda["tags"]
def _check_pair_creation(self, endpoint, payload):
pre_collection_count = self.__count_contents(type="dataset_collection")
@@ -431,10 +428,12 @@ class HistoryContentsApiTestCase(ApiTestCase):
@skip_without_tool("collection_creates_list")
def test_jobs_summary_simple_hdca(self):
create_response = self.dataset_collection_populator.create_list_in_history(self.history_id, contents=["a\nb\nc\nd", "e\nf\ng\nh"])
create_response = self.dataset_collection_populator.create_list_in_history(
self.history_id, contents=["a\nb\nc\nd", "e\nf\ng\nh"]
)
hdca_id = create_response.json()["id"]
run = self.dataset_populator.run_collection_creates_list(self.history_id, hdca_id)
collections = run['output_collections']
collections = run["output_collections"]
collection = collections[0]
jobs_summary_url = f"histories/{self.history_id}/contents/dataset_collections/{collection['id']}/jobs_summary"
jobs_summary_response = self._get(jobs_summary_url)
@@ -444,14 +443,16 @@ class HistoryContentsApiTestCase(ApiTestCase):
@skip_without_tool("cat1")
def test_jobs_summary_implicit_hdca(self):
create_response = self.dataset_collection_populator.create_pair_in_history(self.history_id, contents=["123", "456"])
create_response = self.dataset_collection_populator.create_pair_in_history(
self.history_id, contents=["123", "456"]
)
hdca_id = create_response.json()["id"]
inputs = {
"input1": {'batch': True, 'values': [{'src': 'hdca', 'id': hdca_id}]},
"input1": {"batch": True, "values": [{"src": "hdca", "id": hdca_id}]},
}
run = self.dataset_populator.run_tool("cat1", inputs=inputs, history_id=self.history_id)
self.dataset_populator.wait_for_history_jobs(self.history_id)
collections = run['implicit_collections']
collections = run["implicit_collections"]
collection = collections[0]
jobs_summary_url = f"histories/{self.history_id}/contents/dataset_collections/{collection['id']}/jobs_summary"
jobs_summary_response = self._get(jobs_summary_url)
@@ -462,17 +463,16 @@ class HistoryContentsApiTestCase(ApiTestCase):
assert states.get("ok") == 2, states
def test_dataset_collection_hide_originals(self):
payload = self.dataset_collection_populator.create_pair_payload(
self.history_id,
type="dataset_collection"
)
payload = self.dataset_collection_populator.create_pair_payload(self.history_id, type="dataset_collection")
payload["hide_source_items"] = True
dataset_collection_response = self._post(f"histories/{self.history_id}/contents", payload, json=True)
self.__check_create_collection_response(dataset_collection_response)
contents_response = self._get(f"histories/{self.history_id}/contents")
datasets = [d for d in contents_response.json() if d["history_content_type"] == "dataset" and d["hid"] in [1, 2]]
datasets = [
d for d in contents_response.json() if d["history_content_type"] == "dataset" and d["hid"] in [1, 2]
]
# Assert two datasets in source were hidden.
assert len(datasets) == 2
assert not datasets[0]["visible"]
@@ -490,23 +490,14 @@ class HistoryContentsApiTestCase(ApiTestCase):
def test_update_batch_dataset_collection(self):
hdca = self._create_pair_collection()
body = {
"items": [{
"history_content_type": "dataset_collection",
"id": hdca["id"]
}],
"name": "newnameforpair"
}
body = {"items": [{"history_content_type": "dataset_collection", "id": hdca["id"]}], "name": "newnameforpair"}
update_response = self._put(f"histories/{self.history_id}/contents", data=body, json=True)
self._assert_status_code_is(update_response, 200)
show_response = self.__show(hdca)
assert str(show_response.json()["name"]) == "newnameforpair"
def _create_pair_collection(self):
payload = self.dataset_collection_populator.create_pair_payload(
self.history_id,
type="dataset_collection"
)
payload = self.dataset_collection_populator.create_pair_payload(self.history_id, type="dataset_collection")
dataset_collection_response = self._post(f"histories/{self.history_id}/contents", payload, json=True)
self._assert_status_code_is(dataset_collection_response, 200)
hdca = dataset_collection_response.json()
@@ -517,11 +508,13 @@ class HistoryContentsApiTestCase(ApiTestCase):
hdca_id = hdca["id"]
second_history_id = self.dataset_populator.new_history()
create_data = dict(
source='hdca',
source="hdca",
content=hdca_id,
)
assert len(self._get(f"histories/{second_history_id}/contents/dataset_collections").json()) == 0
create_response = self._post(f"histories/{second_history_id}/contents/dataset_collections", create_data, json=True)
create_response = self._post(
f"histories/{second_history_id}/contents/dataset_collections", create_data, json=True
)
self.__check_create_collection_response(create_response)
contents = self._get(f"histories/{second_history_id}/contents/dataset_collections").json()
assert len(contents) == 1
@@ -534,10 +527,12 @@ class HistoryContentsApiTestCase(ApiTestCase):
hdca_id = hdca["id"]
assert hdca["elements"][0]["object"]["metadata_dbkey"] == "?"
assert hdca["elements"][0]["object"]["genome_build"] == "?"
create_data = {'source': 'hdca', 'content': hdca_id, 'dbkey': 'hg19'}
create_response = self._post(f"histories/{self.history_id}/contents/dataset_collections", create_data, json=True)
create_data = {"source": "hdca", "content": hdca_id, "dbkey": "hg19"}
create_response = self._post(
f"histories/{self.history_id}/contents/dataset_collections", create_data, json=True
)
collection = self.__check_create_collection_response(create_response)
new_forward = collection['elements'][0]['object']
new_forward = collection["elements"][0]["object"]
assert new_forward["metadata_dbkey"] == "hg19"
assert new_forward["genome_build"] == "hg19"
@@ -546,12 +541,14 @@ class HistoryContentsApiTestCase(ApiTestCase):
hdca_id = hdca["id"]
second_history_id = self.dataset_populator.new_history()
create_data = dict(
source='hdca',
source="hdca",
content=hdca_id,
copy_elements=True,
)
assert len(self._get(f"histories/{second_history_id}/contents/dataset_collections").json()) == 0
create_response = self._post(f"histories/{second_history_id}/contents/dataset_collections", create_data, json=True)
create_response = self._post(
f"histories/{second_history_id}/contents/dataset_collections", create_data, json=True
)
self.__check_create_collection_response(create_response)
contents = self._get(f"histories/{second_history_id}/contents/dataset_collections").json()
@@ -592,10 +589,12 @@ class HistoryContentsApiTestCase(ApiTestCase):
assert hda["hda_ldda"] == "hda"
assert hda["history_content_type"] == "dataset"
assert hda["copied_from_ldda_id"] == ldda_id
assert hda['history_id'] == history_id
assert hda["history_id"] == history_id
def test_hdca_from_inaccessible_library_datasets(self):
library, library_dataset = self.library_populator.new_library_dataset_in_private_library("HDCACreateInaccesibleLibrary")
library, library_dataset = self.library_populator.new_library_dataset_in_private_library(
"HDCACreateInaccesibleLibrary"
)
ldda_id = library_dataset["id"]
element_identifiers = [{"name": "el1", "src": "ldda", "id": ldda_id}]
create_data = dict(
@@ -607,7 +606,9 @@ class HistoryContentsApiTestCase(ApiTestCase):
)
with self._different_user():
second_history_id = self.dataset_populator.new_history()
create_response = self._post(f"histories/{second_history_id}/contents/dataset_collections", create_data, json=True)
create_response = self._post(
f"histories/{second_history_id}/contents/dataset_collections", create_data, json=True
)
self._assert_status_code_is(create_response, 403)
def __check_create_collection_response(self, response):
@@ -617,7 +618,9 @@ class HistoryContentsApiTestCase(ApiTestCase):
return dataset_collection
def __show(self, contents):
show_response = self._get(f"histories/{self.history_id}/contents/{contents['history_content_type']}s/{contents['id']}")
show_response = self._get(
f"histories/{self.history_id}/contents/{contents['history_content_type']}s/{contents['id']}"
)
return show_response
def __count_contents(self, history_id=None, **kwds):
@@ -643,15 +646,17 @@ class HistoryContentsApiTestCase(ApiTestCase):
assert input_hda["id"] == query_hda["id"]
def test_job_state_summary_field(self):
create_response = self.dataset_collection_populator.create_pair_in_history(self.history_id, contents=["123", "456"])
create_response = self.dataset_collection_populator.create_pair_in_history(
self.history_id, contents=["123", "456"]
)
self._assert_status_code_is(create_response, 200)
contents_response = self._get(f"histories/{self.history_id}/contents?v=dev&keys=job_state_summary&view=summary")
self._assert_status_code_is(contents_response, 200)
contents = contents_response.json()
for c in filter(lambda c: c['history_content_type'] == 'dataset_collection', contents):
for c in filter(lambda c: c["history_content_type"] == "dataset_collection", contents):
assert isinstance(c, dict)
assert 'job_state_summary' in c
assert isinstance(c['job_state_summary'], dict)
assert "job_state_summary" in c
assert isinstance(c["job_state_summary"], dict)
def _get_content(self, history_id, update_time):
return self._get(f"/api/histories/{history_id}/contents/near/100/100?update_time-gt={update_time}").json()
@@ -669,7 +674,7 @@ class HistoryContentsApiTestCase(ApiTestCase):
def test_history_contents_near_with_since(self):
with self.dataset_populator.test_history() as history_id:
original_history = self._get(f"/api/histories/{history_id}").json()
original_history_stamp = original_history['update_time']
original_history_stamp = original_history["update_time"]
# check empty contents, with no since flag, should return an empty 200 result
history_contents = self._get(f"/api/histories/{history_id}/contents/near/100/100")
@@ -677,7 +682,9 @@ class HistoryContentsApiTestCase(ApiTestCase):
assert len(history_contents.json()) == 0
# adding a since parameter, should return a 204 if history has not changed at all
history_contents = self._get(f"/api/histories/{history_id}/contents/near/100/100?since={original_history_stamp}")
history_contents = self._get(
f"/api/histories/{history_id}/contents/near/100/100?since={original_history_stamp}"
)
assert history_contents.status_code == 204
# add some stuff
@@ -691,19 +698,21 @@ class HistoryContentsApiTestCase(ApiTestCase):
# check to make sure the history date has actually changed due to changing the contents
changed_history = self._get(f"/api/histories/{history_id}").json()
changed_history_stamp = changed_history['update_time']
changed_history_stamp = changed_history["update_time"]
assert original_history_stamp != changed_history_stamp
# a repeated contents request with since=original_history_stamp should now return data
# because we have added datasets and the update_time should have been changed
changed_content = self._get(f"/api/histories/{history_id}/contents/near/100/100?since={original_history_stamp}")
changed_content = self._get(
f"/api/histories/{history_id}/contents/near/100/100?since={original_history_stamp}"
)
assert changed_content.status_code == 200
assert len(changed_content.json()) == 4
def test_history_contents_near_since_with_standard_iso8601_date(self):
with self.dataset_populator.test_history() as history_id:
original_history = self._get(f"/api/histories/{history_id}").json()
original_history_stamp = original_history['update_time']
original_history_stamp = original_history["update_time"]
# this is the standard date format that javascript will emit using .toISOString(), it
# should be the expected date format for any modern api
@@ -711,25 +720,27 @@ class HistoryContentsApiTestCase(ApiTestCase):
# checking to make sure that the same exact history.update_time returns a "not changed"
# result after date parsing
valid_iso8601_date = original_history_stamp + 'Z'
valid_iso8601_date = original_history_stamp + "Z"
encoded_valid_date = urllib.parse.quote_plus(valid_iso8601_date)
history_contents = self._get(f"/api/histories/{history_id}/contents/near/100/100?since={encoded_valid_date}")
history_contents = self._get(
f"/api/histories/{history_id}/contents/near/100/100?since={encoded_valid_date}"
)
assert history_contents.status_code == 204
# test parsing for other standard is08601 formats
sample_formats = ['2021-08-26T15:53:02+00:00', '2021-08-26T15:53:02Z', '2002-10-10T12:00:00-05:00']
sample_formats = ["2021-08-26T15:53:02+00:00", "2021-08-26T15:53:02Z", "2002-10-10T12:00:00-05:00"]
for date_str in sample_formats:
encoded_date = urllib.parse.quote_plus(date_str) # handles pluses, minuses
history_contents = self._get(f"/api/histories/{history_id}/contents/near/100/100?since={encoded_date}")
self._assert_status_code_is_ok(history_contents)
@skip_without_tool('cat_data_and_sleep')
@skip_without_tool("cat_data_and_sleep")
def test_history_contents_near_with_update_time_implicit_collection(self):
with self.dataset_populator.test_history() as history_id:
hdca_id = self.dataset_collection_populator.create_list_in_history(history_id=history_id).json()['id']
hdca_id = self.dataset_collection_populator.create_list_in_history(history_id=history_id).json()["id"]
self.dataset_populator.wait_for_history(history_id)
inputs = {
"input1": {'batch': True, 'values': [{"src": "hdca", "id": hdca_id}]},
"input1": {"batch": True, "values": [{"src": "hdca", "id": hdca_id}]},
"sleep_time": 2,
}
response = self.dataset_populator.run_tool(
@@ -738,31 +749,43 @@ class HistoryContentsApiTestCase(ApiTestCase):
history_id,
)
update_time = datetime.utcnow().isoformat()
collection_id = response['implicit_collections'][0]['id']
collection_id = response["implicit_collections"][0]["id"]
for _ in range(20):
time.sleep(1)
update = self._get_content(history_id, update_time=update_time)
if any(c for c in update if c['history_content_type'] == 'dataset_collection' and c['job_state_summary']['ok'] == 3):
if any(
c
for c in update
if c["history_content_type"] == "dataset_collection" and c["job_state_summary"]["ok"] == 3
):
return
raise Exception(f"History content update time query did not include final update for implicit collection {collection_id}")
raise Exception(
f"History content update time query did not include final update for implicit collection {collection_id}"
)
@skip_without_tool('collection_creates_dynamic_nested')
@skip_without_tool("collection_creates_dynamic_nested")
def test_history_contents_near_with_update_time_explicit_collection(self):
with self.dataset_populator.test_history() as history_id:
inputs = {'foo': 'bar', 'sleep_time': 2}
inputs = {"foo": "bar", "sleep_time": 2}
response = self.dataset_populator.run_tool(
"collection_creates_dynamic_nested",
inputs,
history_id,
)
update_time = datetime.utcnow().isoformat()
collection_id = response['output_collections'][0]['id']
collection_id = response["output_collections"][0]["id"]
for _ in range(20):
time.sleep(1)
update = self._get_content(history_id, update_time=update_time)
if any(c for c in update if c['history_content_type'] == 'dataset_collection' and c['populated_state'] == 'ok'):
if any(
c
for c in update
if c["history_content_type"] == "dataset_collection" and c["populated_state"] == "ok"
):
return
raise Exception(f"History content update time query did not include populated_state update for dynamic nested collection {collection_id}")
raise Exception(
f"History content update time query did not include populated_state update for dynamic nested collection {collection_id}"
)
def test_index_filter_by_type(self):
history_id = self.dataset_populator.new_history()
@@ -806,7 +829,9 @@ class HistoryContentsApiTestCase(ApiTestCase):
self._assert_status_code_is_ok(create_homogeneous_response)
def _assert_collection_has_expected_elements_datatypes(self, history_id, collection_name, expected_datatypes):
contents_response = self._get(f"histories/{history_id}/contents?v=dev&view=betawebclient&q=name-eq&qv={collection_name}")
contents_response = self._get(
f"histories/{history_id}/contents?v=dev&view=betawebclient&q=name-eq&qv={collection_name}"
)
self._assert_status_code_is(contents_response, 200)
collection = contents_response.json()[0]
self.assertCountEqual(collection["elements_datatypes"], expected_datatypes)
@@ -816,6 +841,7 @@ class HistoryContentsApiNearTestCase(ApiTestCase):
"""
Test the /api/histories/{history_id}/contents/{direction}/{hid}/{limit} endpoint.
"""
NEAR = DirectionOptions.near
BEFORE = DirectionOptions.before
AFTER = DirectionOptions.after
@@ -838,87 +864,87 @@ class HistoryContentsApiNearTestCase(ApiTestCase):
self._create_list_in_history(history_id)
result = self._get_content(history_id, self.NEAR, hid=1)
assert len(result) == 8
assert result[0]['hid'] == 8
assert result[1]['hid'] == 7
assert result[2]['hid'] == 6
assert result[3]['hid'] == 5
assert result[4]['hid'] == 4
assert result[5]['hid'] == 3
assert result[6]['hid'] == 2
assert result[7]['hid'] == 1
assert result[0]["hid"] == 8
assert result[1]["hid"] == 7
assert result[2]["hid"] == 6
assert result[3]["hid"] == 5
assert result[4]["hid"] == 4
assert result[5]["hid"] == 3
assert result[6]["hid"] == 2
assert result[7]["hid"] == 1
def test_near_even_limit(self):
with self.dataset_populator.test_history() as history_id:
self._create_list_in_history(history_id)
result = self._get_content(history_id, self.NEAR, hid=5, limit=3)
assert len(result) == 3
assert result[0]['hid'] == 6 # hid + 1
assert result[1]['hid'] == 5 # hid
assert result[2]['hid'] == 4 # hid - 1
assert result[0]["hid"] == 6 # hid + 1
assert result[1]["hid"] == 5 # hid
assert result[2]["hid"] == 4 # hid - 1
def test_near_odd_limit(self):
with self.dataset_populator.test_history() as history_id:
self._create_list_in_history(history_id)
result = self._get_content(history_id, self.NEAR, hid=5, limit=4)
assert len(result) == 4
assert result[0]['hid'] == 7 # hid + 2
assert result[1]['hid'] == 6 # hid + 1
assert result[2]['hid'] == 5 # hid
assert result[3]['hid'] == 4 # hid - 1
assert result[0]["hid"] == 7 # hid + 2
assert result[1]["hid"] == 6 # hid + 1
assert result[2]["hid"] == 5 # hid
assert result[3]["hid"] == 4 # hid - 1
def test_near_less_than_before_limit(self): # n before < limit // 2
with self.dataset_populator.test_history() as history_id:
self._create_list_in_history(history_id)
result = self._get_content(history_id, self.NEAR, hid=1, limit=3)
assert len(result) == 2
assert result[0]['hid'] == 2 # hid + 1
assert result[1]['hid'] == 1 # hid (there's nothing before hid=1)
assert result[0]["hid"] == 2 # hid + 1
assert result[1]["hid"] == 1 # hid (there's nothing before hid=1)
def test_near_less_than_after_limit(self): # n after < limit // 2 + 1
with self.dataset_populator.test_history() as history_id:
self._create_list_in_history(history_id)
result = self._get_content(history_id, self.NEAR, hid=8, limit=3)
assert len(result) == 2
assert result[0]['hid'] == 8 # hid (there's nothing after hid=8)
assert result[1]['hid'] == 7 # hid - 1
assert result[0]["hid"] == 8 # hid (there's nothing after hid=8)
assert result[1]["hid"] == 7 # hid - 1
def test_near_less_than_before_and_after_limit(self):
with self.dataset_populator.test_history() as history_id:
self._create_list_in_history(history_id, n=1)
result = self._get_content(history_id, self.NEAR, hid=2, limit=10)
assert len(result) == 4
assert result[0]['hid'] == 4 # hid + 2 (can't go after hid=4)
assert result[1]['hid'] == 3 # hid + 1
assert result[2]['hid'] == 2 # hid
assert result[3]['hid'] == 1 # hid - 1 (can't go before hid=1)
assert result[0]["hid"] == 4 # hid + 2 (can't go after hid=4)
assert result[1]["hid"] == 3 # hid + 1
assert result[2]["hid"] == 2 # hid
assert result[3]["hid"] == 1 # hid - 1 (can't go before hid=1)
def test_before(self):
with self.dataset_populator.test_history() as history_id:
self._create_list_in_history(history_id)
result = self._get_content(history_id, self.BEFORE, hid=5, limit=3)
assert len(result) == 3
assert result[0]['hid'] == 4 # hid - 1
assert result[1]['hid'] == 3 # hid - 2
assert result[2]['hid'] == 2 # hid - 3
assert result[0]["hid"] == 4 # hid - 1
assert result[1]["hid"] == 3 # hid - 2
assert result[2]["hid"] == 2 # hid - 3
def test_before_less_than_limit(self):
with self.dataset_populator.test_history() as history_id:
self._create_list_in_history(history_id)
result = self._get_content(history_id, self.BEFORE, hid=2, limit=3)
assert len(result) == 1
assert result[0]['hid'] == 1 # hid - 1
assert result[0]["hid"] == 1 # hid - 1
def test_after(self):
with self.dataset_populator.test_history() as history_id:
self._create_list_in_history(history_id)
result = self._get_content(history_id, self.AFTER, hid=5, limit=2)
assert len(result) == 2
assert result[0]['hid'] == 7 # hid + 2 (hid + 3 not included: tests reversed order)
assert result[1]['hid'] == 6 # hid + 1
assert result[0]["hid"] == 7 # hid + 2 (hid + 3 not included: tests reversed order)
assert result[1]["hid"] == 6 # hid + 1
def test_after_less_than_limit(self):
with self.dataset_populator.test_history() as history_id:
self._create_list_in_history(history_id)
result = self._get_content(history_id, self.AFTER, hid=7, limit=3)
assert len(result) == 1
assert result[0]['hid'] == 8 # hid + 1
assert result[0]["hid"] == 8 # hid + 1
+67 -53
View File
@@ -1,9 +1,15 @@
import functools
import operator
from collections import namedtuple
from json import dumps, loads
from json import (
dumps,
loads,
)
from galaxy_test.base.populators import skip_without_tool, summarize_instance_history_on_error
from galaxy_test.base.populators import (
skip_without_tool,
summarize_instance_history_on_error,
)
from .test_workflows import BaseWorkflowsApiTestCase
@@ -46,7 +52,7 @@ class WorkflowExtractionApiTestCase(BaseWorkflowsApiTestCase):
for old_dataset in old_contents:
self.__copy_content_to_history(self.history_id, old_dataset)
new_contents = self._history_contents()
input_hids = [c["hid"] for c in new_contents[(offset + 0):(offset + 2)]]
input_hids = [c["hid"] for c in new_contents[(offset + 0) : (offset + 2)]]
cat1_job_id = self.__job_id(self.history_id, new_contents[(offset + 2)]["id"])
def reimport_jobs_ids(new_history_id):
@@ -69,7 +75,7 @@ class WorkflowExtractionApiTestCase(BaseWorkflowsApiTestCase):
for old_dataset in old_contents:
self.__copy_content_to_history(self.history_id, old_dataset)
new_contents = self._history_contents()
input_hids = [c["hid"] for c in new_contents[(offset + 0):(offset + 2)]]
input_hids = [c["hid"] for c in new_contents[(offset + 0) : (offset + 2)]]
def reimport_jobs_ids(new_history_id):
return [j["id"] for j in self.dataset_populator.history_jobs(new_history_id) if j["tool_id"] == "cat1"]
@@ -110,7 +116,10 @@ class WorkflowExtractionApiTestCase(BaseWorkflowsApiTestCase):
def test_extract_copied_mapping_from_history_reimported(self):
import unittest
raise unittest.SkipTest("Mapping connection for copied collections not yet implemented in history import/export")
raise unittest.SkipTest(
"Mapping connection for copied collections not yet implemented in history import/export"
)
old_history_id = self.dataset_populator.new_history()
hdca, job_id1, job_id2 = self.__run_random_lines_mapped_over_singleton(old_history_id)
@@ -120,7 +129,9 @@ class WorkflowExtractionApiTestCase(BaseWorkflowsApiTestCase):
self.__copy_content_to_history(self.history_id, old_content)
def reimport_jobs_ids(new_history_id):
rval = [j["id"] for j in self.dataset_populator.history_jobs(new_history_id) if j["tool_id"] == "random_lines1"]
rval = [
j["id"] for j in self.dataset_populator.history_jobs(new_history_id) if j["tool_id"] == "random_lines1"
]
assert len(rval) == 2
print(rval)
return rval
@@ -139,12 +150,11 @@ class WorkflowExtractionApiTestCase(BaseWorkflowsApiTestCase):
@skip_without_tool("random_lines1")
@skip_without_tool("multi_data_param")
def test_extract_reduction_from_history(self):
hdca = self.dataset_collection_populator.create_pair_in_history(self.history_id, contents=["1 2 3\n4 5 6", "7 8 9\n10 11 10"]).json()
hdca = self.dataset_collection_populator.create_pair_in_history(
self.history_id, contents=["1 2 3\n4 5 6", "7 8 9\n10 11 10"]
).json()
hdca_id = hdca["id"]
inputs1 = {
"input": {"batch": True, "values": [{"src": "hdca", "id": hdca_id}]},
"num_lines": 2
}
inputs1 = {"input": {"batch": True, "values": [{"src": "hdca", "id": hdca_id}]}, "num_lines": 2}
implicit_hdca1, job_id1 = self._run_tool_get_collection_and_job_id(self.history_id, "random_lines1", inputs1)
inputs2 = {
"f1": {"src": "hdca", "id": implicit_hdca1["id"]},
@@ -180,7 +190,8 @@ class WorkflowExtractionApiTestCase(BaseWorkflowsApiTestCase):
@skip_without_tool("collection_paired_test")
def test_extract_workflows_with_dataset_collections(self):
jobs_summary = self._run_workflow("""
jobs_summary = self._run_workflow(
"""
class: GalaxyWorkflow
steps:
- label: text_input1
@@ -192,7 +203,8 @@ steps:
test_data:
text_input1:
collection_type: paired
""")
"""
)
job_id = self._job_id_for_tool(jobs_summary.jobs, "collection_paired_test")
downloaded_workflow = self._extract_and_download_workflow(
reimport_as="extract_from_history_with_basic_collections",
@@ -205,7 +217,7 @@ test_data:
verify_connected=True,
data_input_count=0,
data_collection_input_count=1,
tool_ids=["collection_paired_test"]
tool_ids=["collection_paired_test"],
)
collection_step = self._get_steps_of_type(downloaded_workflow, "data_collection_input", expected_len=1)[0]
@@ -214,7 +226,8 @@ test_data:
@skip_without_tool("cat_collection")
def test_subcollection_mapping(self):
jobs_summary = self._run_workflow("""
jobs_summary = self._run_workflow(
"""
class: GalaxyWorkflow
steps:
- label: text_input1
@@ -231,7 +244,8 @@ steps:
test_data:
text_input1:
collection_type: "list:paired"
""")
"""
)
job1_id = self._job_id_for_tool(jobs_summary.jobs, "cat1")
job2_id = self._job_id_for_tool(jobs_summary.jobs, "cat_collection")
downloaded_workflow = self._extract_and_download_workflow(
@@ -255,7 +269,8 @@ test_data:
@skip_without_tool("cat_list")
@skip_without_tool("collection_creates_dynamic_nested")
def test_subcollection_reduction(self):
jobs_summary = self._run_workflow("""
jobs_summary = self._run_workflow(
"""
class: GalaxyWorkflow
steps:
creates_nested_list:
@@ -264,7 +279,8 @@ steps:
tool_id: cat_list
in:
input1: creates_nested_list/list_output
""")
"""
)
job1_id = self._job_id_for_tool(jobs_summary.jobs, "cat_list")
job2_id = self._job_id_for_tool(jobs_summary.jobs, "collection_creates_dynamic_nested")
self._extract_and_download_workflow(
@@ -277,7 +293,8 @@ steps:
@skip_without_tool("collection_split_on_column")
def test_extract_workflow_with_output_collections(self):
jobs_summary = self._run_workflow("""
jobs_summary = self._run_workflow(
"""
class: GalaxyWorkflow
steps:
- label: text_input1
@@ -304,7 +321,8 @@ steps:
test_data:
text_input1: "samp1\t10.0\nsamp2\t20.0\n"
text_input2: "samp1\t30.0\nsamp2\t40.0\n"
""")
"""
)
tool_ids = ["cat1", "collection_split_on_column", "cat_list"]
job_ids = [functools.partial(self._job_id_for_tool, jobs_summary.jobs)(_) for _ in tool_ids]
downloaded_workflow = self._extract_and_download_workflow(
@@ -324,7 +342,8 @@ test_data:
@skip_without_tool("collection_creates_pair")
@summarize_instance_history_on_error
def test_extract_with_mapped_output_collections(self):
jobs_summary = self._run_workflow("""
jobs_summary = self._run_workflow(
"""
class: GalaxyWorkflow
steps:
- label: text_input1
@@ -356,7 +375,8 @@ test_data:
content: "samp1\t10.0\nsamp2\t20.0\n"
- identifier: samp2
content: "samp1\t30.0\nsamp2\t40.0\n"
""")
"""
)
tool_ids = ["cat1", "collection_creates_pair", "cat_collection", "cat_list"]
job_ids = [functools.partial(self._job_id_for_tool, jobs_summary.jobs)(_) for _ in tool_ids]
downloaded_workflow = self._extract_and_download_workflow(
@@ -385,32 +405,22 @@ test_data:
return tool_jobs[-1]
def __run_random_lines_mapped_over_pair(self, history_id):
hdca = self.dataset_collection_populator.create_pair_in_history(history_id, contents=["1 2 3\n4 5 6", "7 8 9\n10 11 10"]).json()
hdca = self.dataset_collection_populator.create_pair_in_history(
history_id, contents=["1 2 3\n4 5 6", "7 8 9\n10 11 10"]
).json()
hdca_id = hdca["id"]
inputs1 = {
"input": {"batch": True, "values": [{"src": "hdca", "id": hdca_id}]},
"num_lines": 2
}
inputs1 = {"input": {"batch": True, "values": [{"src": "hdca", "id": hdca_id}]}, "num_lines": 2}
implicit_hdca1, job_id1 = self._run_tool_get_collection_and_job_id(history_id, "random_lines1", inputs1)
inputs2 = {
"input": {"batch": True, "values": [{"src": "hdca", "id": implicit_hdca1["id"]}]},
"num_lines": 1
}
inputs2 = {"input": {"batch": True, "values": [{"src": "hdca", "id": implicit_hdca1["id"]}]}, "num_lines": 1}
_, job_id2 = self._run_tool_get_collection_and_job_id(history_id, "random_lines1", inputs2)
return hdca, job_id1, job_id2
def __run_random_lines_mapped_over_singleton(self, history_id):
hdca = self.dataset_collection_populator.create_list_in_history(history_id, contents=["1 2 3\n4 5 6"]).json()
hdca_id = hdca["id"]
inputs1 = {
"input": {"batch": True, "values": [{"src": "hdca", "id": hdca_id}]},
"num_lines": 2
}
inputs1 = {"input": {"batch": True, "values": [{"src": "hdca", "id": hdca_id}]}, "num_lines": 2}
implicit_hdca1, job_id1 = self._run_tool_get_collection_and_job_id(history_id, "random_lines1", inputs1)
inputs2 = {
"input": {"batch": True, "values": [{"src": "hdca", "id": implicit_hdca1["id"]}]},
"num_lines": 1
}
inputs2 = {"input": {"batch": True, "values": [{"src": "hdca", "id": implicit_hdca1["id"]}]}, "num_lines": 1}
_, job_id2 = self._run_tool_get_collection_and_job_id(history_id, "random_lines1", inputs2)
return hdca, job_id1, job_id2
@@ -450,17 +460,11 @@ test_data:
def __copy_content_to_history(self, history_id, content):
if content["history_content_type"] == "dataset":
payload = dict(
source="hda",
content=content["id"]
)
payload = dict(source="hda", content=content["id"])
response = self._post(f"histories/{history_id}/contents/datasets", payload, json=True)
else:
payload = dict(
source="hdca",
content=content["id"]
)
payload = dict(source="hdca", content=content["id"])
response = self._post(f"histories/{history_id}/contents/dataset_collections", payload, json=True)
self._assert_status_code_is(response, 200)
return response.json()
@@ -499,11 +503,15 @@ test_data:
history_length = self.dataset_populator.history_length(history_id)
new_history_id = self.dataset_populator.reimport_history(
history_id, history_name, wait_on_history_length=history_length, export_kwds={},
history_id,
history_name,
wait_on_history_length=history_length,
export_kwds={},
)
# wait a little more for those jobs, todo fix to wait for history imported false or
# for a specific number of jobs...
import time
time.sleep(1)
if "reimport_jobs_ids" in extract_payload:
@@ -513,8 +521,14 @@ test_data:
# Assume no copying or anything so just straight map job ids by index.
# Jobs are created after datasets, need to also wait on those...
history_jobs = [j for j in self.dataset_populator.history_jobs(history_id) if j["tool_id"] != "__EXPORT_HISTORY__"]
new_history_jobs = [j for j in self.dataset_populator.history_jobs(new_history_id) if j["tool_id"] != "__EXPORT_HISTORY__"]
history_jobs = [
j for j in self.dataset_populator.history_jobs(history_id) if j["tool_id"] != "__EXPORT_HISTORY__"
]
new_history_jobs = [
j
for j in self.dataset_populator.history_jobs(new_history_id)
if j["tool_id"] != "__EXPORT_HISTORY__"
]
history_job_ids = [j["id"] for j in history_jobs]
new_history_job_ids = [j["id"] for j in new_history_jobs]
@@ -592,7 +606,7 @@ test_data:
data_collection_input_count=None,
tool_ids=None,
):
steps = workflow['steps']
steps = workflow["steps"]
if step_count is not None:
assert len(steps) == step_count
@@ -612,7 +626,7 @@ test_data:
disconnected_inputs = []
for value in steps.values():
if value['type'] == "tool":
if value["type"] == "tool":
input_connections = value["input_connections"]
if not input_connections:
disconnected_inputs.append(value)
@@ -623,4 +637,4 @@ test_data:
raise AssertionError(message)
RunJobsSummary = namedtuple('RunJobsSummary', ['history_id', 'workflow_id', 'inputs', 'jobs'])
RunJobsSummary = namedtuple("RunJobsSummary", ["history_id", "workflow_id", "inputs", "jobs"])
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+5 -7
View File
@@ -1,11 +1,9 @@
__version__ = "22.1.0rc1"
__version__ = "22.5.0.dev0"
PROJECT_NAME = "galaxy-app"
PROJECT_OWNER = PROJECT_USERAME = "galaxyproject"
PROJECT_URL = "https://github.com/galaxyproject/galaxy"
PROJECT_AUTHOR = 'Galaxy Project and Community'
PROJECT_DESCRIPTION = 'Galaxy Application (backend)'
PROJECT_EMAIL = 'galaxy-committers@lists.galaxyproject.org'
RAW_CONTENT_URL = "https://raw.github.com/{}/{}/master/".format(
PROJECT_USERAME, PROJECT_NAME
)
PROJECT_AUTHOR = "Galaxy Project and Community"
PROJECT_DESCRIPTION = "Galaxy Application (backend)"
PROJECT_EMAIL = "galaxy-committers@lists.galaxyproject.org"
RAW_CONTENT_URL = "https://raw.github.com/{}/{}/master/".format(PROJECT_USERAME, PROJECT_NAME)
+5 -7
View File
@@ -1,11 +1,9 @@
__version__ = "22.1.0rc1"
__version__ = "22.5.0.dev0"
PROJECT_NAME = "galaxy-auth"
PROJECT_OWNER = PROJECT_USERAME = "galaxyproject"
PROJECT_URL = "https://github.com/galaxyproject/galaxy"
PROJECT_AUTHOR = 'Galaxy Project and Community'
PROJECT_DESCRIPTION = 'Galaxy Auth Framework and Implementations'
PROJECT_EMAIL = 'galaxy-committers@lists.galaxyproject.org'
RAW_CONTENT_URL = "https://raw.github.com/{}/{}/master/".format(
PROJECT_USERAME, PROJECT_NAME
)
PROJECT_AUTHOR = "Galaxy Project and Community"
PROJECT_DESCRIPTION = "Galaxy Auth Framework and Implementations"
PROJECT_EMAIL = "galaxy-committers@lists.galaxyproject.org"
RAW_CONTENT_URL = "https://raw.github.com/{}/{}/master/".format(PROJECT_USERAME, PROJECT_NAME)
@@ -1,11 +1,9 @@
__version__ = "22.1.0rc1"
__version__ = "22.5.0.dev0"
PROJECT_NAME = "galaxy-containers"
PROJECT_OWNER = PROJECT_USERAME = "galaxyproject"
PROJECT_URL = "https://github.com/galaxyproject/galaxy"
PROJECT_AUTHOR = 'Galaxy Project and Community'
PROJECT_DESCRIPTION = 'Galaxy Container Modeling and Interaction Abstractions'
PROJECT_EMAIL = 'galaxy-committers@lists.galaxyproject.org'
RAW_CONTENT_URL = "https://raw.github.com/{}/{}/master/".format(
PROJECT_USERAME, PROJECT_NAME
)
PROJECT_AUTHOR = "Galaxy Project and Community"
PROJECT_DESCRIPTION = "Galaxy Container Modeling and Interaction Abstractions"
PROJECT_EMAIL = "galaxy-committers@lists.galaxyproject.org"
RAW_CONTENT_URL = "https://raw.github.com/{}/{}/master/".format(PROJECT_USERAME, PROJECT_NAME)
+5 -7
View File
@@ -1,11 +1,9 @@
__version__ = "22.1.0rc1"
__version__ = "22.5.0.dev0"
PROJECT_NAME = "galaxy-data"
PROJECT_OWNER = PROJECT_USERAME = "galaxyproject"
PROJECT_URL = "https://github.com/galaxyproject/galaxy"
PROJECT_AUTHOR = 'Galaxy Project and Community'
PROJECT_DESCRIPTION = 'Galaxy Datatype Framework and Datatypes'
PROJECT_EMAIL = 'galaxy-committers@lists.galaxyproject.org'
RAW_CONTENT_URL = "https://raw.github.com/{}/{}/master/".format(
PROJECT_USERAME, PROJECT_NAME
)
PROJECT_AUTHOR = "Galaxy Project and Community"
PROJECT_DESCRIPTION = "Galaxy Datatype Framework and Datatypes"
PROJECT_EMAIL = "galaxy-committers@lists.galaxyproject.org"
RAW_CONTENT_URL = "https://raw.github.com/{}/{}/master/".format(PROJECT_USERAME, PROJECT_NAME)
@@ -1,9 +1,9 @@
__version__ = "22.1.0rc1"
__version__ = "22.5.0.dev0"
PROJECT_NAME = "galaxy-files"
PROJECT_OWNER = PROJECT_USERAME = "galaxyproject"
PROJECT_URL = "https://github.com/galaxyproject/galaxy"
PROJECT_AUTHOR = 'Galaxy Project and Community'
PROJECT_DESCRIPTION = 'Galaxy File Source Framework and Default Plugins'
PROJECT_EMAIL = 'galaxy-committers@lists.galaxyproject.org'
PROJECT_AUTHOR = "Galaxy Project and Community"
PROJECT_DESCRIPTION = "Galaxy File Source Framework and Default Plugins"
PROJECT_EMAIL = "galaxy-committers@lists.galaxyproject.org"
RAW_CONTENT_URL = f"https://raw.github.com/{PROJECT_USERAME}/{PROJECT_NAME}/master/"
@@ -1,11 +1,9 @@
__version__ = "22.1.0rc1"
__version__ = "22.5.0.dev0"
PROJECT_NAME = "galaxy-job-execution"
PROJECT_OWNER = PROJECT_USERAME = "galaxyproject"
PROJECT_URL = "https://github.com/galaxyproject/galaxy"
PROJECT_AUTHOR = 'Galaxy Project and Community'
PROJECT_DESCRIPTION = 'Galaxy Job Execution Runtime Utilities'
PROJECT_EMAIL = 'galaxy-committers@lists.galaxyproject.org'
RAW_CONTENT_URL = "https://raw.github.com/{}/{}/master/".format(
PROJECT_USERAME, PROJECT_NAME
)
PROJECT_AUTHOR = "Galaxy Project and Community"
PROJECT_DESCRIPTION = "Galaxy Job Execution Runtime Utilities"
PROJECT_EMAIL = "galaxy-committers@lists.galaxyproject.org"
RAW_CONTENT_URL = "https://raw.github.com/{}/{}/master/".format(PROJECT_USERAME, PROJECT_NAME)
@@ -1,11 +1,9 @@
__version__ = "22.1.0rc1"
__version__ = "22.5.0.dev0"
PROJECT_NAME = "galaxy-job-metrics"
PROJECT_OWNER = PROJECT_USERAME = "galaxyproject"
PROJECT_URL = "https://github.com/galaxyproject/galaxy"
PROJECT_AUTHOR = 'Galaxy Project and Community'
PROJECT_DESCRIPTION = 'Galaxy Job Metrics'
PROJECT_EMAIL = 'galaxy-committers@lists.galaxyproject.org'
RAW_CONTENT_URL = "https://raw.github.com/{}/{}/master/".format(
PROJECT_USERAME, PROJECT_NAME
)
PROJECT_AUTHOR = "Galaxy Project and Community"
PROJECT_DESCRIPTION = "Galaxy Job Metrics"
PROJECT_EMAIL = "galaxy-committers@lists.galaxyproject.org"
RAW_CONTENT_URL = "https://raw.github.com/{}/{}/master/".format(PROJECT_USERAME, PROJECT_NAME)
+5 -7
View File
@@ -1,11 +1,9 @@
__version__ = "22.1.0rc1"
__version__ = "22.5.0.dev0"
PROJECT_NAME = "galaxy"
PROJECT_OWNER = PROJECT_USERAME = "galaxyproject"
PROJECT_URL = "https://github.com/galaxyproject/galaxy"
PROJECT_AUTHOR = 'Galaxy Project and Community'
PROJECT_DESCRIPTION = 'Galaxy Server Metapackage'
PROJECT_EMAIL = 'galaxy-committers@lists.galaxyproject.org'
RAW_CONTENT_URL = "https://raw.github.com/{}/{}/master/".format(
PROJECT_USERAME, PROJECT_NAME
)
PROJECT_AUTHOR = "Galaxy Project and Community"
PROJECT_DESCRIPTION = "Galaxy Server Metapackage"
PROJECT_EMAIL = "galaxy-committers@lists.galaxyproject.org"
RAW_CONTENT_URL = "https://raw.github.com/{}/{}/master/".format(PROJECT_USERAME, PROJECT_NAME)
@@ -1,11 +1,9 @@
__version__ = "22.1.0rc1"
__version__ = "22.5.0.dev0"
PROJECT_NAME = "galaxy-objectstore"
PROJECT_OWNER = PROJECT_USERAME = "galaxyproject"
PROJECT_URL = "https://github.com/galaxyproject/galaxy"
PROJECT_AUTHOR = 'Galaxy Project and Community'
PROJECT_DESCRIPTION = 'Galaxy Objectstore Framework and Plugins'
PROJECT_EMAIL = 'galaxy-committers@lists.galaxyproject.org'
RAW_CONTENT_URL = "https://raw.github.com/{}/{}/master/".format(
PROJECT_USERAME, PROJECT_NAME
)
PROJECT_AUTHOR = "Galaxy Project and Community"
PROJECT_DESCRIPTION = "Galaxy Objectstore Framework and Plugins"
PROJECT_EMAIL = "galaxy-committers@lists.galaxyproject.org"
RAW_CONTENT_URL = "https://raw.github.com/{}/{}/master/".format(PROJECT_USERAME, PROJECT_NAME)
@@ -1,11 +1,9 @@
__version__ = "22.1.0rc1"
__version__ = "22.5.0.dev0"
PROJECT_NAME = "galaxy-selenium"
PROJECT_OWNER = PROJECT_USERAME = "galaxyproject"
PROJECT_URL = "https://github.com/galaxyproject/galaxy"
PROJECT_AUTHOR = 'Galaxy Project and Community'
PROJECT_DESCRIPTION = 'Galaxy Selenium Interaction Framework'
PROJECT_EMAIL = 'galaxy-committers@lists.galaxyproject.org'
RAW_CONTENT_URL = "https://raw.github.com/{}/{}/master/".format(
PROJECT_USERAME, PROJECT_NAME
)
PROJECT_AUTHOR = "Galaxy Project and Community"
PROJECT_DESCRIPTION = "Galaxy Selenium Interaction Framework"
PROJECT_EMAIL = "galaxy-committers@lists.galaxyproject.org"
RAW_CONTENT_URL = "https://raw.github.com/{}/{}/master/".format(PROJECT_USERAME, PROJECT_NAME)
@@ -1,11 +1,9 @@
__version__ = "22.1.0rc1"
__version__ = "22.5.0.dev0"
PROJECT_NAME = "galaxy-test-api"
PROJECT_OWNER = PROJECT_USERAME = "galaxyproject"
PROJECT_URL = "https://github.com/galaxyproject/galaxy"
PROJECT_AUTHOR = 'Galaxy Project and Community'
PROJECT_DESCRIPTION = 'Galaxy API Tests'
PROJECT_EMAIL = 'galaxy-committers@lists.galaxyproject.org'
RAW_CONTENT_URL = "https://raw.github.com/{}/{}/master/".format(
PROJECT_USERAME, PROJECT_NAME
)
PROJECT_AUTHOR = "Galaxy Project and Community"
PROJECT_DESCRIPTION = "Galaxy API Tests"
PROJECT_EMAIL = "galaxy-committers@lists.galaxyproject.org"
RAW_CONTENT_URL = "https://raw.github.com/{}/{}/master/".format(PROJECT_USERAME, PROJECT_NAME)
@@ -1,11 +1,9 @@
__version__ = "22.1.0rc1"
__version__ = "22.5.0.dev0"
PROJECT_NAME = "galaxy-test-base"
PROJECT_OWNER = PROJECT_USERAME = "galaxyproject"
PROJECT_URL = "https://github.com/galaxyproject/galaxy"
PROJECT_AUTHOR = 'Galaxy Project and Community'
PROJECT_DESCRIPTION = 'Galaxy Testing Utilities'
PROJECT_EMAIL = 'galaxy-committers@lists.galaxyproject.org'
RAW_CONTENT_URL = "https://raw.github.com/{}/{}/master/".format(
PROJECT_USERAME, PROJECT_NAME
)
PROJECT_AUTHOR = "Galaxy Project and Community"
PROJECT_DESCRIPTION = "Galaxy Testing Utilities"
PROJECT_EMAIL = "galaxy-committers@lists.galaxyproject.org"
RAW_CONTENT_URL = "https://raw.github.com/{}/{}/master/".format(PROJECT_USERAME, PROJECT_NAME)
@@ -1,11 +1,9 @@
__version__ = "22.1.0rc1"
__version__ = "22.5.0.dev0"
PROJECT_NAME = "galaxy-test-driver"
PROJECT_OWNER = PROJECT_USERAME = "galaxyproject"
PROJECT_URL = "https://github.com/galaxyproject/galaxy"
PROJECT_AUTHOR = 'Galaxy Project and Community'
PROJECT_DESCRIPTION = 'Galaxy Test Driver'
PROJECT_EMAIL = 'galaxy-committers@lists.galaxyproject.org'
RAW_CONTENT_URL = "https://raw.github.com/{}/{}/master/".format(
PROJECT_USERAME, PROJECT_NAME
)
PROJECT_AUTHOR = "Galaxy Project and Community"
PROJECT_DESCRIPTION = "Galaxy Test Driver"
PROJECT_EMAIL = "galaxy-committers@lists.galaxyproject.org"
RAW_CONTENT_URL = "https://raw.github.com/{}/{}/master/".format(PROJECT_USERAME, PROJECT_NAME)
@@ -1,11 +1,9 @@
__version__ = "22.1.0rc1"
__version__ = "22.5.0.dev0"
PROJECT_NAME = "galaxy-test-selenium"
PROJECT_OWNER = PROJECT_USERAME = "galaxyproject"
PROJECT_URL = "https://github.com/galaxyproject/galaxy"
PROJECT_AUTHOR = 'Galaxy Project and Community'
PROJECT_DESCRIPTION = 'Galaxy Selenium Tests'
PROJECT_EMAIL = 'galaxy-committers@lists.galaxyproject.org'
RAW_CONTENT_URL = "https://raw.github.com/{}/{}/master/".format(
PROJECT_USERAME, PROJECT_NAME
)
PROJECT_AUTHOR = "Galaxy Project and Community"
PROJECT_DESCRIPTION = "Galaxy Selenium Tests"
PROJECT_EMAIL = "galaxy-committers@lists.galaxyproject.org"
RAW_CONTENT_URL = "https://raw.github.com/{}/{}/master/".format(PROJECT_USERAME, PROJECT_NAME)
@@ -1,11 +1,9 @@
__version__ = "22.1.0rc1"
__version__ = "22.5.0.dev0"
PROJECT_NAME = "galaxy-tool-util"
PROJECT_OWNER = PROJECT_USERAME = "galaxyproject"
PROJECT_URL = "https://github.com/galaxyproject/galaxy"
PROJECT_AUTHOR = 'Galaxy Project and Community'
PROJECT_DESCRIPTION = 'Galaxy Tool and Tool Dependency Utilities'
PROJECT_EMAIL = 'galaxy-committers@lists.galaxyproject.org'
RAW_CONTENT_URL = "https://raw.github.com/{}/{}/master/".format(
PROJECT_USERAME, PROJECT_NAME
)
PROJECT_AUTHOR = "Galaxy Project and Community"
PROJECT_DESCRIPTION = "Galaxy Tool and Tool Dependency Utilities"
PROJECT_EMAIL = "galaxy-committers@lists.galaxyproject.org"
RAW_CONTENT_URL = "https://raw.github.com/{}/{}/master/".format(PROJECT_USERAME, PROJECT_NAME)
+5 -7
View File
@@ -1,11 +1,9 @@
__version__ = "22.1.0rc1"
__version__ = "22.5.0.dev0"
PROJECT_NAME = "galaxy-util"
PROJECT_OWNER = PROJECT_USERAME = "galaxyproject"
PROJECT_URL = "https://github.com/galaxyproject/galaxy"
PROJECT_AUTHOR = 'Galaxy Project and Community'
PROJECT_DESCRIPTION = 'Galaxy Generic Utilities'
PROJECT_EMAIL = 'galaxy-committers@lists.galaxyproject.org'
RAW_CONTENT_URL = "https://raw.github.com/{}/{}/master/".format(
PROJECT_USERAME, PROJECT_NAME
)
PROJECT_AUTHOR = "Galaxy Project and Community"
PROJECT_DESCRIPTION = "Galaxy Generic Utilities"
PROJECT_EMAIL = "galaxy-committers@lists.galaxyproject.org"
RAW_CONTENT_URL = "https://raw.github.com/{}/{}/master/".format(PROJECT_USERAME, PROJECT_NAME)
@@ -1,11 +1,9 @@
__version__ = "22.1.0rc1"
__version__ = "22.5.0.dev0"
PROJECT_NAME = "galaxy-web-framework"
PROJECT_OWNER = PROJECT_USERAME = "galaxyproject"
PROJECT_URL = "https://github.com/galaxyproject/galaxy"
PROJECT_AUTHOR = 'Galaxy Project and Community'
PROJECT_DESCRIPTION = 'Galaxy Web Framework'
PROJECT_EMAIL = 'galaxy-committers@lists.galaxyproject.org'
RAW_CONTENT_URL = "https://raw.github.com/{}/{}/master/".format(
PROJECT_USERAME, PROJECT_NAME
)
PROJECT_AUTHOR = "Galaxy Project and Community"
PROJECT_DESCRIPTION = "Galaxy Web Framework"
PROJECT_EMAIL = "galaxy-committers@lists.galaxyproject.org"
RAW_CONTENT_URL = "https://raw.github.com/{}/{}/master/".format(PROJECT_USERAME, PROJECT_NAME)
@@ -1,11 +1,9 @@
__version__ = "22.1.0rc1"
__version__ = "22.5.0.dev0"
PROJECT_NAME = "galaxy-web-stack"
PROJECT_OWNER = PROJECT_USERAME = "galaxyproject"
PROJECT_URL = "https://github.com/galaxyproject/galaxy"
PROJECT_AUTHOR = 'Galaxy Project and Community'
PROJECT_DESCRIPTION = 'Galaxy Web Strack Abstraction'
PROJECT_EMAIL = 'galaxy-committers@lists.galaxyproject.org'
RAW_CONTENT_URL = "https://raw.github.com/{}/{}/master/".format(
PROJECT_USERAME, PROJECT_NAME
)
PROJECT_AUTHOR = "Galaxy Project and Community"
PROJECT_DESCRIPTION = "Galaxy Web Strack Abstraction"
PROJECT_EMAIL = "galaxy-committers@lists.galaxyproject.org"
RAW_CONTENT_URL = "https://raw.github.com/{}/{}/master/".format(PROJECT_USERAME, PROJECT_NAME)
@@ -1,11 +1,9 @@
__version__ = "22.1.0rc1"
__version__ = "22.5.0.dev0"
PROJECT_NAME = "galaxy-webapps"
PROJECT_OWNER = PROJECT_USERAME = "galaxyproject"
PROJECT_URL = "https://github.com/galaxyproject/galaxy"
PROJECT_AUTHOR = 'Galaxy Project and Community'
PROJECT_DESCRIPTION = 'Galaxy Web Apps'
PROJECT_EMAIL = 'jmchilton@gmail.com'
RAW_CONTENT_URL = "https://raw.github.com/{}/{}/master/".format(
PROJECT_USERAME, PROJECT_NAME
)
PROJECT_AUTHOR = "Galaxy Project and Community"
PROJECT_DESCRIPTION = "Galaxy Web Apps"
PROJECT_EMAIL = "jmchilton@gmail.com"
RAW_CONTENT_URL = "https://raw.github.com/{}/{}/master/".format(PROJECT_USERAME, PROJECT_NAME)
+14
View File
@@ -1,3 +1,15 @@
[tool.black]
line-length = 120
target-version = ['py37']
include = '\.pyi?$'
extend-exclude = '''
^/(
| packages
| tools
)/
'''
force-exclude = 'lib/galaxy/util/jstree.py'
[tool.poetry]
name = "galaxy"
version = "22.01.dev0"
@@ -90,10 +102,12 @@ Whoosh = "*"
zipstream-new = "*"
[tool.poetry.dev-dependencies]
black = "^22.1.0"
cwltest = "2.2.20210901154959"
darker = "*"
fluent-logger = "*"
httpx = "*"
isort = "^5.10.1"
lxml = "!=4.2.2"
markdown-it-reporter = "*"
NoseHTML = "*"
+2 -5
View File
@@ -5,16 +5,13 @@ universal = 1
# These are exceptions allowed by Galaxy style guidelines:
# B008 Do not perform function calls in argument defaults (for FastAPI Depends and Body)
# E128 continuation line under-indented for visual indent
# E203 is whitespace before ':'; we follow black's formatting here. See https://black.readthedocs.io/en/stable/faq.html#why-are-flake8-s-e203-and-w503-violated
# E402 module level import not at top of file # TODO, we would like to improve this.
# E501 is line length
# W503 is line breaks before binary operators, which has been reversed in PEP 8.
# D** are docstring linting - which we mostly ignore except D302. (Hopefully we will solve more over time).
ignore = B008,E128,E501,E402,W503,D100,D101,D102,D103,D104,D105,D106,D107,D200,D201,D202,D204,D205,D206,D207,D208,D209,D210,D211,D300,D301,D400,D401,D402,D403,D412,D413
ignore = B008,E128,E203,E402,E501,W503,D100,D101,D102,D103,D104,D105,D106,D107,D200,D201,D202,D204,D205,D206,D207,D208,D209,D210,D211,D300,D301,D400,D401,D402,D403,D412,D413
exclude = lib/galaxy/util/jstree.py
# For flake8-import-order
# https://github.com/PyCQA/flake8-import-order/blob/master/tests/test_cases/complete_smarkets.py
import-order-style = smarkets
application-import-names = galaxy,galaxy_test,tool_shed
[mypy]
show_error_codes = True
+9 -17
View File
@@ -1,19 +1,22 @@
import abc
import os
from contextlib import contextmanager
from typing import Dict, Type
from typing import (
Dict,
Type,
)
from unittest import TestCase
from galaxy.app_unittest_utils.tools_support import UsesApp
from galaxy.jobs import (
JobWrapper,
TaskWrapper
TaskWrapper,
)
from galaxy.model import (
Base,
Job,
Task,
User
User,
)
from galaxy.util.bunch import Bunch
@@ -24,7 +27,6 @@ TEST_COMMAND = ""
class BaseWrapperTestCase(UsesApp):
def setUp(self):
self.setup_app()
job = Job()
@@ -72,13 +74,11 @@ class BaseWrapperTestCase(UsesApp):
class JobWrapperTestCase(BaseWrapperTestCase, TestCase):
def _wrapper(self):
return JobWrapper(self.job, self.queue) # type: ignore[arg-type]
class TaskWrapperTestCase(BaseWrapperTestCase, TestCase):
def setUp(self):
super().setUp()
self.task = Task(self.job, self.working_directory, "prepare_bwa_job.sh")
@@ -90,7 +90,6 @@ class TaskWrapperTestCase(BaseWrapperTestCase, TestCase):
class MockEvaluator:
def __init__(self, app, tool, job, local_working_directory):
self.app = app
self.tool = tool
@@ -109,14 +108,12 @@ class MockEvaluator:
class MockJobQueue:
def __init__(self, app):
self.app = app
self.dispatcher = MockJobDispatcher(app)
class MockJobDispatcher:
def __init__(self, app):
pass
@@ -125,7 +122,6 @@ class MockJobDispatcher:
class MockContext:
def __init__(self, model_objects):
self.expunged_all = False
self.flushed = False
@@ -146,7 +142,6 @@ class MockContext:
class MockQuery:
def __init__(self, class_objects):
self.class_objects = class_objects
@@ -158,26 +153,24 @@ class MockQuery:
class MockTool:
def __init__(self, app):
self.version_string_cmd = TEST_VERSION_COMMAND
self.tool_dir = "/path/to/tools"
self.dependencies = []
self.requires_galaxy_python_environment = False
self.id = 'mock_id'
self.id = "mock_id"
self.home_target = None
self.tmp_target = None
self.tool_source = Bunch(to_string=lambda: '')
self.tool_source = Bunch(to_string=lambda: "")
def get_job_destination(self, params):
return Bunch(runner='local', id='local', params={})
return Bunch(runner="local", id="local", params={})
def build_dependency_shell_commands(self, job_directory):
return TEST_DEPENDENCIES_COMMANDS
class MockToolbox:
def __init__(self, test_tool):
self.test_tool = test_tool
@@ -191,7 +184,6 @@ class MockToolbox:
class MockObjectStore:
def __init__(self, working_directory):
self.working_directory = working_directory
os.makedirs(working_directory)
+29 -32
View File
@@ -15,17 +15,18 @@ from galaxy.model import (
)
from galaxy.tool_util.parser.output_objects import ToolOutput
from galaxy.tools.evaluation import ToolEvaluator
# For MockTool
from galaxy.tools.parameters import params_from_strings
from galaxy.tools.parameters.basic import (
DataToolParameter,
IntegerToolParameter,
SelectToolParameter
SelectToolParameter,
)
from galaxy.tools.parameters.grouping import (
Conditional,
ConditionalWhen,
Repeat
Repeat,
)
from galaxy.util import XML
from galaxy.util.bunch import Bunch
@@ -37,7 +38,6 @@ TEST_GALAXY_URL = "http://mycool.galaxyproject.org:8456"
class ToolEvaluatorTestCase(TestCase, UsesApp):
def setUp(self):
self.setup_app()
self.tool = MockTool(self.app)
@@ -54,14 +54,18 @@ class ToolEvaluatorTestCase(TestCase, UsesApp):
self._setup_test_bwa_job()
self._set_compute_environment()
command_line = self.evaluator.build()[0]
self.assertEqual(command_line, "bwa --thresh=4 --in=/galaxy/files/dataset_1.dat --out=/galaxy/files/dataset_2.dat")
self.assertEqual(
command_line, "bwa --thresh=4 --in=/galaxy/files/dataset_1.dat --out=/galaxy/files/dataset_2.dat"
)
def test_repeat_evaluation(self):
repeat = Repeat()
repeat.name = "r"
repeat.inputs = {"thresh": self.tool.test_thresh_param()}
self.tool.set_params({"r": repeat})
self.job.parameters = [JobParameter(name="r", value='''[{"thresh": 4, "__index__": 0},{"thresh": 5, "__index__": 1}]''')]
self.job.parameters = [
JobParameter(name="r", value="""[{"thresh": 4, "__index__": 0},{"thresh": 5, "__index__": 1}]""")
]
self.tool._command_line = "prog1 #for $r_i in $r # $r_i.thresh#end for#"
self._set_compute_environment()
command_line = self.evaluator.build()[0]
@@ -80,7 +84,7 @@ class ToolEvaluatorTestCase(TestCase, UsesApp):
self.assertEqual(command_line, "prog1 '%s'" % self.app.security.encode_id(42))
def test_conditional_evaluation(self):
select_xml = XML('''<param name="always_true" type="select"><option value="true">True</option></param>''')
select_xml = XML("""<param name="always_true" type="select"><option value="true">True</option></param>""")
parameter = SelectToolParameter(self.tool, select_xml)
conditional = Conditional()
@@ -91,7 +95,9 @@ class ToolEvaluatorTestCase(TestCase, UsesApp):
when.value = "true"
conditional.cases = [when]
self.tool.set_params({"c": conditional})
self.job.parameters = [JobParameter(name="c", value='''{"thresh": 4, "always_true": "true", "__current_case__": 0}''')]
self.job.parameters = [
JobParameter(name="c", value="""{"thresh": 4, "always_true": "true", "__current_case__": 0}""")
]
self.tool._command_line = "prog1 --thresh=${c.thresh} --test_param=${c.always_true}"
self._set_compute_environment()
command_line = self.evaluator.build()[0]
@@ -100,9 +106,9 @@ class ToolEvaluatorTestCase(TestCase, UsesApp):
def test_evaluation_of_optional_datasets(self):
# Make sure optional dataset don't cause evaluation to break and
# evaluate in cheetah templates as 'None'.
select_xml = XML('''<param name="input1" type="data" optional="true"></param>''')
select_xml = XML("""<param name="input1" type="data" optional="true"></param>""")
parameter = DataToolParameter(self.tool, select_xml)
self.job.parameters = [JobParameter(name="input1", value='null')]
self.job.parameters = [JobParameter(name="input1", value="null")]
self.tool.set_params({"input1": parameter})
self.tool._command_line = "prog1 --opt_input='${input1}'"
self._set_compute_environment()
@@ -125,8 +131,8 @@ class ToolEvaluatorTestCase(TestCase, UsesApp):
job_path_1 = "%s/dataset_1.dat" % self.test_directory
job_path_2 = "%s/dataset_2.dat" % self.test_directory
self._set_compute_environment(
input_paths=[DatasetPath(1, '/galaxy/files/dataset_1.dat', false_path=job_path_1)],
output_paths=[DatasetPath(2, '/galaxy/files/dataset_2.dat', false_path=job_path_2)],
input_paths=[DatasetPath(1, "/galaxy/files/dataset_1.dat", false_path=job_path_1)],
output_paths=[DatasetPath(2, "/galaxy/files/dataset_2.dat", false_path=job_path_2)],
)
command_line = self.evaluator.build()[0]
self.assertEqual(command_line, f"bwa --thresh=4 --in={job_path_1} --out={job_path_2}")
@@ -154,11 +160,13 @@ class ToolEvaluatorTestCase(TestCase, UsesApp):
self.__test_arbitrary_path_rewriting()
def __test_arbitrary_path_rewriting(self):
self.job.parameters = [JobParameter(name="index_path", value="\"/old/path/human\"")]
xml = XML('''<param name="index_path" type="select">
self.job.parameters = [JobParameter(name="index_path", value='"/old/path/human"')]
xml = XML(
"""<param name="index_path" type="select">
<option value="/old/path/human">Human</option>
<option value="/old/path/mouse">Mouse</option>
</param>''')
</param>"""
)
parameter = SelectToolParameter(self.tool, xml)
def get_field_by_name_for_value(name, value, trans, other_values):
@@ -170,9 +178,7 @@ class ToolEvaluatorTestCase(TestCase, UsesApp):
return [["", "/old/path/human", ""]]
parameter.options = Bunch(get_field_by_name_for_value=get_field_by_name_for_value, get_options=get_options)
self.tool.set_params({
"index_path": parameter
})
self.tool.set_params({"index_path": parameter})
self.tool._command_line = "prog1 $index_path.fields.path"
self._set_compute_environment(unstructured_path_rewrites={"/old": "/new"})
command_line = self.evaluator.build()[0]
@@ -214,40 +220,32 @@ class ToolEvaluatorTestCase(TestCase, UsesApp):
assert "exec_before_job" in self.tool.hooks_called
def _setup_test_bwa_job(self):
def hda(id, name, path):
hda = HistoryDatasetAssociation(name=name, metadata=dict())
hda.dataset = Dataset(id=id, external_filename=path)
return hda
id, name, path = 111, 'input1', '/galaxy/files/dataset_1.dat'
id, name, path = 111, "input1", "/galaxy/files/dataset_1.dat"
self.job.input_datasets = [JobToInputDatasetAssociation(name=name, dataset=hda(id, name, path))]
id, name, path = 112, 'output1', '/galaxy/files/dataset_2.dat'
id, name, path = 112, "output1", "/galaxy/files/dataset_2.dat"
self.job.output_datasets = [JobToOutputDatasetAssociation(name=name, dataset=hda(id, name, path))]
class MockHistoryDatasetAssociation(HistoryDatasetAssociation):
def __init__(self, **kwds):
self._metadata = dict()
super().__init__(**kwds)
class ComputeEnvironment(SimpleComputeEnvironment):
def __init__(
self,
new_file_path,
working_directory,
input_paths=None,
output_paths=None,
unstructured_path_rewrites=None
self, new_file_path, working_directory, input_paths=None, output_paths=None, unstructured_path_rewrites=None
):
if input_paths is None:
input_paths = ['/galaxy/files/dataset_1.dat']
input_paths = ["/galaxy/files/dataset_1.dat"]
if output_paths is None:
output_paths = ['/galaxy/files/dataset_2.dat']
output_paths = ["/galaxy/files/dataset_2.dat"]
self._new_file_path = new_file_path
self._working_directory = working_directory
self._input_paths = input_paths
@@ -300,10 +298,9 @@ class ComputeEnvironment(SimpleComputeEnvironment):
class MockTool:
def __init__(self, app):
self.profile = 16.01
self.python_template_version = '2.7'
self.python_template_version = "2.7"
self.app = app
self.hooks_called = []
self.environment_variables = []
File diff suppressed because it is too large Load Diff
@@ -9,7 +9,6 @@ import pytest
from fastapi import FastAPI
from fastapi.param_functions import Depends
from httpx import AsyncClient
pytest.importorskip("starlette_context")
from starlette_context import context as request_context
from galaxy.app_unittest_utils.galaxy_mock import MockApp
@@ -37,7 +36,7 @@ async def _get_app():
GX_APP = MockApp()
GX_APP.stop = False
app = GX_APP
request_id = request_context.data['X-Request-ID']
request_id = request_context.data["X-Request-ID"]
app.model.set_request_id(request_id)
try:
yield app
@@ -59,17 +58,17 @@ async def read_main(app=Depends(get_app)):
return {"msg": "Hello World"}
@app.get('/internal_server_error')
@app.get("/internal_server_error")
def error(app=Depends(get_app)):
assert app.model.scoped_registry.registry == {}
app.model.session()
assert len(app.model.scoped_registry.registry) == 1
request_id = app.model.request_scopefunc()
assert is_valid_uuid(request_id)
raise UnexpectedException('Oh noes!')
raise UnexpectedException("Oh noes!")
@app.get('/sync_wait')
@app.get("/sync_wait")
def sync_wait(app=Depends(get_app)):
app.model.session()
time.sleep(0.2)
@@ -78,7 +77,7 @@ def sync_wait(app=Depends(get_app)):
return request_id
@app.get('/async_wait')
@app.get("/async_wait")
async def async_wait(app=Depends(get_app)):
app.model.session()
await asyncio.sleep(0.2)
@@ -119,7 +118,7 @@ async def test_request_scoped_sa_session_exception():
async def test_request_scoped_sa_session_concurrent_requests_sync():
add_request_id_middleware(app)
async with AsyncClient(app=app, base_url="http://test") as client:
awaitables = (client.get('/sync_wait') for _ in range(10))
awaitables = (client.get("/sync_wait") for _ in range(10))
result = await asyncio.gather(*awaitables)
uuids = []
for r in result:
@@ -134,7 +133,7 @@ async def test_request_scoped_sa_session_concurrent_requests_sync():
async def test_request_scoped_sa_session_concurrent_requests_async():
add_request_id_middleware(app)
async with AsyncClient(app=app, base_url="http://test") as client:
awaitables = (client.get('/async_wait') for _ in range(10))
awaitables = (client.get("/async_wait") for _ in range(10))
result = await asyncio.gather(*awaitables)
uuids = []
for r in result:
@@ -153,7 +152,7 @@ async def test_request_scoped_sa_session_concurrent_requests_and_background_thre
with concurrent.futures.ThreadPoolExecutor() as pool:
background_pool = loop.run_in_executor(pool, target)
async with AsyncClient(app=app, base_url="http://test") as client:
awaitables = (client.get('/async_wait') for _ in range(10))
awaitables = (client.get("/async_wait") for _ in range(10))
result = await asyncio.gather(*awaitables)
uuids = []
for r in result: