diff --git a/setup.py b/setup.py
index 37d7af9..cb42298 100644
--- a/setup.py
+++ b/setup.py
@@ -29,7 +29,7 @@
setup_requires=['setuptools_scm', 'wheel'],
install_requires=[
'tomtoolkit>=2.12,<3.0',
- 'antares-client>=1.2,<2.0',
+ 'antares-client>=1.4,<2.0',
'elasticsearch-dsl>=7.3,<7.5'
],
extras_require={
diff --git a/tom_antares/antares.py b/tom_antares/antares.py
index f219205..01712cc 100644
--- a/tom_antares/antares.py
+++ b/tom_antares/antares.py
@@ -1,34 +1,22 @@
import logging
-import requests
import antares_client
-from antares_client.search import get_by_ztf_object_id
+import marshmallow
+from antares_client.search import get_available_tags, get_by_ztf_object_id
from astropy.time import Time, TimezoneInfo
-from crispy_forms.layout import Div, Fieldset, Layout, HTML
+from crispy_forms.layout import HTML, Div, Fieldset, Layout
from django import forms
-import marshmallow
-
-from tom_alerts.alerts import GenericBroker, GenericQueryForm, GenericAlert
+from tom_alerts.alerts import GenericAlert, GenericBroker, GenericQueryForm
from tom_targets.models import Target, TargetName
logger = logging.getLogger(__name__)
ANTARES_BASE_URL = 'https://antares.noirlab.edu'
-ANTARES_API_URL = 'https://api.antares.noirlab.edu'
-ANTARES_TAG_URL = ANTARES_API_URL + '/v1/tags'
-
-
-def get_available_tags(url: str = ANTARES_TAG_URL):
- response = requests.get(url).json()
- tags = response.get('data', {})
- if response.get('links', {}).get('next'):
- return tags + get_available_tags(response['links']['next'])
- return tags
def get_tag_choices():
tags = get_available_tags()
- return [(s['id'], s['id']) for s in tags]
+ return [(s, s) for s in tags]
# class ConeSearchWidget(forms.widgets.MultiWidget):
@@ -70,71 +58,73 @@ class ANTARESBrokerForm(GenericQueryForm):
ztfid = forms.CharField(
required=False,
label='',
- widget=forms.TextInput(attrs={'placeholder': 'ZTF object id, e.g. ZTF19aapreis'})
- )
+ widget=forms.TextInput(
+ attrs={'placeholder': 'ZTF object id, e.g. ZTF19aapreis'}
+ ),
+ )
tag = forms.MultipleChoiceField(required=False, choices=get_tag_choices)
nobs__gt = forms.IntegerField(
required=False,
label='Detections Lower',
- widget=forms.TextInput(attrs={'placeholder': 'Min number of measurements'})
+ widget=forms.TextInput(attrs={'placeholder': 'Min number of measurements'}),
)
nobs__lt = forms.IntegerField(
required=False,
label='Detections Upper',
- widget=forms.TextInput(attrs={'placeholder': 'Max number of measurements'})
+ widget=forms.TextInput(attrs={'placeholder': 'Max number of measurements'}),
)
ra = forms.FloatField(
required=False,
label='RA',
widget=forms.TextInput(attrs={'placeholder': 'RA (Degrees)'}),
- min_value=0.0
+ min_value=0.0,
)
dec = forms.FloatField(
required=False,
label='Dec',
widget=forms.TextInput(attrs={'placeholder': 'Dec (Degrees)'}),
- min_value=0.0
+ min_value=0.0,
)
sr = forms.FloatField(
required=False,
label='Search Radius',
widget=forms.TextInput(attrs={'placeholder': 'radius (Degrees)'}),
- min_value=0.0
+ min_value=0.0,
)
mjd__gt = forms.FloatField(
required=False,
label='Min date of alert detection ',
widget=forms.TextInput(attrs={'placeholder': 'Date (MJD)'}),
- min_value=0.0
+ min_value=0.0,
)
mjd__lt = forms.FloatField(
required=False,
label='Max date of alert detection',
widget=forms.TextInput(attrs={'placeholder': 'Date (MJD)'}),
- min_value=0.0
+ min_value=0.0,
)
mag__min = forms.FloatField(
required=False,
label='Min magnitude of the latest alert',
widget=forms.TextInput(attrs={'placeholder': 'Min Magnitude'}),
- min_value=0.0
+ min_value=0.0,
)
mag__max = forms.FloatField(
required=False,
label='Max magnitude of the latest alert',
widget=forms.TextInput(attrs={'placeholder': 'Max Magnitude'}),
- min_value=0.0
+ min_value=0.0,
)
esquery = forms.JSONField(
required=False,
label='Elastic Search query in JSON format',
- widget=forms.TextInput(attrs={'placeholder': '{"query":{}}'}),
+ widget=forms.Textarea(attrs={'placeholder': '{"query":{}}'}),
)
max_alerts = forms.FloatField(
label='Maximum number of alerts to fetch',
widget=forms.TextInput(attrs={'placeholder': 'Max Alerts'}),
min_value=1,
- initial=20
+ initial=20,
)
# cone_search = ConeSearchField()
@@ -147,20 +137,19 @@ def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.helper.layout = Layout(
self.common_layout,
- HTML('''
+ HTML(
+ '''
Users can query objects in the ANTARES database using one of the following
three methods: 1. an object ID by ZTF, 2. a simple query form with constraints
of object brightness, position, and associated tag, 3. an advanced query with
Elastic Search syntax.
- '''),
+ '''
+ ),
HTML('
'),
HTML('Query by object name
'),
- Fieldset(
- 'ZTF object ID',
- 'ztfid'
- ),
+ Fieldset('ZTF object ID', 'ztfid'),
HTML('
'),
HTML('Simple query form
'),
Fieldset(
@@ -174,9 +163,9 @@ def __init__(self, *args, **kwargs):
'mjd__lt',
css_class='col',
),
- css_class='form-row'
- )
+ css_class='form-row',
),
+ ),
Fieldset(
'Number of measurements',
Div(
@@ -189,7 +178,7 @@ def __init__(self, *args, **kwargs):
css_class='col',
),
css_class='form-row',
- )
+ ),
),
Fieldset(
'Brightness of the latest alert',
@@ -202,47 +191,31 @@ def __init__(self, *args, **kwargs):
'mag__max',
css_class='col',
),
- css_class='form-row'
- )
+ css_class='form-row',
+ ),
),
Fieldset(
'Cone Search',
Div(
- Div(
- 'ra',
- css_class='col'
- ),
- Div(
- 'dec',
- css_class='col'
- ),
- Div(
- 'sr',
- css_class='col'
- ),
- css_class='form-row'
- )
- ),
- Fieldset(
- 'View Tags',
- 'tag'
- ),
- Fieldset(
- 'Max Alerts',
- 'max_alerts'
+ Div('ra', css_class='col'),
+ Div('dec', css_class='col'),
+ Div('sr', css_class='col'),
+ css_class='form-row',
+ ),
),
+ Fieldset('View Tags', 'tag'),
+ Fieldset('Max Alerts', 'max_alerts'),
HTML('
'),
HTML('Advanced query
'),
- Fieldset(
- '',
- 'esquery'
- ),
- HTML('''
+ Fieldset('', 'esquery'),
+ HTML(
+ '''
- Please see ANTARES
+ Please see ANTARES
Documentation for a detailed description of advanced searches.
- ''')
+ '''
+ )
# HTML('
'),
# Fieldset(
# 'API Search',
@@ -254,33 +227,55 @@ def clean(self):
cleaned_data = super().clean()
# Ensure all cone search fields are present
- if any(cleaned_data[k] for k in ['ra', 'dec', 'sr']) and not all(cleaned_data[k] for k in ['ra', 'dec', 'sr']):
- raise forms.ValidationError('All of RA, Dec, and Search Radius must be included to perform a cone search.')
+ if (
+ any(cleaned_data[k] for k in ['ra', 'dec', 'sr'])
+ and not all(cleaned_data[k] for k in ['ra', 'dec', 'sr'])
+ ):
+ raise forms.ValidationError(
+ 'All of RA, Dec, and Search Radius must be included to perform a cone search.'
+ )
# default value for cone search
if not all(cleaned_data[k] for k in ['ra', 'dec', 'sr']):
- cleaned_data['ra'] = 180.
- cleaned_data['dec'] = 0.
- cleaned_data['sr'] = 180.
+ cleaned_data['ra'] = 180.0
+ cleaned_data['dec'] = 0.0
+ cleaned_data['sr'] = 180.0
# Ensure alert timing constraints have sensible values
- if all(cleaned_data[k] for k in ['mjd__lt', 'mjd__gt']) and cleaned_data['mjd__lt'] <= cleaned_data['mjd__gt']:
- raise forms.ValidationError('Min date of alert detection must be earlier than max date of alert detection.')
+ if (
+ all(cleaned_data[k] for k in ['mjd__lt', 'mjd__gt'])
+ and cleaned_data['mjd__lt'] <= cleaned_data['mjd__gt']
+ ):
+ raise forms.ValidationError(
+ 'Min date of alert detection must be earlier than max date of alert detection.'
+ )
# Ensure number of measurement constraints have sensible values
- if (all(cleaned_data[k] for k in ['nobs__lt', 'nobs__gt'])
- and cleaned_data['nobs__lt'] <= cleaned_data['nobs__gt']):
- raise forms.ValidationError('Min number of measurements must be smaller than max number of measurements.')
+ if (
+ all(cleaned_data[k] for k in ['nobs__lt', 'nobs__gt'])
+ and cleaned_data['nobs__lt'] <= cleaned_data['nobs__gt']
+ ):
+ raise forms.ValidationError(
+ 'Min number of measurements must be smaller than max number of measurements.'
+ )
# Ensure magnitude constraints have sensible values
- if (all(cleaned_data[k] for k in ['mag__min', 'mag__max'])
- and cleaned_data['mag__max'] <= cleaned_data['mag__min']):
- raise forms.ValidationError('Min magnitude must be smaller than max magnitude.')
+ if (
+ all(cleaned_data[k] for k in ['mag__min', 'mag__max'])
+ and cleaned_data['mag__max'] <= cleaned_data['mag__min']
+ ):
+ raise forms.ValidationError(
+ 'Min magnitude must be smaller than max magnitude.'
+ )
# Ensure using either a stream or the advanced search form
# if not (cleaned_data['tag'] or cleaned_data['esquery']):
# raise forms.ValidationError('Please either select tag(s) or use the advanced search query.')
# Ensure using either a stream or the advanced search form
- if not (cleaned_data['ztfid'] or cleaned_data['tag'] or cleaned_data['esquery']):
+ if not (
+ cleaned_data.get('ztfid')
+ or cleaned_data.get('tag')
+ or cleaned_data.get('esquery')
+ ):
raise forms.ValidationError(
'Please either enter the ZTF ID, or select tag(s), or use the advanced search query.'
)
@@ -294,12 +289,12 @@ class ANTARESBroker(GenericBroker):
@classmethod
def alert_to_dict(cls, locus):
- """
+ '''
Note: The ANTARES API returns a Locus object, which in the TOM Toolkit
would otherwise be called an alert.
This method serializes the Locus into a dict so that it can be cached by the view.
- """
+ '''
return {
'locus_id': locus.locus_id,
'ra': locus.ra,
@@ -308,11 +303,14 @@ def alert_to_dict(cls, locus):
'tags': locus.tags,
# 'lightcurve': locus.lightcurve.to_json(),
'catalogs': locus.catalogs,
- 'alerts': [{
- 'alert_id': alert.alert_id,
- 'mjd': alert.mjd,
- 'properties': alert.properties
- } for alert in locus.alerts]
+ 'alerts': [
+ {
+ 'alert_id': alert.alert_id,
+ 'mjd': alert.mjd,
+ 'properties': alert.properties,
+ }
+ for alert in locus.alerts
+ ],
}
def fetch_alerts(self, parameters: dict) -> iter:
@@ -331,16 +329,8 @@ def fetch_alerts(self, parameters: dict) -> iter:
max_alerts = parameters.get('max_alerts', 20)
if ztfid:
query = {
- "query": {
- "bool": {
- "must": [
- {
- "match": {
- "properties.ztf_object_id": ztfid
- }
- }
- ]
- }
+ 'query': {
+ 'bool': {'must': [{'match': {'properties.ztf_object_id': ztfid}}]}
}
}
elif elsquery:
@@ -357,43 +347,49 @@ def fetch_alerts(self, parameters: dict) -> iter:
filters.append(nobs_range)
if mjd_lt:
- mjd_lt_range = {'range': {'properties.newest_alert_observation_time': {'lte': mjd_lt}}}
+ mjd_lt_range = {
+ 'range': {
+ 'properties.newest_alert_observation_time': {'lte': mjd_lt}
+ }
+ }
filters.append(mjd_lt_range)
if mjd_gt:
- mjd_gt_range = {'range': {'properties.oldest_alert_observation_time': {'gte': mjd_gt}}}
+ mjd_gt_range = {
+ 'range': {
+ 'properties.oldest_alert_observation_time': {'gte': mjd_gt}
+ }
+ }
filters.append(mjd_gt_range)
if mag_min or mag_max:
mag_range = {'range': {'properties.newest_alert_magnitude': {}}}
if mag_min:
- mag_range['range']['properties.newest_alert_magnitude']['gte'] = mag_min
+ mag_range['range']['properties.newest_alert_magnitude'][
+ 'gte'
+ ] = mag_min
if mag_max:
- mag_range['range']['properties.newest_alert_magnitude']['lte'] = mag_max
+ mag_range['range']['properties.newest_alert_magnitude'][
+ 'lte'
+ ] = mag_max
filters.append(mag_range)
if sra and ssr: # TODO: add cross-field validation
- ra_range = {'range': {'ra': {'gte': sra-ssr, 'lte': sra+ssr}}}
+ ra_range = {'range': {'ra': {'gte': sra - ssr, 'lte': sra + ssr}}}
filters.append(ra_range)
if sdec and ssr: # TODO: add cross-field validation
- dec_range = {'range': {'dec': {'gte': sdec-ssr, 'lte': sdec+ssr}}}
+ dec_range = {'range': {'dec': {'gte': sdec - ssr, 'lte': sdec + ssr}}}
filters.append(dec_range)
if tags:
filters.append({'terms': {'tags': tags}})
- query = {
- "query": {
- "bool": {
- "filter": filters
- }
- }
- }
+ query = {'query': {'bool': {'filter': filters}}}
loci = antares_client.search.search(query)
-# if ztfid:
-# loci = get_by_ztf_object_id(ztfid)
+ # if ztfid:
+ # loci = get_by_ztf_object_id(ztfid)
alerts = []
while len(alerts) < max_alerts:
try:
@@ -403,8 +399,8 @@ def fetch_alerts(self, parameters: dict) -> iter:
alerts.append(self.alert_to_dict(locus))
return iter(alerts)
- def fetch_alert(self, id):
- alert = get_by_ztf_object_id(id)
+ def fetch_alert(self, id_):
+ alert = get_by_ztf_object_id(id_)
return alert
# TODO: This function
@@ -420,14 +416,20 @@ def to_target(self, alert: dict) -> Target:
)
antares_name = TargetName(target=target, name=alert['locus_id'])
aliases = [antares_name]
- if alert['properties'].get('horizons_targetname'): # TODO: review if any other target names need to be created
- aliases.append(TargetName(name=alert['properties'].get('horizons_targetname')))
+ if alert['properties'].get(
+ 'horizons_targetname'
+ ): # TODO: review if any other target names need to be created
+ aliases.append(
+ TargetName(name=alert['properties'].get('horizons_targetname'))
+ )
return target, [], aliases
def to_generic_alert(self, alert):
- url = f"{ANTARES_BASE_URL}/loci/{alert['locus_id']}"
+ url = f'{ANTARES_BASE_URL}/loci/{alert["locus_id"]}'
timestamp = Time(
- alert['properties'].get('newest_alert_observation_time'), format='mjd', scale='utc'
+ alert['properties'].get('newest_alert_observation_time'),
+ format='mjd',
+ scale='utc',
).to_datetime(timezone=TimezoneInfo())
return GenericAlert(
timestamp=timestamp,
@@ -437,5 +439,5 @@ def to_generic_alert(self, alert):
ra=alert['ra'],
dec=alert['dec'],
mag=alert['properties'].get('newest_alert_magnitude', ''),
- score=alert['alerts'][-1]['properties'].get('ztf_rb', '')
+ score=alert['alerts'][-1]['properties'].get('ztf_rb', ''),
)