"""Shared paths, constants and TAP helpers for the dark_companions study.""" import os import ssl import time import urllib.parse import urllib.request STUDY = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) DATA = os.path.join(STUDY, "data") FIGS = os.path.join(STUDY, "figures") RAW = os.environ.get("DARKCOMP_RAW", "fermi_raw/dark_companions") for _d in (DATA, FIGS, RAW): os.makedirs(_d, exist_ok=True) GAIA_TAP = "https://gea.esac.esa.int/tap-server/tap" VIZIER_TAP = "https://tapvizier.cds.unistra.fr/TAPVizieR/tap" # physical constants (SI) H_PLANCK = 6.62607015e-34 C_LIGHT = 2.99792458e8 K_B = 1.380649e-23 SIGMA_SB = 5.670374419e-8 L_SUN = 3.828e26 AU = 1.495978707e11 PC = 3.0856775814913673e16 G_NEWT = 6.67430e-11 M_SUN = 1.98847e30 KAPPA_MAS = 8.144 # mas / Msun, Einstein-radius constant def _ctx(): return ssl.create_default_context() def http_get(url, timeout=300): req = urllib.request.Request(url, headers={"User-Agent": "fermi-dark-companions/1.0"}) with urllib.request.urlopen(req, timeout=timeout, context=_ctx()) as r: return r.read() def tap_sync(base, adql, fmt="csv", timeout=600): data = urllib.parse.urlencode( {"REQUEST": "doQuery", "LANG": "ADQL", "FORMAT": fmt, "QUERY": adql} ).encode() req = urllib.request.Request(base + "/sync", data=data, headers={"User-Agent": "fermi-dark-companions/1.0"}) with urllib.request.urlopen(req, timeout=timeout, context=_ctx()) as r: return r.read() def tap_async(base, adql, fmt="csv", poll=5, timeout=3600, verbose=True): """Run an asynchronous TAP job and return the result bytes.""" data = urllib.parse.urlencode( {"REQUEST": "doQuery", "LANG": "ADQL", "FORMAT": fmt, "PHASE": "RUN", "QUERY": adql} ).encode() req = urllib.request.Request(base + "/async", data=data, headers={"User-Agent": "fermi-dark-companions/1.0"}) with urllib.request.urlopen(req, timeout=120, context=_ctx()) as r: job_url = r.geturl() t0 = time.time() while True: phase = http_get(job_url + "/phase", timeout=60).decode().strip() if verbose: print(" job", job_url.rsplit("/", 1)[-1], phase, f"{time.time()-t0:.0f}s", flush=True) if phase == "COMPLETED": break if phase in ("ERROR", "ABORTED"): err = http_get(job_url + "/error", timeout=60).decode(errors="replace") raise RuntimeError("TAP job failed: " + err[:2000]) if time.time() - t0 > timeout: raise RuntimeError("TAP job timed out") time.sleep(poll) return http_get(job_url + "/results/result", timeout=1800) def fetch_to(path, getter, force=False): """Download once; reuse the cached raw file afterwards.""" if os.path.exists(path) and os.path.getsize(path) > 0 and not force: return path blob = getter() with open(path + ".part", "wb") as f: f.write(blob) os.replace(path + ".part", path) return path