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.
@@ -1,37 +1,41 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: timewise
3
- Version: 0.4.12
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,<3.12
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
- Requires-Dist: astropy (>=5.1,<6.0)
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: pyvo (>=1.4.1,<2.0.0)
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.21.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.13.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: Repository, https://github.com/JannisNe/timewise
38
+ Project-URL: Homepage, https://github.com/JannisNe/timewise
35
39
  Description-Content-Type: text/markdown
36
40
 
37
41
  [![CI](https://github.com/JannisNe/timewise/actions/workflows/continous_integration.yml/badge.svg)](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
+ ]
@@ -2,4 +2,4 @@ from timewise.wise_data_by_visit import WiseDataByVisit
2
2
  from timewise.wise_bigdata_desy_cluster import WISEDataDESYCluster
3
3
  from timewise.parent_sample_base import ParentSampleBase
4
4
 
5
- __version__ = "0.4.12"
5
+ __version__ = "0.5.3"
@@ -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 = os.path.join(self.cache_dir, "cache.pkl")
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 os.path.isfile(self._cache_file):
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 os.path.isfile(self._cache_file):
99
+ if hasattr(self, "_cache_file") and self._cache_file.is_file():
101
100
  logger.debug(f'removing {self._cache_file}')
102
- os.remove(self._cache_file)
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 os.path.isfile(v):
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 cache_dir, plots_dir
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
- self.cache_dir = os.path.join(cache_dir, base_name)
30
- self.plots_dir = os.path.join(plots_dir, base_name)
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
- if not os.path.isdir(d):
34
- os.makedirs(d)
34
+ d.parent.mkdir(parents=True, exist_ok=True)
35
35
 
36
- self.local_sample_copy = os.path.join(self.cache_dir, 'sample.csv')
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
- [os.path.join(self.plots_dir, f"{i}_{r[self.default_keymap['id']]}.pdf")
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
- local_copy = os.path.join(cache_dir, 'mirong_sample.csv')
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
- if not os.path.isfile(local_copy):
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
- mirong_sample.to_csv(local_copy, index=False)
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 {local_copy}')
57
- mirong_sample = pd.read_csv(local_copy)
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
- cache_file = os.path.join(cache_dir, h + ".cache")
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 os.path.isfile(cache_file):
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())