• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

TOMToolkit / tom_base / 6713509976

31 Oct 2023 11:13PM UTC coverage: 86.773% (+0.7%) from 86.072%
6713509976

push

github-actions

web-flow
Merge pull request #699 from TOMToolkit/dev

Multi-Feature Merge. Please Review Carefully.

795 of 795 new or added lines in 39 files covered. (100.0%)

8253 of 9511 relevant lines covered (86.77%)

0.87 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

90.38
/tom_alerts/alerts.py
1
from abc import ABC, abstractmethod
1✔
2
from dataclasses import dataclass
1✔
3
from datetime import datetime
1✔
4
from importlib import import_module
1✔
5

6
from django import forms
1✔
7
from django.conf import settings
1✔
8
from django.shortcuts import reverse
1✔
9
from crispy_forms.bootstrap import StrictButton
1✔
10
from crispy_forms.helper import FormHelper
1✔
11
from crispy_forms.layout import Layout, Submit
1✔
12

13
from tom_alerts.models import BrokerQuery
1✔
14
from tom_observations.models import ObservationRecord
1✔
15
from tom_targets.models import Target
1✔
16

17

18
DEFAULT_ALERT_CLASSES = [
1✔
19
    'tom_alerts.brokers.lasair.LasairBroker',
20
    'tom_alerts.brokers.scout.ScoutBroker',
21
    'tom_alerts.brokers.alerce.ALeRCEBroker',
22
    'tom_alerts.brokers.antares.ANTARESBroker',
23
    'tom_alerts.brokers.gaia.GaiaBroker',
24
    'tom_alerts.brokers.fink.FinkBroker',  # the stub for the plugin
25
    'tom_alerts.brokers.hermes.HermesBroker',
26
]
27

28

29
def get_service_classes():
1✔
30
    """
31
    Gets the broker classes available to this TOM as specified by ``TOM_ALERT_CLASSES`` in ``settings.py``. If none are
32
    specified, returns the default set.
33

34
    :returns: dict of broker classes, with keys being the name of the broker and values being the broker class
35
    :rtype: dict
36
    """
37
    try:
1✔
38
        TOM_ALERT_CLASSES = settings.TOM_ALERT_CLASSES
1✔
39
    except AttributeError:
1✔
40
        TOM_ALERT_CLASSES = DEFAULT_ALERT_CLASSES
1✔
41

42
    service_choices = {}
1✔
43
    for service in TOM_ALERT_CLASSES:
1✔
44
        mod_name, class_name = service.rsplit('.', 1)
1✔
45
        try:
1✔
46
            mod = import_module(mod_name)
1✔
47
            clazz = getattr(mod, class_name)
1✔
48
        except (ImportError, AttributeError):
1✔
49
            raise ImportError(f'Could not import {service}. Did you provide the correct path?')
1✔
50
        service_choices[clazz.name] = clazz
1✔
51
    return service_choices
1✔
52

53

54
def get_service_class(name):
1✔
55
    """
56
    Gets the specific broker class for a given broker name.
57

58
    :returns: Broker class
59
    :rtype: class
60
    """
61
    available_classes = get_service_classes()
1✔
62
    try:
1✔
63
        return available_classes[name]
1✔
64
    except KeyError:
1✔
65
        raise ImportError(
1✔
66
            '''Could not a find a broker with that name.
67
            Did you add it to TOM_ALERT_CLASSES?'''
68
        )
69

70

71
@dataclass
1✔
72
class GenericAlert:
1✔
73
    """
74
    dataclass representing an alert in order to display it in the UI.
75
    """
76

77
    timestamp: datetime
1✔
78
    id: int
1✔
79
    name: str
1✔
80
    ra: float
1✔
81
    dec: float
1✔
82
    mag: float
1✔
83
    score: float
1✔
84
    url: str
1✔
85

86
    def to_target(self):
1✔
87
        """
88
        Returns a Target instance for an object defined by an alert, as well as
89
        any TargetExtra or additional TargetNames.
90

91
        :returns: representation of object for an alert
92
        :rtype: `Target`
93

94
        :returns: dict of extras to be added to the new Target
95
        :rtype: `dict`
96

97
        :returns: list of aliases to be added to the new Target
98
        :rtype: `list`
99
        """
100
        return Target(
1✔
101
            name=self.name,
102
            type='SIDEREAL',
103
            ra=self.ra,
104
            dec=self.dec
105
        ), {}, []
106

107

108
class GenericQueryForm(forms.Form):
1✔
109
    """
110
    Form class representing the default form for a broker.
111
    """
112

113
    query_name = forms.CharField(required=True)
1✔
114
    broker = forms.CharField(
1✔
115
        required=True,
116
        max_length=50,
117
        widget=forms.HiddenInput()
118
    )
119

120
    def __init__(self, *args, **kwargs):
1✔
121
        super().__init__(*args, **kwargs)
1✔
122
        self.helper = FormHelper()
1✔
123
        self.helper.add_input(Submit('submit', 'Submit'))
1✔
124
        self.common_layout = Layout('query_name', 'broker')
1✔
125

126
    def save(self, query_id=None):
1✔
127
        """
128
        Saves the form data in the database as a ``BrokerQuery``.
129

130
        :returns: ``BrokerQuery`` model representation of the form that was saved to the db
131
        :rtype: ``BrokerQuery``
132
        """
133
        if query_id:
1✔
134
            query = BrokerQuery.objects.get(id=query_id)
1✔
135
        else:
136
            query = BrokerQuery()
1✔
137
        query.name = self.cleaned_data['query_name']
1✔
138
        query.broker = self.cleaned_data['broker']
