From d2b7d30ad06a6b699c986c7e8a75b6a9cfcbb7e6 Mon Sep 17 00:00:00 2001 From: nnesquivelr Date: Thu, 5 Oct 2023 12:44:08 -0300 Subject: [PATCH 1/4] Update filter tags --- setup.py | 2 +- tom_antares/antares.py | 247 +++++++++++++++++++++-------------------- 2 files changed, 126 insertions(+), 123 deletions(-) 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..6881f21 100644 --- a/tom_antares/antares.py +++ b/tom_antares/antares.py @@ -1,34 +1,23 @@ import logging -import requests import antares_client -from antares_client.search import get_by_ztf_object_id +import marshmallow +import requests +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 +59,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 +138,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 + 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 +164,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 +179,7 @@ def __init__(self, *args, **kwargs): css_class='col', ), css_class='form-row', - ) + ), ), Fieldset( 'Brightness of the latest alert', @@ -202,47 +192,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 +228,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 +290,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 +304,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 +330,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 +348,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: @@ -420,14 +417,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 +440,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', ''), ) From 0ca9212f02c09ddd74ec100f483183b77f0ba166 Mon Sep 17 00:00:00 2001 From: nnesquivelr Date: Thu, 5 Oct 2023 13:48:39 -0300 Subject: [PATCH 2/4] Fix invalid syntax (F999) --- tom_antares/antares.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tom_antares/antares.py b/tom_antares/antares.py index 6881f21..a7503af 100644 --- a/tom_antares/antares.py +++ b/tom_antares/antares.py @@ -119,7 +119,7 @@ class ANTARESBrokerForm(GenericQueryForm): esquery = forms.JSONField( required=False, label='Elastic Search query in JSON format', - widget=forms.Textarea(attrs={'placeholder': '{'query':{}}'}), + widget=forms.Textarea(attrs={'placeholder': '{"query":{}}'}), ) max_alerts = forms.FloatField( label='Maximum number of alerts to fetch', From 06698a22916c124711fbecb5aa8014e6a91fe3ec Mon Sep 17 00:00:00 2001 From: nnesquivelr Date: Thu, 5 Oct 2023 14:00:46 -0300 Subject: [PATCH 3/4] Fix f-string: unmatched '[' (F999) and Redefining built-in 'id' --- tom_antares/antares.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tom_antares/antares.py b/tom_antares/antares.py index a7503af..7a71a26 100644 --- a/tom_antares/antares.py +++ b/tom_antares/antares.py @@ -400,8 +400,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 @@ -426,7 +426,7 @@ def to_target(self, alert: dict) -> Target: 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', From 362c3a56ac04ec2fc0ce5dbd49d552a0f4ea7c5a Mon Sep 17 00:00:00 2001 From: nnesquivelr Date: Thu, 5 Oct 2023 14:11:19 -0300 Subject: [PATCH 4/4] Remove unused import --- tom_antares/antares.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tom_antares/antares.py b/tom_antares/antares.py index 7a71a26..01712cc 100644 --- a/tom_antares/antares.py +++ b/tom_antares/antares.py @@ -2,7 +2,6 @@ import antares_client import marshmallow -import requests from antares_client.search import get_available_tags, get_by_ztf_object_id from astropy.time import Time, TimezoneInfo from crispy_forms.layout import HTML, Div, Fieldset, Layout @@ -141,7 +140,7 @@ def __init__(self, *args, **kwargs): HTML( '''

- Users can query objects in the ANTARES database using one of the following + 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.