timewise 0.4.12__tar.gz → 0.5.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {timewise-0.4.12 → timewise-0.5.3}/PKG-INFO +13 -9
- timewise-0.5.3/pyproject.toml +50 -0
- {timewise-0.4.12 → timewise-0.5.3}/timewise/__init__.py +1 -1
- {timewise-0.4.12 → timewise-0.5.3}/timewise/big_parent_sample.py +4 -5
- {timewise-0.4.12 → timewise-0.5.3}/timewise/config_loader.py +2 -2
- timewise-0.5.3/timewise/general.py +52 -0
- {timewise-0.4.12 → timewise-0.5.3}/timewise/parent_sample_base.py +9 -7
- {timewise-0.4.12 → timewise-0.5.3}/timewise/utils.py +108 -17
- {timewise-0.4.12 → timewise-0.5.3}/timewise/wise_bigdata_desy_cluster.py +57 -51
- {timewise-0.4.12 → timewise-0.5.3}/timewise/wise_data_base.py +172 -76
- {timewise-0.4.12 → timewise-0.5.3}/timewise/wise_data_by_visit.py +1 -1
- timewise-0.4.12/pyproject.toml +0 -50
- timewise-0.4.12/timewise/general.py +0 -44
- {timewise-0.4.12 → timewise-0.5.3}/LICENSE +0 -0
- {timewise-0.4.12 → timewise-0.5.3}/README.md +0 -0
- {timewise-0.4.12 → timewise-0.5.3}/timewise/cli.py +0 -0
- {timewise-0.4.12 → timewise-0.5.3}/timewise/point_source_utils.py +0 -0
- {timewise-0.4.12 → timewise-0.5.3}/timewise/wise_flux_conversion_correction.dat +0 -0
|
@@ -1,37 +1,41 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: timewise
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.3
|
|
4
4
|
Summary: A small package to download infrared data from the WISE satellite
|
|
5
|
-
Home-page: https://github.com/JannisNe/timewise
|
|
6
5
|
License: MIT
|
|
7
6
|
Author: Jannis Necker
|
|
8
7
|
Author-email: jannis.necker@gmail.com
|
|
9
|
-
Requires-Python: >=3.
|
|
8
|
+
Requires-Python: >=3.9,<3.12
|
|
10
9
|
Classifier: License :: OSI Approved :: MIT License
|
|
11
10
|
Classifier: Programming Language :: Python :: 3
|
|
12
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
13
11
|
Classifier: Programming Language :: Python :: 3.9
|
|
14
12
|
Classifier: Programming Language :: Python :: 3.10
|
|
15
13
|
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
-
|
|
14
|
+
Provides-Extra: dev
|
|
15
|
+
Provides-Extra: docs
|
|
16
|
+
Requires-Dist: astropy (>=5.1,<6.0.0)
|
|
17
17
|
Requires-Dist: backoff (>=2.1.2,<3.0.0)
|
|
18
|
+
Requires-Dist: coveralls (>=3.3.1,<4.0.0) ; extra == "dev"
|
|
18
19
|
Requires-Dist: jupyter[jupyter] (>=1.0.0,<2.0.0)
|
|
19
20
|
Requires-Dist: jupyterlab[jupyter] (>=4.0.6,<5.0.0)
|
|
20
21
|
Requires-Dist: matplotlib (>=3.5.3,<4.0.0)
|
|
22
|
+
Requires-Dist: myst-parser (>=1,<3) ; extra == "docs"
|
|
21
23
|
Requires-Dist: numpy (>=1.23.2,<2.0.0)
|
|
22
24
|
Requires-Dist: pandas (>=1.4.3,<3.0.0)
|
|
23
25
|
Requires-Dist: pydantic (>=1.9.0,<2.0.0)
|
|
24
|
-
Requires-Dist:
|
|
26
|
+
Requires-Dist: pytest (>=7.2.2,<8.0.0) ; extra == "dev"
|
|
27
|
+
Requires-Dist: pyvo (>=1.7.0,<2.0.0)
|
|
25
28
|
Requires-Dist: requests (>=2.28.1,<3.0.0)
|
|
26
|
-
Requires-Dist: scikit-image (>=0.19.3,<0.
|
|
29
|
+
Requires-Dist: scikit-image (>=0.19.3,<0.22.0)
|
|
27
30
|
Requires-Dist: scikit-learn (>=1.3.0,<2.0.0)
|
|
28
|
-
Requires-Dist: seaborn (>=0.11.2,<0.
|
|
31
|
+
Requires-Dist: seaborn (>=0.11.2,<0.14.0)
|
|
32
|
+
Requires-Dist: sphinx-rtd-theme (>=1.3.0,<2.0.0) ; extra == "docs"
|
|
29
33
|
Requires-Dist: tqdm (>=4.64.0,<5.0.0)
|
|
30
34
|
Requires-Dist: urllib3 (==1.26.15)
|
|
31
35
|
Requires-Dist: virtualenv (>=20.16.3,<21.0.0)
|
|
32
36
|
Project-URL: Bug Tracker, https://github.com/JannisNe/timewise/issues
|
|
33
37
|
Project-URL: Documentation, https://timewise.readthedocs.io/en/latest
|
|
34
|
-
Project-URL:
|
|
38
|
+
Project-URL: Homepage, https://github.com/JannisNe/timewise
|
|
35
39
|
Description-Content-Type: text/markdown
|
|
36
40
|
|
|
37
41
|
[](https://github.com/JannisNe/timewise/actions/workflows/continous_integration.yml)
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["poetry-core>=2.0.0"]
|
|
3
|
+
build-backend = "poetry.core.masonry.api"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "timewise"
|
|
7
|
+
version = "0.5.3"
|
|
8
|
+
description = "A small package to download infrared data from the WISE satellite"
|
|
9
|
+
authors = [
|
|
10
|
+
{ name = "Jannis Necker", email = "jannis.necker@gmail.com" },
|
|
11
|
+
]
|
|
12
|
+
license = { text = "MIT" }
|
|
13
|
+
readme = "README.md"
|
|
14
|
+
requires-python = ">=3.9,<3.12"
|
|
15
|
+
dependencies = [
|
|
16
|
+
"tqdm>=4.64.0,<5.0.0",
|
|
17
|
+
"requests>=2.28.1,<3.0.0",
|
|
18
|
+
"pandas>=1.4.3,<3.0.0",
|
|
19
|
+
"numpy>=1.23.2,<2.0.0",
|
|
20
|
+
"pyvo>=1.7.0,<2.0.0",
|
|
21
|
+
"astropy>=5.1,<6.0.0",
|
|
22
|
+
"matplotlib>=3.5.3,<4.0.0",
|
|
23
|
+
"scikit-image>=0.19.3,<0.22.0",
|
|
24
|
+
"backoff>=2.1.2,<3.0.0",
|
|
25
|
+
"virtualenv>=20.16.3,<21.0.0",
|
|
26
|
+
"seaborn>=0.11.2,<0.14.0",
|
|
27
|
+
"urllib3==1.26.15",
|
|
28
|
+
"pydantic>=1.9.0,<2.0.0",
|
|
29
|
+
"scikit-learn>=1.3.0,<2.0.0",
|
|
30
|
+
"jupyterlab[jupyter]>=4.0.6,<5.0.0",
|
|
31
|
+
"jupyter[jupyter]>=1.0.0,<2.0.0",
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
[project.scripts]
|
|
35
|
+
timewise = "timewise.cli:timewise_cli"
|
|
36
|
+
|
|
37
|
+
[project.urls]
|
|
38
|
+
Homepage = "https://github.com/JannisNe/timewise"
|
|
39
|
+
Documentation = "https://timewise.readthedocs.io/en/latest"
|
|
40
|
+
"Bug Tracker" = "https://github.com/JannisNe/timewise/issues"
|
|
41
|
+
|
|
42
|
+
[project.optional-dependencies]
|
|
43
|
+
dev = [
|
|
44
|
+
"coveralls>=3.3.1,<4.0.0",
|
|
45
|
+
"pytest>=7.2.2,<8.0.0",
|
|
46
|
+
]
|
|
47
|
+
docs = [
|
|
48
|
+
"myst-parser>=1,<3",
|
|
49
|
+
"sphinx-rtd-theme>=1.3.0,<2.0.0",
|
|
50
|
+
]
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import gc
|
|
2
|
-
import os
|
|
3
2
|
import pickle
|
|
4
3
|
import threading
|
|
5
4
|
import time
|
|
@@ -30,7 +29,7 @@ class BigParentSampleBase(ParentSampleBase):
|
|
|
30
29
|
self._keep_df_in_memory = keep_file_in_memory
|
|
31
30
|
self._time_when_df_was_used_last = time.time()
|
|
32
31
|
self._df = None
|
|
33
|
-
self._cache_file =
|
|
32
|
+
self._cache_file = self.cache_dir / "cache.pkl"
|
|
34
33
|
self._lock_cache_file = False
|
|
35
34
|
|
|
36
35
|
self._clean_thread = threading.Thread(target=self._periodically_drop_df_to_disk, daemon=True, name='ParentSampleCleanThread').start()
|
|
@@ -50,7 +49,7 @@ class BigParentSampleBase(ParentSampleBase):
|
|
|
50
49
|
|
|
51
50
|
if isinstance(self._df, type(None)):
|
|
52
51
|
|
|
53
|
-
if
|
|
52
|
+
if self._cache_file.is_file():
|
|
54
53
|
logger.debug(f'loading from {self._cache_file}')
|
|
55
54
|
self._wait_for_unlock_cache_file()
|
|
56
55
|
self._lock_cache_file = True
|
|
@@ -97,9 +96,9 @@ class BigParentSampleBase(ParentSampleBase):
|
|
|
97
96
|
logger.debug('stopped clean thread')
|
|
98
97
|
|
|
99
98
|
def __del__(self):
|
|
100
|
-
if hasattr(self, "_cache_file") and
|
|
99
|
+
if hasattr(self, "_cache_file") and self._cache_file.is_file():
|
|
101
100
|
logger.debug(f'removing {self._cache_file}')
|
|
102
|
-
|
|
101
|
+
self._cache_file.unlink()
|
|
103
102
|
|
|
104
103
|
if hasattr(self, "clean_thread"):
|
|
105
104
|
logger.debug(f'stopping clean thread')
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
import yaml
|
|
3
3
|
import json
|
|
4
|
-
import os
|
|
5
4
|
import inspect
|
|
6
5
|
from pydantic import BaseModel, validator
|
|
7
6
|
import pandas as pd
|
|
8
7
|
import importlib
|
|
8
|
+
from pathlib import Path
|
|
9
9
|
|
|
10
10
|
from timewise.parent_sample_base import ParentSampleBase
|
|
11
11
|
from timewise.wise_data_base import WISEDataBase
|
|
@@ -80,7 +80,7 @@ class TimewiseConfigLoader(BaseModel):
|
|
|
80
80
|
@validator("filename")
|
|
81
81
|
def validate_file(cls, v: str):
|
|
82
82
|
if v is not None:
|
|
83
|
-
if not
|
|
83
|
+
if not Path(v).is_file():
|
|
84
84
|
raise ValueError(f"No file {v}!")
|
|
85
85
|
return v
|
|
86
86
|
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import os
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
# Setting up the Logger
|
|
7
|
+
main_logger = logging.getLogger('timewise')
|
|
8
|
+
logger_format = logging.Formatter(
|
|
9
|
+
'%(levelname)s:%(name)s:%(funcName)s - [%(threadName)s] - %(asctime)s: \n\t%(message)s', "%H:%M:%S"
|
|
10
|
+
)
|
|
11
|
+
stream_handler = logging.StreamHandler()
|
|
12
|
+
stream_handler.setFormatter(logger_format)
|
|
13
|
+
main_logger.addHandler(stream_handler)
|
|
14
|
+
main_logger.propagate = False # do not propagate to root logger
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def get_directories() -> dict[str, Path | None]:
|
|
20
|
+
# Setting up data directory
|
|
21
|
+
DATA_DIR_KEY = 'TIMEWISE_DATA'
|
|
22
|
+
if DATA_DIR_KEY in os.environ:
|
|
23
|
+
data_dir = Path(os.environ[DATA_DIR_KEY]).expanduser()
|
|
24
|
+
else:
|
|
25
|
+
logger.warning(f'{DATA_DIR_KEY} not set! Using home directory.')
|
|
26
|
+
data_dir = Path('~/').expanduser()
|
|
27
|
+
|
|
28
|
+
BIGDATA_DIR_KEY = 'TIMEWISE_BIGDATA'
|
|
29
|
+
if BIGDATA_DIR_KEY in os.environ:
|
|
30
|
+
bigdata_dir = Path(os.environ[BIGDATA_DIR_KEY]).expanduser()
|
|
31
|
+
logger.info(f"Using bigdata directory {bigdata_dir}")
|
|
32
|
+
else:
|
|
33
|
+
bigdata_dir = None
|
|
34
|
+
logger.info(f"No bigdata directory set.")
|
|
35
|
+
|
|
36
|
+
output_dir = data_dir / 'output'
|
|
37
|
+
plots_dir = output_dir / 'plots'
|
|
38
|
+
cache_dir = data_dir / 'cache'
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
'data_dir': data_dir,
|
|
42
|
+
'bigdata_dir': bigdata_dir,
|
|
43
|
+
'output_dir': output_dir,
|
|
44
|
+
'plots_dir': plots_dir,
|
|
45
|
+
'cache_dir': cache_dir
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def backoff_hndlr(details):
|
|
50
|
+
logger.info("Backing off {wait:0.1f} seconds after {tries} tries "
|
|
51
|
+
"calling function {target} with args {args} and kwargs "
|
|
52
|
+
"{kwargs}".format(**details))
|
|
@@ -3,7 +3,7 @@ import pandas as pd
|
|
|
3
3
|
import numpy as np
|
|
4
4
|
import logging
|
|
5
5
|
|
|
6
|
-
from timewise.general import
|
|
6
|
+
from timewise.general import get_directories
|
|
7
7
|
from timewise.utils import plot_sdss_cutout, plot_panstarrs_cutout
|
|
8
8
|
|
|
9
9
|
|
|
@@ -26,14 +26,14 @@ class ParentSampleBase(abc.ABC):
|
|
|
26
26
|
|
|
27
27
|
def __init__(self, base_name):
|
|
28
28
|
# set up directories
|
|
29
|
-
|
|
30
|
-
self.
|
|
29
|
+
d = get_directories()
|
|
30
|
+
self.cache_dir = d["cache_dir"] / base_name
|
|
31
|
+
self.plots_dir = d["plots_dir"] / base_name
|
|
31
32
|
|
|
32
33
|
for d in [self.cache_dir, self.plots_dir]:
|
|
33
|
-
|
|
34
|
-
os.makedirs(d)
|
|
34
|
+
d.parent.mkdir(parents=True, exist_ok=True)
|
|
35
35
|
|
|
36
|
-
self.local_sample_copy =
|
|
36
|
+
self.local_sample_copy = self.cache_dir / 'sample.csv'
|
|
37
37
|
|
|
38
38
|
def plot_cutout(self, ind, arcsec=20, interactive=False, **kwargs):
|
|
39
39
|
"""
|
|
@@ -54,9 +54,10 @@ class ParentSampleBase(abc.ABC):
|
|
|
54
54
|
|
|
55
55
|
fn = kwargs.pop(
|
|
56
56
|
"fn",
|
|
57
|
-
[
|
|
57
|
+
[self.plots_dir / f"{i}_{r[self.default_keymap['id']]}.pdf"
|
|
58
58
|
for i, r in sel.iterrows()]
|
|
59
59
|
)
|
|
60
|
+
self.plots_dir.mkdir(parents=True, exist_ok=True)
|
|
60
61
|
|
|
61
62
|
logger.debug(f"\nRA: {ra}\nDEC: {dec}\nTITLE: {title}\nFN: {fn}")
|
|
62
63
|
ou = list()
|
|
@@ -84,4 +85,5 @@ class ParentSampleBase(abc.ABC):
|
|
|
84
85
|
|
|
85
86
|
def save_local(self):
|
|
86
87
|
logger.debug(f"saving under {self.local_sample_copy}")
|
|
88
|
+
self.local_sample_copy.parent.mkdir(parents=True, exist_ok=True)
|
|
87
89
|
self.df.to_csv(self.local_sample_copy)
|
|
@@ -13,14 +13,19 @@ from astropy.table import Table
|
|
|
13
13
|
from PIL import Image
|
|
14
14
|
from io import BytesIO
|
|
15
15
|
import hashlib
|
|
16
|
+
from threading import Thread
|
|
17
|
+
from queue import Queue
|
|
18
|
+
import sys
|
|
16
19
|
|
|
17
|
-
|
|
18
|
-
from timewise.general import cache_dir, backoff_hndlr
|
|
20
|
+
from timewise.general import backoff_hndlr, get_directories
|
|
19
21
|
|
|
20
22
|
|
|
21
23
|
logger = logging.getLogger(__name__)
|
|
22
24
|
mirong_url = 'http://staff.ustc.edu.cn/~jnac/data_public/wisevar.txt'
|
|
23
|
-
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def get_mirong_path():
|
|
28
|
+
return get_directories()['cache_dir'] / 'mirong_sample.csv'
|
|
24
29
|
|
|
25
30
|
|
|
26
31
|
@cache
|
|
@@ -30,7 +35,8 @@ def get_2d_gaussian_correction(cl):
|
|
|
30
35
|
|
|
31
36
|
def get_mirong_sample():
|
|
32
37
|
|
|
33
|
-
|
|
38
|
+
mirong_path = get_mirong_path()
|
|
39
|
+
if not mirong_path.is_file():
|
|
34
40
|
|
|
35
41
|
logger.info(f'getting MIRONG sample from {mirong_url}')
|
|
36
42
|
r = requests.get(mirong_url)
|
|
@@ -45,16 +51,16 @@ def get_mirong_sample():
|
|
|
45
51
|
mirong_sample = pd.DataFrame(lll[1:-1], columns=lll[0])
|
|
46
52
|
mirong_sample['ra'] = mirong_sample['RA']
|
|
47
53
|
mirong_sample['dec'] = mirong_sample['DEC']
|
|
48
|
-
logger.debug(f'saving to {local_copy}')
|
|
49
|
-
|
|
50
|
-
mirong_sample.to_csv(local_copy, index=False)
|
|
51
54
|
logger.info(f'found {len(mirong_sample)} objects in MIRONG Sample')
|
|
55
|
+
|
|
52
56
|
mirong_sample.drop(columns=['ra', 'dec'], inplace=True)
|
|
53
|
-
|
|
57
|
+
logger.debug(f'saving to {mirong_path}')
|
|
58
|
+
mirong_path.parent.mkdir(parents=True, exist_ok=True)
|
|
59
|
+
mirong_sample.to_csv(mirong_path, index=False)
|
|
54
60
|
|
|
55
61
|
else:
|
|
56
|
-
logger.debug(f'loading {
|
|
57
|
-
mirong_sample = pd.read_csv(
|
|
62
|
+
logger.debug(f'loading {mirong_path}')
|
|
63
|
+
mirong_sample = pd.read_csv(mirong_path)
|
|
58
64
|
|
|
59
65
|
return mirong_sample
|
|
60
66
|
|
|
@@ -154,9 +160,10 @@ class PanSTARRSQueryError(Exception):
|
|
|
154
160
|
def load_cache_or_download(url):
|
|
155
161
|
logger.debug(f"loading or downloading {url}")
|
|
156
162
|
h = hashlib.md5(url.encode()).hexdigest()
|
|
157
|
-
|
|
163
|
+
cache_dir = get_directories()['cache_dir']
|
|
164
|
+
cache_file = cache_dir / (h + ".cache")
|
|
158
165
|
logger.debug(f"cache file is {cache_file}")
|
|
159
|
-
if not
|
|
166
|
+
if not cache_file.is_file():
|
|
160
167
|
logger.debug(f"downloading {url}")
|
|
161
168
|
r = requests.get(url)
|
|
162
169
|
with open(cache_file, 'wb') as f:
|
|
@@ -424,9 +431,48 @@ def get_excess_variance(y, y_err, mu):
|
|
|
424
431
|
|
|
425
432
|
class StableAsyncTAPJob(vo.dal.AsyncTAPJob):
|
|
426
433
|
"""
|
|
427
|
-
Implements backoff for call of phase which otherwise breaks the code if there are connection issues
|
|
434
|
+
Implements backoff for call of phase which otherwise breaks the code if there are connection issues.
|
|
435
|
+
Also stores the response of TapQuery.submit() under self.submit_response for debugging
|
|
428
436
|
"""
|
|
429
437
|
|
|
438
|
+
def __init__(self, url, *, session=None, delete=True):
|
|
439
|
+
super(StableAsyncTAPJob, self).__init__(url, session=session, delete=delete)
|
|
440
|
+
self.submit_response = None
|
|
441
|
+
|
|
442
|
+
@classmethod
|
|
443
|
+
def create(
|
|
444
|
+
cls, baseurl, query, *, language="ADQL", maxrec=None, uploads=None,
|
|
445
|
+
session=None, **keywords):
|
|
446
|
+
"""
|
|
447
|
+
creates a async tap job on the server under ``baseurl``
|
|
448
|
+
Raises requests.HTTPError if TAPQuery.submit() failes.
|
|
449
|
+
|
|
450
|
+
Parameters
|
|
451
|
+
----------
|
|
452
|
+
baseurl : str
|
|
453
|
+
the TAP baseurl
|
|
454
|
+
query : str
|
|
455
|
+
the query string
|
|
456
|
+
language : str
|
|
457
|
+
specifies the query language, default ADQL.
|
|
458
|
+
useful for services which allow to use the backend query language.
|
|
459
|
+
maxrec : int
|
|
460
|
+
the maximum records to return. defaults to the service default
|
|
461
|
+
uploads : dict
|
|
462
|
+
a mapping from table names to objects containing a votable
|
|
463
|
+
session : object
|
|
464
|
+
optional session to use for network requests
|
|
465
|
+
"""
|
|
466
|
+
tapquery = vo.dal.TAPQuery(
|
|
467
|
+
baseurl, query, mode="async", language=language, maxrec=maxrec,
|
|
468
|
+
uploads=uploads, session=session, **keywords)
|
|
469
|
+
response = tapquery.submit()
|
|
470
|
+
response.raise_for_status()
|
|
471
|
+
job = cls(response.url, session=session)
|
|
472
|
+
job._client_set_maxrec = maxrec
|
|
473
|
+
job.submit_response = response
|
|
474
|
+
return job
|
|
475
|
+
|
|
430
476
|
@property
|
|
431
477
|
@backoff.on_exception(
|
|
432
478
|
backoff.expo,
|
|
@@ -446,6 +492,7 @@ class StableTAPService(vo.dal.TAPService):
|
|
|
446
492
|
def submit_job(
|
|
447
493
|
self,
|
|
448
494
|
query,
|
|
495
|
+
*,
|
|
449
496
|
language="ADQL",
|
|
450
497
|
maxrec=None,
|
|
451
498
|
uploads=None,
|
|
@@ -454,10 +501,10 @@ class StableTAPService(vo.dal.TAPService):
|
|
|
454
501
|
return StableAsyncTAPJob.create(
|
|
455
502
|
self.baseurl,
|
|
456
503
|
query,
|
|
457
|
-
language,
|
|
458
|
-
maxrec,
|
|
459
|
-
uploads,
|
|
460
|
-
self._session,
|
|
504
|
+
language=language,
|
|
505
|
+
maxrec=maxrec,
|
|
506
|
+
uploads=uploads,
|
|
507
|
+
session=self._session,
|
|
461
508
|
**keywords
|
|
462
509
|
)
|
|
463
510
|
|
|
@@ -465,3 +512,47 @@ class StableTAPService(vo.dal.TAPService):
|
|
|
465
512
|
#######################################################
|
|
466
513
|
# END CUSTOM TAP Service #
|
|
467
514
|
###########################################################################################################
|
|
515
|
+
|
|
516
|
+
|
|
517
|
+
###########################################################################################################
|
|
518
|
+
# START CUSTOM TAP Service #
|
|
519
|
+
#######################################################
|
|
520
|
+
|
|
521
|
+
|
|
522
|
+
class ErrorQueue(Queue):
|
|
523
|
+
"""Queue subclass whose join() re-raises exceptions from worker threads."""
|
|
524
|
+
|
|
525
|
+
def __init__(self, *args, **kwargs):
|
|
526
|
+
super().__init__(*args, **kwargs)
|
|
527
|
+
self.error_queue = Queue()
|
|
528
|
+
|
|
529
|
+
def report_error(self, exc_info):
|
|
530
|
+
"""Called by workers to push an exception into the error queue."""
|
|
531
|
+
self.error_queue.put(exc_info)
|
|
532
|
+
# Also decrement unfinished_tasks, so join() won't block forever
|
|
533
|
+
with self.all_tasks_done:
|
|
534
|
+
self.unfinished_tasks = max(0, self.unfinished_tasks - 1)
|
|
535
|
+
self.all_tasks_done.notify_all()
|
|
536
|
+
|
|
537
|
+
def join(self):
|
|
538
|
+
"""Wait until all tasks are done, or raise if a worker failed."""
|
|
539
|
+
with self.all_tasks_done:
|
|
540
|
+
while self.unfinished_tasks:
|
|
541
|
+
if not self.error_queue.empty():
|
|
542
|
+
exc_info = self.error_queue.get()
|
|
543
|
+
raise exc_info[1].with_traceback(exc_info[2])
|
|
544
|
+
self.all_tasks_done.wait()
|
|
545
|
+
|
|
546
|
+
|
|
547
|
+
class ExceptionSafeThread(Thread):
|
|
548
|
+
"""Thread subclass that reports uncaught exceptions to the ErrorQueue."""
|
|
549
|
+
|
|
550
|
+
def __init__(self, error_queue: ErrorQueue, *args, **kwargs):
|
|
551
|
+
super().__init__(*args, **kwargs)
|
|
552
|
+
self.error_queue = error_queue
|
|
553
|
+
|
|
554
|
+
def run(self):
|
|
555
|
+
try:
|
|
556
|
+
super().run()
|
|
557
|
+
except Exception:
|
|
558
|
+
self.error_queue.report_error(sys.exc_info())
|