mirror of
https://github.com/galaxyproject/galaxy.git
synced 2026-09-24 16:30:27 +08:00
Merge commit '623a5e165ad79e15321b19b2a2afbf2f9224cc90~1' into release_22.01
This commit is contained in:
@@ -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/
|
||||
|
||||
@@ -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
@@ -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
|
||||
@@ -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
|
||||
|
||||
|
||||
+405
-310
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
|
||||
+518
-352
File diff suppressed because it is too large
Load Diff
@@ -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
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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]
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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",
|
||||
)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 "")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
+1572
-962
File diff suppressed because it is too large
Load Diff
@@ -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)
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"])
|
||||
|
||||
+1286
-677
File diff suppressed because it is too large
Load Diff
+307
-221
File diff suppressed because it is too large
Load Diff
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 = "*"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user