|
| 1 | +Adding a Data Service Module for the TOM Toolkit |
| 2 | +------------------------------------------------ |
| 3 | + |
| 4 | +This guide is to walk you step by step through the process of creating a Data Service. |
| 5 | +This assumes that you want a user interface for querying your data service via a form. |
| 6 | +Many of these steps can be skipped if your service is only intended to be accessed internally. |
| 7 | + |
| 8 | +Setting up the Basic Data Service: |
| 9 | +********************************** |
| 10 | + |
| 11 | +First we will build the bare bones of our data service. This is the bare minimum to get the service to show up in the |
| 12 | +TOM. We'll start with three peices of code: |
| 13 | + |
| 14 | +First the actual query class: |
| 15 | ++++++++++++++++++++++++++++++ |
| 16 | + |
| 17 | + |
| 18 | +.. code-block:: python |
| 19 | + :caption: my_dataservice.py |
| 20 | + :linenos: |
| 21 | +
|
| 22 | + from tom_dataservices.dataservices import BaseDataService |
| 23 | + from my_dataservice.forms import MyServiceForm |
| 24 | +
|
| 25 | + class MyDataService(BaseDataService): |
| 26 | + """ |
| 27 | + This is an Example Data Service with the minimum required |
| 28 | + functionality. |
| 29 | + """ |
| 30 | + name = 'MyService' |
| 31 | +
|
| 32 | + @classmethod |
| 33 | + def get_form_class(cls): |
| 34 | + """ |
| 35 | + Points to the form class discussed below. |
| 36 | + """ |
| 37 | + return MyServiceForm |
| 38 | + |
| 39 | + def build_query_parameters(self, parameters, **kwargs): |
| 40 | + """ |
| 41 | + Use this function to convert the form results into the query parameters understood |
| 42 | + by the Data Service. |
| 43 | + """ |
| 44 | + return self.query_parameters |
| 45 | + |
| 46 | + def query_service(self, data, **kwargs): |
| 47 | + """ |
| 48 | + This is where you actually make the call to the Data Service. |
| 49 | + Return the results. |
| 50 | + """ |
| 51 | + return self.query_results |
| 52 | +
|
| 53 | +
|
| 54 | +Your Data Service needs a form: |
| 55 | ++++++++++++++++++++++++++++++++ |
| 56 | + |
| 57 | +.. code-block:: python |
| 58 | + :caption: forms.py |
| 59 | + :linenos: |
| 60 | +
|
| 61 | + from django import forms |
| 62 | + from tom_dataservices.forms import BaseQueryForm |
| 63 | +
|
| 64 | + class MyServiceForm(BaseQueryForm): |
| 65 | + first_field = forms.CharField(required=False, |
| 66 | + label='An Example Field', |
| 67 | + help_text='Put important info here.') |
| 68 | +
|
| 69 | +
|
| 70 | +Adding the integration point: |
| 71 | ++++++++++++++++++++++++++++++ |
| 72 | + |
| 73 | +.. code-block:: python |
| 74 | + :caption: apps.py |
| 75 | + :linenos: |
| 76 | +
|
| 77 | + from django.apps import AppConfig |
| 78 | +
|
| 79 | +
|
| 80 | + class MyAppConfig(AppConfig): |
| 81 | + default_auto_field = 'django.db.models.BigAutoField' |
| 82 | + name = 'my_app' |
| 83 | +
|
| 84 | + def data_services(self): |
| 85 | + """ |
| 86 | + integration point for including data services in the TOM |
| 87 | + This method should return a list of dictionaries containing dot separated DataService classes |
| 88 | + """ |
| 89 | + return [{'class': f'{self.name}.my_dataservice.MyDataService'}] |
| 90 | +
|
| 91 | +
|
| 92 | +Customizing your Data Service: |
| 93 | +****************************** |
| 94 | + |
| 95 | +The next step is to update our code to have all specific features relevent for our data service. Here we will focus on |
| 96 | +extending several methods of `BaseDataService` to be relevent for your data service. |
| 97 | + |
| 98 | + |
| 99 | +`BaseDataService.build_query_parameters` |
| 100 | +++++++++++++++++++++++++++++++++++++++++ |
| 101 | + |
| 102 | +For starters, let's make our `build_query_parameters` function inside of `MyDataService` actually do something. |
| 103 | +This code is to convert all of the form fields into a data dictionary or set of query parameters that is understood by |
| 104 | +the data service (or more specifically our `query_service` method.) |
| 105 | + |
| 106 | +.. code-block:: python |
| 107 | + :caption: my_dataservice.MyDataService |
| 108 | + :linenos: |
| 109 | +
|
| 110 | + def build_query_parameters(self, parameters, **kwargs): |
| 111 | + """ |
| 112 | + Use this function to convert the form results into the query parameters understood |
| 113 | + by the Data Service. |
| 114 | + """ |
| 115 | + data = { |
| 116 | + 'example_field': parameters.get('first_field') |
| 117 | + } |
| 118 | +
|
| 119 | + self.query_parameters = data |
| 120 | + return data |
| 121 | +
|
| 122 | +In some cases, this can be very straightforward, while in others this can involve complex constructions of query |
| 123 | +commands. Ultimately this is based on the API or client of your Data Service, and how you chose to name your form |
| 124 | +fields. |
| 125 | + |
| 126 | +`BaseDataService.query_service` |
| 127 | ++++++++++++++++++++++++++++++++ |
| 128 | + |
| 129 | +Next we will need to fill out our `query_service` module. This is the function that actualy goes and calls the query |
| 130 | +service using the parameters created by `build_query_parameters`. This function produces query results that can then be |
| 131 | +interpreted by `query_targets`, `query_photometry`, or other functions to produce specific kinds of results that can be |
| 132 | +interpreted by your TOM. |
| 133 | + |
| 134 | +.. code-block:: python |
| 135 | + :caption: my_dataservice.MyDataService |
| 136 | + :linenos: |
| 137 | +
|
| 138 | + def query_service(self, data, **kwargs): |
| 139 | + """ |
| 140 | + This is where you actually make the call to the Data Service. |
| 141 | + Return the results. |
| 142 | + """ |
| 143 | + if self.get_urls(url_type='search'): |
| 144 | + results = requests.post(self.get_urls(url_type='search'), data, headers=self.build_headers()) |
| 145 | + else: |
| 146 | + results = data_service_client.search(data) |
| 147 | + self.query_results = results |
| 148 | + return self.query_results |
| 149 | +
|
| 150 | +Again, depending on the nature of your data service, the `query_service` function could take many different forms. |
| 151 | +This may also require you to create a `build_headers` method, or make use of the `urls`, `get_configuration`, or |
| 152 | +`get_credentials`methods. Saving the results to `self.query_results` could save time in other methods by not requireing |
| 153 | +you to redo the query. |
| 154 | + |
| 155 | +`BaseDataService.query_target` |
| 156 | +++++++++++++++++++++++++++++++ |
| 157 | + |
| 158 | +We will just use `query_target` as an example. The same ideas apply to any of the individual query functions. |
| 159 | +This is the function that pulls useful data from the query results in a way that the TOM understands. In this case, we |
| 160 | +will be extracting Target data from the query results and creating a dictionary. |
| 161 | + |
| 162 | +.. code-block:: python |
| 163 | + :caption: my_dataservice.MyDataService |
| 164 | + :linenos: |
| 165 | +
|
| 166 | + def query_target(self, data, **kwargs): |
| 167 | + """ |
| 168 | + This code calls `query_dataservice` and returns a dictionary of results. |
| 169 | + This call and the results should be tailroed towards describing targets. |
| 170 | + """ |
| 171 | + query_results = super().query_targets(data) |
| 172 | + targets = [] |
| 173 | + for result in query_results: |
| 174 | + result['name'] = f"MyService:{result['ra']},{result['dec']}" |
| 175 | + targets.append(result) |
| 176 | + return targets |
| 177 | +
|
| 178 | +In this example, we create or modify the name of a query result so we will have something to enter into the TOM. |
| 179 | +Line 6 calls the super which will either retrieve `self.query_results` if it exists or run `query_service`. |
| 180 | +The final output should be a dictionary of results. |
| 181 | + |
| 182 | +`BaseDataService.create_target_from_query` |
| 183 | +++++++++++++++++++++++++++++++++++++++++++ |
| 184 | + |
| 185 | +Continuing with our `target` example, we need to be able to `create_target_from_query` in order to actually save the |
| 186 | +target object resulting from a succesful result for `query_target` above. This function expects a single instance with |
| 187 | +the same format as the list of dictionaries created by `query_targets` and converts that dictionary into a Target Object |
| 188 | +returning that object. |
| 189 | + |
| 190 | +.. code-block:: python |
| 191 | + :caption: my_dataservice.MyDataService |
| 192 | + :linenos: |
| 193 | +
|
| 194 | + def create_target_from_query(self, target_result, **kwargs): |
| 195 | + """Create a new target from the query results |
| 196 | + :returns: target object |
| 197 | + :rtype: `Target` |
| 198 | + """ |
| 199 | +
|
| 200 | + target = Target( |
| 201 | + name=target_result['name'], |
| 202 | + type='SIDEREAL', |
| 203 | + ra=target_result['ra'], |
| 204 | + dec=target_result['dec'] |
| 205 | + ) |
| 206 | + return target |
0 commit comments