1✔
139
        query.parameters = self.cleaned_data
1✔
140
        query.save()
1✔
141
        return query
1✔
142

143

144
class GenericUpstreamSubmissionForm(forms.Form):
1✔
145
    target = forms.ModelChoiceField(required=False, queryset=Target.objects.all(), widget=forms.HiddenInput())
1✔
146
    observation_record = forms.ModelChoiceField(required=False, queryset=ObservationRecord.objects.all(),
1✔
147
                                                widget=forms.HiddenInput())
148
    redirect_url = forms.CharField(required=False, max_length=100, widget=forms.HiddenInput())
1✔
149

150
    def __init__(self, *args, **kwargs):
1✔
151
        broker_name = kwargs.pop('broker')  # NOTE: parent constructor is not expecting broker and will fail
1✔
152
        super().__init__(*args, **kwargs)
1✔
153
        self.helper = FormHelper()
1✔
154
        self.helper.form_class = 'form-inline'
1✔
155
        self.helper.form_action = reverse('tom_alerts:submit-alert', kwargs={'broker': broker_name})
1✔
156
        self.helper.layout = Layout(
1✔
157
            'target',
158
            'observation_record',
159
            'redirect_url',
160
            StrictButton(f'Submit to {broker_name}', type='submit', css_class='btn-outline-primary'))
161

162
    def clean(self):
1✔
163
        cleaned_data = super().clean()
1✔
164

165
        if not (cleaned_data.get('target') or cleaned_data.get('observation_record')):
1✔
166
            raise forms.ValidationError('Must provide either Target or ObservationRecord to be submitted upstream.')
1✔
167

168
        return cleaned_data
1✔
169

170

171
class GenericBroker(ABC):
1✔
172
    """
173
    The ``GenericBroker`` provides an interface for implementing a broker module. It contains a number of methods to be
174
    implemented, but only the methods decorated with ``@abstractmethod`` are required to be implemented. In order to
175
    make use of a broker module, add the path to ``TOM_ALERT_CLASSES`` in your ``settings.py``.
176

177
    For an implementation example, please see
178
    https://github.com/TOMToolkit/tom_base/blob/main/tom_alerts/brokers/mars.py
179
    """
180
    alert_submission_form = GenericUpstreamSubmissionForm
1✔
181
    score_description = "The meaning of this field changes between brokers, please consult this broker's documentation."
1✔
182

183
    @abstractmethod
1✔
184
    def fetch_alerts(self, parameters: dict):
1✔
185
        """
186
        This method takes in the query parameters needed to filter
187
        alerts for a broker and makes the GET query to the broker
188
        endpoint.
189

190
        :param parameters: JSON string of query parameters
191
        :type parameters: dict
192
        """
193

194
    def fetch_alert(self, alert_id):
1✔
195
        """
196
        This method takes an alert id and retrieves the specific
197
        alert data from the given broker.
198

199
        :param alert_id: Broker-specific id corresponding with the desired alert
200
        :type alert_id: str
201
        """
202
        return None
×
203

204
    def process_reduced_data(self, target, alert=None):
1✔
205
        """
206
        Retrieves and creates records for any reduced data provided
207
        by a specific broker. Updates existing data if it has changed.
208

209
        :param target: ``Target`` object that was previously created from a ``BrokerQuery`` alert
210
        :type target: Target
211

212
        :param alert: alert data from a particular ``BrokerQuery``
213
        :type alert: str
214
        """
215

216
    def to_target(self, alert):
1✔
217
        """
218
        Creates ``Target`` object from the broker-specific alert data.
219

220
        :param alert: alert data from a particular ``BrokerQuery``
221
        :type alert: str
222
        """
223
        return None
×
224

225
    def submit_upstream_alert(self, target=None, observation_record=None, **kwargs):
1✔
226
        """
227
        Submits an alert upstream back to the broker. At least one of a target or an
228
        observation record must be provided.
229

230
        :param target: ``Target`` object to be converted to an alert and submitted upstream
231
        :type target: ``Target``
232

233
        :param observation_record: ``ObservationRecord`` object to be converted to an alert and submitted upstream
234
        :type observation_record: ``ObservationRecord``
235

236
        :returns: True or False depending on success of message submission
237
        :rtype: bool
238
        """
239

240
    @abstractmethod
1✔
241
    def to_generic_alert(self, alert):
1✔
242
        """
243
        This method creates a ``GenericAlert`` object from the broker-specific
244
        alert data for use outside the implementation of the ``GenericBroker``.
245

246
        :param alert: alert data from a particular ``BrokerQuery``
247
        :type alert: str
248
        """
249
        return None
×
250

251
    def fetch_and_save_all(self, parameters):
1✔
252
        """
253
        Gets all alerts using a particular ``BrokerQuery`` and creates a ``Target`` from each one.
254

255
        :param parameters: JSON string of query parameters
256
        :type parameters: str
257

258
        :returns: list of ``Target`` objects
259
        :rtype: list
260
        """
261
        targets = []
×
262
        for alert in self.fetch_alerts(parameters):
×
263
            generic_alert = self.to_generic_alert(alert)
×
264
            full_alert = self.fetch_alert(generic_alert.id)
×
265
            target = self.to_target(full_alert)
×
266
            targets.append(target)
×
267

268
        return targets
×
269

270
    def get_broker_context_data(self, *args, **kwargs):
1✔
271
        """Override this method in the subclass to allow the Broker to add additional context
272
        data to the View. This method will be called by views.RunQueryView.get_context_data()
273
        and the returned dictionary will be added to the View's context.
274
        """
275
        return {}
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc