mirror of
https://github.com/galaxyproject/galaxy.git
synced 2026-09-19 10:51:34 +08:00
Merge remote-tracking branch 'upstream/release_23.1' into dev
This commit is contained in:
@@ -962,6 +962,13 @@
|
||||
<datatype extension="castep" type="galaxy.datatypes.text:Castep" display_in_upload="true"/>
|
||||
<datatype extension="param" type="galaxy.datatypes.text:Param" display_in_upload="true"/>
|
||||
<datatype extension="den_fmt" type="galaxy.datatypes.text:FormattedDensity" display_in_upload="true"/>
|
||||
<!-- Larch types -->
|
||||
<datatype extension="prj" type="galaxy.datatypes.larch:AthenaProject" display_in_upload="true" description="Athena project file."/>
|
||||
<datatype extension="inp" type="galaxy.datatypes.larch:FEFFInput" display_in_upload="true" description="FEFF input file."/>
|
||||
<datatype extension="sp" type="galaxy.datatypes.tabular:CSV" subclass="true" description="CSV representing selected FEFF paths."/>
|
||||
<datatype extension="gds" type="galaxy.datatypes.tabular:CSV" subclass="true" description="CSV representing GDS parameters."/>
|
||||
<datatype extension="feff" type="galaxy.datatypes.tabular:CSV" subclass="true" description="CSV representing summary of FEFF paths."/>
|
||||
<datatype extension="feffit" type="galaxy.datatypes.data:Text" subclass="true" description="Report of Artemis FEFFIT."/>
|
||||
<!-- ECOLOGY types -->
|
||||
<datatype extension="bil" type="galaxy.datatypes.binary:Binary" mimetype="application/octet-stream" display_in_upload="true" subclass="true" description="ENVI file with band interleave by line (BIL) format"/>
|
||||
<datatype extension="hdr" type="galaxy.datatypes.data:Text" mimetype="text/plain" display_in_upload="true" subclass="true" description="ENVI metadata header file"/>
|
||||
@@ -1224,6 +1231,8 @@
|
||||
<sniffer type="galaxy.datatypes.media:Mpg"/>
|
||||
<sniffer type="galaxy.datatypes.speech:TextGrid" />
|
||||
<sniffer type="galaxy.datatypes.speech:BPF" />
|
||||
<sniffer type="galaxy.datatypes.larch:FEFFInput" />
|
||||
<sniffer type="galaxy.datatypes.larch:AthenaProject" />
|
||||
<sniffer type="galaxy.datatypes.text:Castep" />
|
||||
<sniffer type="galaxy.datatypes.text:CTLresult"/>
|
||||
<sniffer type="galaxy.datatypes.text:FormattedDensity" />
|
||||
|
||||
@@ -0,0 +1,218 @@
|
||||
from typing import List
|
||||
|
||||
from galaxy.datatypes.data import (
|
||||
get_file_peek,
|
||||
Text,
|
||||
)
|
||||
from galaxy.datatypes.metadata import MetadataElement
|
||||
from galaxy.datatypes.protocols import DatasetProtocol
|
||||
from galaxy.datatypes.sniff import (
|
||||
build_sniff_from_prefix,
|
||||
FilePrefix,
|
||||
get_headers,
|
||||
)
|
||||
|
||||
|
||||
@build_sniff_from_prefix
|
||||
class AthenaProject(Text):
|
||||
"""
|
||||
Athena project format
|
||||
"""
|
||||
|
||||
file_ext = "prj"
|
||||
compressed = True
|
||||
compressed_format = "gzip"
|
||||
|
||||
MetadataElement(
|
||||
name="atsym",
|
||||
desc="Atom symbol",
|
||||
readonly=True,
|
||||
visible=True,
|
||||
)
|
||||
MetadataElement(
|
||||
name="bkg_e0",
|
||||
desc="Edge energy (eV)",
|
||||
readonly=True,
|
||||
visible=True,
|
||||
)
|
||||
MetadataElement(
|
||||
name="edge",
|
||||
desc="Edge",
|
||||
readonly=True,
|
||||
visible=True,
|
||||
)
|
||||
MetadataElement(
|
||||
name="npts",
|
||||
desc="Number of points",
|
||||
readonly=True,
|
||||
visible=True,
|
||||
)
|
||||
MetadataElement(
|
||||
name="xmax",
|
||||
desc="Maximum energy (eV)",
|
||||
readonly=True,
|
||||
visible=True,
|
||||
)
|
||||
MetadataElement(
|
||||
name="xmin",
|
||||
desc="Minimum energy (eV)",
|
||||
readonly=True,
|
||||
visible=True,
|
||||
)
|
||||
|
||||
def sniff_prefix(self, file_prefix: FilePrefix) -> bool:
|
||||
"""
|
||||
Try to guess if the file is an Athena project file.
|
||||
|
||||
>>> from galaxy.datatypes.sniff import get_test_fname
|
||||
>>> fname = get_test_fname('test.prj')
|
||||
>>> AthenaProject().sniff(fname)
|
||||
True
|
||||
>>> from galaxy.datatypes.sniff import get_test_fname
|
||||
>>> fname = get_test_fname('Si.cif')
|
||||
>>> AthenaProject().sniff(fname)
|
||||
False
|
||||
"""
|
||||
|
||||
return file_prefix.startswith("# Athena project file")
|
||||
|
||||
def set_meta(self, dataset: DatasetProtocol, *, overwrite: bool = True, **kwd) -> None:
|
||||
"""
|
||||
Extract metadata from @args
|
||||
"""
|
||||
|
||||
def extract_arg(args: List[str], arg_name: str):
|
||||
try:
|
||||
index = args.index(f"'{arg_name}'")
|
||||
setattr(dataset.metadata, arg_name, args[index + 1].replace("'", ""))
|
||||
except ValueError:
|
||||
return
|
||||
|
||||
headers = get_headers(dataset.file_name, sep=" = ", count=3, comment_designator="#")
|
||||
args = []
|
||||
for header in headers:
|
||||
if header[0] == "@args":
|
||||
args = header[1][1:-2].split(",")
|
||||
break
|
||||
|
||||
extract_arg(args, "atsym")
|
||||
extract_arg(args, "bkg_e0")
|
||||
extract_arg(args, "edge")
|
||||
extract_arg(args, "npts")
|
||||
extract_arg(args, "xmax")
|
||||
extract_arg(args, "xmin")
|
||||
|
||||
def set_peek(self, dataset: DatasetProtocol, **kwd) -> None:
|
||||
if not dataset.dataset.purged:
|
||||
dataset.peek = get_file_peek(dataset.file_name)
|
||||
dataset.info = (
|
||||
f"atsym: {dataset.metadata.atsym}\n"
|
||||
f"bkg_e0: {dataset.metadata.bkg_e0}\n"
|
||||
f"edge: {dataset.metadata.edge}\n"
|
||||
f"npts: {dataset.metadata.npts}\n"
|
||||
f"xmax: {dataset.metadata.xmax}\n"
|
||||
f"xmin: {dataset.metadata.xmin}"
|
||||
)
|
||||
dataset.blurb = f"Athena project file of {dataset.metadata.atsym} {dataset.metadata.edge} edge"
|
||||
|
||||
else:
|
||||
dataset.peek = "file does not exist"
|
||||
dataset.blurb = "file purged from disk"
|
||||
|
||||
|
||||
@build_sniff_from_prefix
|
||||
class FEFFInput(Text):
|
||||
"""
|
||||
FEFF input format
|
||||
"""
|
||||
|
||||
file_ext = "inp"
|
||||
|
||||
MetadataElement(
|
||||
name="title_block",
|
||||
desc="Title block",
|
||||
readonly=True,
|
||||
visible=True,
|
||||
)
|
||||
|
||||
def sniff_prefix(self, file_prefix: FilePrefix) -> bool:
|
||||
"""
|
||||
Try to guess if the file is an FEFF input file.
|
||||
|
||||
>>> from galaxy.datatypes.sniff import get_test_fname
|
||||
>>> fname = get_test_fname('larch_pymatgen.inp')
|
||||
>>> FEFFInput().sniff(fname)
|
||||
True
|
||||
>>> from galaxy.datatypes.sniff import get_test_fname
|
||||
>>> fname = get_test_fname('larch_atoms.inp')
|
||||
>>> FEFFInput().sniff(fname)
|
||||
True
|
||||
>>> from galaxy.datatypes.sniff import get_test_fname
|
||||
>>> fname = get_test_fname('larch_potentials.inp')
|
||||
>>> FEFFInput().sniff(fname)
|
||||
True
|
||||
>>> from galaxy.datatypes.sniff import get_test_fname
|
||||
>>> fname = get_test_fname('larch_bad_atoms.txt')
|
||||
>>> FEFFInput().sniff(fname)
|
||||
False
|
||||
>>> from galaxy.datatypes.sniff import get_test_fname
|
||||
>>> fname = get_test_fname('larch_bad_potentials.txt')
|
||||
>>> FEFFInput().sniff(fname)
|
||||
False
|
||||
>>> from galaxy.datatypes.sniff import get_test_fname
|
||||
>>> fname = get_test_fname('Si.cif')
|
||||
>>> FEFFInput().sniff(fname)
|
||||
False
|
||||
"""
|
||||
# pymatgen marks generated FEFF inputs, but user might also upload from another source
|
||||
if file_prefix.startswith("* This FEFF.inp file generated by pymatgen"):
|
||||
return True
|
||||
|
||||
generator = file_prefix.line_iterator()
|
||||
try:
|
||||
line = next(generator).strip()
|
||||
while line is not None:
|
||||
if line == "POTENTIALS":
|
||||
line = next(generator).strip()
|
||||
if line[0] == "*":
|
||||
words = line[1:].split()
|
||||
if (words[0] in ["potential-index", "ipot"]) and (words[1] == "Z"):
|
||||
return True
|
||||
return False
|
||||
|
||||
elif line == "ATOMS":
|
||||
line = next(generator).strip()
|
||||
if line[0] == "*":
|
||||
words = line[1:].split()
|
||||
if words[:4] == ["x", "y", "z", "ipot"]:
|
||||
return True
|
||||
return False
|
||||
|
||||
else:
|
||||
line = next(generator).strip()
|
||||
|
||||
except StopIteration:
|
||||
return False
|
||||
|
||||
return False
|
||||
|
||||
def set_meta(self, dataset: DatasetProtocol, overwrite: bool = True, **kwd) -> None:
|
||||
"""
|
||||
Extract metadata from TITLE
|
||||
"""
|
||||
title_block = ""
|
||||
headers = get_headers(dataset.file_name, sep=None, comment_designator="*")
|
||||
for header in headers:
|
||||
if header and header[0] == "TITLE":
|
||||
title_block += " ".join(header[1:]) + "\n"
|
||||
|
||||
dataset.metadata.title_block = title_block
|
||||
|
||||
def set_peek(self, dataset: DatasetProtocol, **kwd) -> None:
|
||||
if not dataset.dataset.purged:
|
||||
dataset.peek = get_file_peek(dataset.file_name)
|
||||
dataset.info = dataset.metadata.title_block
|
||||
|
||||
else:
|
||||
dataset.peek = "file does not exist"
|
||||
dataset.blurb = "file purged from disk"
|
||||
@@ -0,0 +1,44 @@
|
||||
TITLE comment: None given
|
||||
TITLE Source:
|
||||
TITLE Structure Summary: Fe2 S4
|
||||
TITLE Reduced formula: FeS2
|
||||
TITLE space group: (Pnnm), space number: (58)
|
||||
TITLE abc: 3.385200 4.447400 5.428700
|
||||
TITLE angles: 90.000000 90.000000 90.000000
|
||||
TITLE sites: 6
|
||||
* 1 Fe 0.000000 0.000000 0.000000
|
||||
* 2 Fe 0.500000 0.500000 0.500000
|
||||
* 3 S 0.000000 0.199900 0.378040
|
||||
* 4 S 0.000000 0.800100 0.621960
|
||||
* 5 S 0.500000 0.699900 0.121960
|
||||
* 6 S 0.500000 0.300100 0.878040
|
||||
|
||||
ATOMS
|
||||
* x y z ipot Atom Distance Number
|
||||
**********- ********- ******- ****** ****** ********** ********
|
||||
0 0 0 0 Fe 0 0
|
||||
-0.889035 -2.05227 -0 2 S 2.23656 4
|
||||
0.889035 2.05227 0 2 S 2.23656 22
|
||||
-1.33466 0.662084 -1.6926 2 S 2.2549 6
|
||||
-1.33466 0.662084 1.6926 2 S 2.2549 8
|
||||
1.33466 -0.662084 -1.6926 2 S 2.2549 11
|
||||
1.33466 -0.662084 1.6926 2 S 2.2549 13
|
||||
0 0 -3.3852 1 Fe 3.3852 18
|
||||
0 0 3.3852 1 Fe 3.3852 19
|
||||
-0.889035 3.37643 0 2 S 3.49152 9
|
||||
0.889035 -3.37643 -0 2 S 3.49152 15
|
||||
-3.11273 -0.662084 -1.6926 2 S 3.60449 1
|
||||
-3.11273 -0.662084 1.6926 2 S 3.60449 3
|
||||
3.11273 0.662084 -1.6926 2 S 3.60449 16
|
||||
3.11273 0.662084 1.6926 2 S 3.60449 20
|
||||
-2.2237 -2.71435 -1.6926 1 Fe 3.89582 2
|
||||
-2.2237 -2.71435 1.6926 1 Fe 3.89582 5
|
||||
-2.2237 2.71435 -1.6926 1 Fe 3.89582 7
|
||||
-2.2237 2.71435 1.6926 1 Fe 3.89582 10
|
||||
2.2237 -2.71435 -1.6926 1 Fe 3.89582 12
|
||||
2.2237 -2.71435 1.6926 1 Fe 3.89582 14
|
||||
2.2237 2.71435 -1.6926 1 Fe 3.89582 17
|
||||
2.2237 2.71435 1.6926 1 Fe 3.89582 21
|
||||
END
|
||||
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
TITLE comment: None given
|
||||
TITLE Source:
|
||||
TITLE Structure Summary: Fe2 S4
|
||||
TITLE Reduced formula: FeS2
|
||||
TITLE space group: (Pnnm), space number: (58)
|
||||
TITLE abc: 3.385200 4.447400 5.428700
|
||||
TITLE angles: 90.000000 90.000000 90.000000
|
||||
TITLE sites: 6
|
||||
* 1 Fe 0.000000 0.000000 0.000000
|
||||
* 2 Fe 0.500000 0.500000 0.500000
|
||||
* 3 S 0.000000 0.199900 0.378040
|
||||
* 4 S 0.000000 0.800100 0.621960
|
||||
* 5 S 0.500000 0.699900 0.121960
|
||||
* 6 S 0.500000 0.300100 0.878040
|
||||
|
||||
ATOMS
|
||||
* a b c ipot Atom Distance Number
|
||||
**********- ********- ******- ****** ****** ********** ********
|
||||
0 0 0 0 Fe 0 0
|
||||
-0.889035 -2.05227 -0 2 S 2.23656 4
|
||||
0.889035 2.05227 0 2 S 2.23656 22
|
||||
-1.33466 0.662084 -1.6926 2 S 2.2549 6
|
||||
-1.33466 0.662084 1.6926 2 S 2.2549 8
|
||||
1.33466 -0.662084 -1.6926 2 S 2.2549 11
|
||||
1.33466 -0.662084 1.6926 2 S 2.2549 13
|
||||
0 0 -3.3852 1 Fe 3.3852 18
|
||||
0 0 3.3852 1 Fe 3.3852 19
|
||||
-0.889035 3.37643 0 2 S 3.49152 9
|
||||
0.889035 -3.37643 -0 2 S 3.49152 15
|
||||
-3.11273 -0.662084 -1.6926 2 S 3.60449 1
|
||||
-3.11273 -0.662084 1.6926 2 S 3.60449 3
|
||||
3.11273 0.662084 -1.6926 2 S 3.60449 16
|
||||
3.11273 0.662084 1.6926 2 S 3.60449 20
|
||||
-2.2237 -2.71435 -1.6926 1 Fe 3.89582 2
|
||||
-2.2237 -2.71435 1.6926 1 Fe 3.89582 5
|
||||
-2.2237 2.71435 -1.6926 1 Fe 3.89582 7
|
||||
-2.2237 2.71435 1.6926 1 Fe 3.89582 10
|
||||
2.2237 -2.71435 -1.6926 1 Fe 3.89582 12
|
||||
2.2237 -2.71435 1.6926 1 Fe 3.89582 14
|
||||
2.2237 2.71435 -1.6926 1 Fe 3.89582 17
|
||||
2.2237 2.71435 1.6926 1 Fe 3.89582 21
|
||||
END
|
||||
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
TITLE comment: None given
|
||||
TITLE Source:
|
||||
TITLE Structure Summary: Fe2 S4
|
||||
TITLE Reduced formula: FeS2
|
||||
TITLE space group: (Pnnm), space number: (58)
|
||||
TITLE abc: 3.385200 4.447400 5.428700
|
||||
TITLE angles: 90.000000 90.000000 90.000000
|
||||
TITLE sites: 6
|
||||
* 1 Fe 0.000000 0.000000 0.000000
|
||||
* 2 Fe 0.500000 0.500000 0.500000
|
||||
* 3 S 0.000000 0.199900 0.378040
|
||||
* 4 S 0.000000 0.800100 0.621960
|
||||
* 5 S 0.500000 0.699900 0.121960
|
||||
* 6 S 0.500000 0.300100 0.878040
|
||||
|
||||
POTENTIALS
|
||||
* pot Z tag lmax1 lmax2 xnatph(stoichometry) spinph
|
||||
******- **- ****- ******- ******- ********************** ********
|
||||
0 26 Fe -1 -1 0.0001 0
|
||||
1 26 Fe -1 -1 2 0
|
||||
2 16 S -1 -1 4 0
|
||||
END
|
||||
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
TITLE comment: None given
|
||||
TITLE Source:
|
||||
TITLE Structure Summary: Fe2 S4
|
||||
TITLE Reduced formula: FeS2
|
||||
TITLE space group: (Pnnm), space number: (58)
|
||||
TITLE abc: 3.385200 4.447400 5.428700
|
||||
TITLE angles: 90.000000 90.000000 90.000000
|
||||
TITLE sites: 6
|
||||
* 1 Fe 0.000000 0.000000 0.000000
|
||||
* 2 Fe 0.500000 0.500000 0.500000
|
||||
* 3 S 0.000000 0.199900 0.378040
|
||||
* 4 S 0.000000 0.800100 0.621960
|
||||
* 5 S 0.500000 0.699900 0.121960
|
||||
* 6 S 0.500000 0.300100 0.878040
|
||||
|
||||
POTENTIALS
|
||||
*ipot Z tag lmax1 lmax2 xnatph(stoichometry) spinph
|
||||
******- **- ****- ******- ******- ********************** ********
|
||||
0 26 Fe -1 -1 0.0001 0
|
||||
1 26 Fe -1 -1 2 0
|
||||
2 16 S -1 -1 4 0
|
||||
END
|
||||
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
* This FEFF.inp file generated by pymatgen
|
||||
TITLE comment: None given
|
||||
TITLE Source:
|
||||
TITLE Structure Summary: Fe2 S4
|
||||
TITLE Reduced formula: FeS2
|
||||
TITLE space group: (Pnnm), space number: (58)
|
||||
TITLE abc: 3.385200 4.447400 5.428700
|
||||
TITLE angles: 90.000000 90.000000 90.000000
|
||||
TITLE sites: 6
|
||||
* 1 Fe 0.000000 0.000000 0.000000
|
||||
* 2 Fe 0.500000 0.500000 0.500000
|
||||
* 3 S 0.000000 0.199900 0.378040
|
||||
* 4 S 0.000000 0.800100 0.621960
|
||||
* 5 S 0.500000 0.699900 0.121960
|
||||
* 6 S 0.500000 0.300100 0.878040
|
||||
|
||||
POTENTIALS
|
||||
*ipot Z tag lmax1 lmax2 xnatph(stoichometry) spinph
|
||||
******- **- ****- ******- ******- ********************** ********
|
||||
0 26 Fe -1 -1 0.0001 0
|
||||
1 26 Fe -1 -1 2 0
|
||||
2 16 S -1 -1 4 0
|
||||
|
||||
ATOMS
|
||||
* x y z ipot Atom Distance Number
|
||||
**********- ********- ******- ****** ****** ********** ********
|
||||
0 0 0 0 Fe 0 0
|
||||
-0.889035 -2.05227 -0 2 S 2.23656 4
|
||||
0.889035 2.05227 0 2 S 2.23656 22
|
||||
-1.33466 0.662084 -1.6926 2 S 2.2549 6
|
||||
-1.33466 0.662084 1.6926 2 S 2.2549 8
|
||||
1.33466 -0.662084 -1.6926 2 S 2.2549 11
|
||||
1.33466 -0.662084 1.6926 2 S 2.2549 13
|
||||
0 0 -3.3852 1 Fe 3.3852 18
|
||||
0 0 3.3852 1 Fe 3.3852 19
|
||||
-0.889035 3.37643 0 2 S 3.49152 9
|
||||
0.889035 -3.37643 -0 2 S 3.49152 15
|
||||
-3.11273 -0.662084 -1.6926 2 S 3.60449 1
|
||||
-3.11273 -0.662084 1.6926 2 S 3.60449 3
|
||||
3.11273 0.662084 -1.6926 2 S 3.60449 16
|
||||
3.11273 0.662084 1.6926 2 S 3.60449 20
|
||||
-2.2237 -2.71435 -1.6926 1 Fe 3.89582 2
|
||||
-2.2237 -2.71435 1.6926 1 Fe 3.89582 5
|
||||
-2.2237 2.71435 -1.6926 1 Fe 3.89582 7
|
||||
-2.2237 2.71435 1.6926 1 Fe 3.89582 10
|
||||
2.2237 -2.71435 -1.6926 1 Fe 3.89582 12
|
||||
2.2237 -2.71435 1.6926 1 Fe 3.89582 14
|
||||
2.2237 2.71435 -1.6926 1 Fe 3.89582 17
|
||||
2.2237 2.71435 1.6926 1 Fe 3.89582 21
|
||||
END
|
||||
|
||||
|
||||
Binary file not shown.
@@ -4943,6 +4943,20 @@ class HistoryDatasetAssociation(DatasetInstance, HasTags, Dictifiable, UsesAnnot
|
||||
self.copied_from_history_dataset_association = copied_from_history_dataset_association
|
||||
self.copied_from_library_dataset_dataset_association = copied_from_library_dataset_dataset_association
|
||||
|
||||
def __strict_check_before_flush__(self):
|
||||
if self.extension != "len":
|
||||
# TODO: Custom builds (with .len extension) do not get a history or a HID.
|
||||
# These should get some other type of permanent storage, perhaps UserDatasetAssociation ?
|
||||
# Everything else needs to have a hid and a history
|
||||
if not self.history and not getattr(self, "history_id", None):
|
||||
raise Exception(f"HistoryDatasetAssociation {self} without history detected, this is not valid")
|
||||
elif not self.hid:
|
||||
raise Exception(f"HistoryDatasetAssociation {self} without hid, this is not valid")
|
||||
elif self.dataset.file_size is None and self.dataset.state not in self.dataset.no_data_states:
|
||||
raise Exception(
|
||||
f"HistoryDatasetAssociation {self} in state {self.dataset.state} with null file size, this is not valid"
|
||||
)
|
||||
|
||||
@property
|
||||
def user(self):
|
||||
if self.history:
|
||||
@@ -7021,6 +7035,9 @@ class DatasetCollectionElement(Base, Dictifiable, Serializable):
|
||||
self.element_index = element_index
|
||||
self.element_identifier = element_identifier or str(element_index)
|
||||
|
||||
def __strict_check_before_flush__(self):
|
||||
assert self.element_object, "Dataset Collection Element without child entity detected, this is not valid"
|
||||
|
||||
@property
|
||||
def element_type(self):
|
||||
if self.hda:
|
||||
|
||||
@@ -3,6 +3,7 @@ Shared model and mapping code between Galaxy and Tool Shed, trying to
|
||||
generalize to generic database connections.
|
||||
"""
|
||||
import contextlib
|
||||
import logging
|
||||
import os
|
||||
import threading
|
||||
from contextvars import ContextVar
|
||||
@@ -29,6 +30,8 @@ from galaxy.util.bunch import Bunch
|
||||
if TYPE_CHECKING:
|
||||
from galaxy.model.store import SessionlessContext
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
# Create a ContextVar with mutable state, this allows sync tasks in the context
|
||||
# of a request (which run within a threadpool) to see changes to the ContextVar
|
||||
# state. See https://github.com/tiangolo/fastapi/issues/953#issuecomment-586006249
|
||||
@@ -146,23 +149,14 @@ def versioned_objects(iter):
|
||||
|
||||
def versioned_objects_strict(iter):
|
||||
for obj in iter:
|
||||
if hasattr(obj, "__strict_check_before_flush__"):
|
||||
obj.__strict_check_before_flush__()
|
||||
if hasattr(obj, "__create_version__"):
|
||||
if obj.extension != "len":
|
||||
# TODO: Custom builds (with .len extension) do not get a history or a HID.
|
||||
# These should get some other type of permanent storage, perhaps UserDatasetAssociation ?
|
||||
# Everything else needs to have a hid and a history
|
||||
if not obj.history and not obj.history_id:
|
||||
raise Exception(f"HistoryDatasetAssociation {obj} without history detected, this is not valid")
|
||||
elif not obj.hid:
|
||||
raise Exception(f"HistoryDatasetAssociation {obj} without hid, this is not valid")
|
||||
elif obj.dataset.file_size is None and obj.dataset.state not in obj.dataset.no_data_states:
|
||||
raise Exception(
|
||||
f"HistoryDatasetAssociation {obj} in state {obj.dataset.state} with null file size, this is not valid"
|
||||
)
|
||||
yield obj
|
||||
|
||||
|
||||
if os.environ.get("GALAXY_TEST_RAISE_EXCEPTION_ON_HISTORYLESS_HDA"):
|
||||
log.debug("Using strict flush checks")
|
||||
versioned_objects = versioned_objects_strict # noqa: F811
|
||||
|
||||
|
||||
|
||||
@@ -21,7 +21,10 @@ from galaxy.schema.fields import (
|
||||
EncodedDatabaseIdField,
|
||||
)
|
||||
from galaxy.schema.schema import Model
|
||||
from galaxy.schema.types import AbsoluteOrRelativeUrl
|
||||
from galaxy.schema.types import (
|
||||
AbsoluteOrRelativeUrl,
|
||||
OffsetNaiveDatetime,
|
||||
)
|
||||
|
||||
|
||||
class NotificationVariant(str, Enum):
|
||||
@@ -246,12 +249,12 @@ class NotificationCreateData(Model):
|
||||
category: NotificationCategory = NotificationCategoryField
|
||||
variant: NotificationVariant = NotificationVariantField
|
||||
content: AnyNotificationContent
|
||||
publication_time: Optional[datetime] = Field(
|
||||
publication_time: Optional[OffsetNaiveDatetime] = Field(
|
||||
None,
|
||||
title="Publication time",
|
||||
description="The time when the notification should be published. Notifications can be created and then scheduled to be published at a later time.",
|
||||
)
|
||||
expiration_time: Optional[datetime] = Field(
|
||||
expiration_time: Optional[OffsetNaiveDatetime] = Field(
|
||||
None,
|
||||
title="Expiration time",
|
||||
description="The time when the notification should expire. By default it will expire after 6 months. Expired notifications will be permanently deleted.",
|
||||
@@ -351,12 +354,12 @@ class NotificationBroadcastUpdateRequest(NotificationUpdateRequest):
|
||||
title="Variant",
|
||||
description="The variant of the notification. Used to express the importance of the notification.",
|
||||
)
|
||||
publication_time: Optional[datetime] = Field(
|
||||
publication_time: Optional[OffsetNaiveDatetime] = Field(
|
||||
None,
|
||||
title="Publication time",
|
||||
description="The time when the notification should be published. Notifications can be created and then scheduled to be published at a later time.",
|
||||
)
|
||||
expiration_time: Optional[datetime] = Field(
|
||||
expiration_time: Optional[OffsetNaiveDatetime] = Field(
|
||||
None,
|
||||
title="Expiration time",
|
||||
description="The time when the notification should expire. By default it will expire after 6 months. Expired notifications will be permanently deleted.",
|
||||
|
||||
@@ -548,6 +548,9 @@ def send_file(start_response, trans, body):
|
||||
trans.response.headers["accept-ranges"] = "bytes"
|
||||
start = None
|
||||
end = None
|
||||
if trans.request.method == "HEAD":
|
||||
trans.response.headers["content-length"] = os.path.getsize(body.name)
|
||||
body = b""
|
||||
if trans.request.range:
|
||||
start = int(trans.request.range.start)
|
||||
file_size = int(trans.response.headers["content-length"])
|
||||
@@ -555,7 +558,8 @@ def send_file(start_response, trans, body):
|
||||
trans.response.headers["content-length"] = str(end - start)
|
||||
trans.response.headers["content-range"] = f"bytes {start}-{end - 1}/{file_size}"
|
||||
trans.response.status = 206
|
||||
body = iterate_file(body, start, end)
|
||||
if body:
|
||||
body = iterate_file(body, start, end)
|
||||
start_response(trans.response.wsgi_status(), trans.response.wsgi_headeritems())
|
||||
return body
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ API operations on Notification objects.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
from fastapi import (
|
||||
@@ -33,6 +32,7 @@ from galaxy.schema.notifications import (
|
||||
UserNotificationsBatchUpdateRequest,
|
||||
UserNotificationUpdateRequest,
|
||||
)
|
||||
from galaxy.schema.types import OffsetNaiveDatetime
|
||||
from galaxy.webapps.galaxy.services.notifications import NotificationService
|
||||
from . import (
|
||||
depends,
|
||||
@@ -56,7 +56,7 @@ class FastAPINotifications:
|
||||
def get_notifications_status(
|
||||
self,
|
||||
trans: ProvidesUserContext = DependsOnTrans,
|
||||
since: datetime = Query(),
|
||||
since: OffsetNaiveDatetime = Query(),
|
||||
) -> NotificationStatusSummary:
|
||||
"""Anonymous users cannot receive personal notifications, only broadcasted notifications."""
|
||||
return self.service.get_notifications_status(trans, since)
|
||||
|
||||
@@ -933,6 +933,14 @@ def populate_api_routes(webapp, app):
|
||||
parent_resources=dict(member_name="job", collection_name="jobs"),
|
||||
)
|
||||
|
||||
webapp.mapper.connect(
|
||||
"index",
|
||||
"/api/jobs/{job_id}/files",
|
||||
controller="job_files",
|
||||
action="index",
|
||||
conditions=dict(method=["HEAD"]),
|
||||
)
|
||||
|
||||
webapp.mapper.resource(
|
||||
"port",
|
||||
"ports",
|
||||
|
||||
@@ -880,6 +880,8 @@ class GalaxyTestDriver(TestDriver):
|
||||
"""Setup various variables used to launch a Galaxy server."""
|
||||
config_object = self._ensure_config_object(config_object)
|
||||
self.external_galaxy = os.environ.get("GALAXY_TEST_EXTERNAL", None)
|
||||
if not self.external_galaxy:
|
||||
os.environ["GALAXY_TEST_STRICT_CHECKS"] = "1"
|
||||
|
||||
# Allow controlling the log format
|
||||
self.log_format = os.environ.get("GALAXY_TEST_LOG_FORMAT")
|
||||
|
||||
@@ -63,6 +63,10 @@ class TestJobFilesIntegration(integration_util.IntegrationTestCase):
|
||||
job_id, job_key = self._api_job_keys(job)
|
||||
data = {"path": self.input_hda.get_file_name(), "job_key": job_key}
|
||||
get_url = self._api_url(f"jobs/{job_id}/files", use_key=True)
|
||||
head_response = requests.head(get_url, params=data)
|
||||
api_asserts.assert_status_code_is_ok(head_response)
|
||||
assert head_response.text == ""
|
||||
assert head_response.headers["content-length"] == str(len(TEST_INPUT_TEXT))
|
||||
response = requests.get(get_url, params=data)
|
||||
api_asserts.assert_status_code_is_ok(response)
|
||||
assert response.text == TEST_INPUT_TEXT
|
||||
|
||||
@@ -269,6 +269,16 @@ class TestNotificationsIntegration(IntegrationTestCase):
|
||||
assert "Scheduled" in subjects
|
||||
assert "Expired" in subjects
|
||||
|
||||
def test_notification_input_dates_consider_timezone(self):
|
||||
payload = notification_broadcast_test_data(subject="Test", message="Test")
|
||||
payload["publication_time"] = "2021-01-01T12:00:00+02:00"
|
||||
payload["expiration_time"] = "2021-01-01T12:00:00Z"
|
||||
response = self._post("notifications/broadcast", data=payload, admin=True, json=True)
|
||||
self._assert_status_code_is_ok(response)
|
||||
notification = response.json()["notification"]
|
||||
assert notification["publication_time"] == "2021-01-01T10:00:00"
|
||||
assert notification["expiration_time"] == "2021-01-01T12:00:00"
|
||||
|
||||
def test_broadcast_notification_action_links(self):
|
||||
# Broadcast notifications can have relative and absolute links
|
||||
response = self._send_broadcast_notification(
|
||||
|
||||
Reference in New Issue
Block a user