Skip to content

Commit 2be9e6c

Browse files
authored
Merge pull request #94 from dfarrow0/master
add tutorial for adding a new endpoint
2 parents 294e1de + af40a7f commit 2be9e6c

File tree

2 files changed

+344
-0
lines changed

2 files changed

+344
-0
lines changed

docs/epidata_development.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ This guide describes how to write and test code for the Epidata API. For
77
preliminary steps,
88
[install docker and create a virtual network](https://github.com/cmu-delphi/operations/blob/master/docs/frontend_development.md#setup).
99

10+
After reading this guide, you may want to visit
11+
[the `fluview_meta` tutorial](new_endpoint_tutorial.md) for an example of how
12+
to add a new endpoint to the API.
13+
1014
# setup
1115

1216
For working on the Epidata API, you'll need the following two Delphi

docs/new_endpoint_tutorial.md

Lines changed: 340 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,340 @@
1+
# Tutorial: Adding a new API endpoint
2+
3+
**Prerequisite:** this guide assumes that you have read the
4+
[epidata development guide](epidata_development.md).
5+
6+
In this tutorial we'll create a brand new endpoint for the Epidata API:
7+
`fluview_meta`. At a high level, we'll do the following steps:
8+
9+
1. understand the data that we want to surface
10+
2. add the new endpoint to `api.php`
11+
3. add the new endpoint to the various client libraries
12+
4. write an integration test for the new endpoint
13+
5. update API documentation for the new endpoint
14+
6. run all unit and integration tests
15+
16+
# setup
17+
18+
Follow
19+
[the backend guide](https://github.com/cmu-delphi/operations/blob/master/docs/backend_development.md)
20+
and [the epidata guide](epidata_development.md) to install Docker and get your
21+
workspace ready for development. Before continuing, your workspace should look
22+
something like the following:
23+
24+
```bash
25+
tree -L 3 .
26+
```
27+
28+
```
29+
.
30+
└── repos
31+
   ├── delphi
32+
   │   ├── delphi-epidata
33+
   │   ├── flu-contest
34+
   │   ├── github-deploy-repo
35+
   │   ├── nowcast
36+
   │   ├── operations
37+
   │   └── utils
38+
   └── undefx
39+
   ├── py3tester
40+
   └── undef-analysis
41+
```
42+
43+
# the data
44+
45+
Here's the requirement: we need to quickly surface the most recent "issue"
46+
(epiweek of publication) for the existing [`fluview` endpoint](api/fluview.md).
47+
This is already provided by the existing [`meta` endpoint](api/meta.md),
48+
however, it's _very_ slow, and it returns a bunch of unrelated data. The goal
49+
is extract the subset of metadata pertaining to `fluview` and return just that
50+
data through a new endpoint.
51+
52+
Each row in the `fluview` table contains
53+
[a lot of data](../src/ddl/fluview.sql), but we're particularly interested in
54+
the following:
55+
56+
- latest publication date
57+
- latest "issue", which is the publication epiweek
58+
- total size of the table
59+
60+
# update the server
61+
62+
Open [`api.php`](../src/server/api.php) and navigate to the bottom where we see
63+
line like `if($source === 'NAME') { ... }`. Right below the `if` block for
64+
`if($source === 'fluview')`, add a new `if else` block for our new endpoint:
65+
66+
```php
67+
else if($source === 'fluview_meta') {
68+
// get the data
69+
$epidata = meta_fluview();
70+
store_result($data, $epidata);
71+
}
72+
```
73+
74+
Fortunately, the function `meta_fluview()` is already defined, so we can just
75+
reuse it. (It's used by the `meta` endpoint as mentioned above.) In general,
76+
you will likely need to define a new function named like
77+
`get_SOURCE(params...)`, especially if you're reading from a new database
78+
table.
79+
80+
# update the client libraries
81+
82+
There are currently four client libraries. They all need to be updated to make
83+
the new `fluview_meta` endpoint available to callers. The pattern is very
84+
similar for all endpoints, so copy-paste will get you 90% of the way there.
85+
86+
`fluview_meta` is especially simple as it takes no parameters, and consequently
87+
there is no need to validate parameters. In general, it's a good idea to do
88+
sanity checks on caller inputs prior to sending the request to the API. See
89+
some of the other endpoint implementations (e.g. `fluview`) for an example of
90+
what this looks like.
91+
92+
Here's what we add to each client:
93+
94+
- [`delphi_epidata.coffee`](../src/client/delphi_epidata.coffee)
95+
96+
```coffeescript
97+
# Fetch FluView metadata
98+
@fluview_meta: (callback) ->
99+
# Set up request
100+
params =
101+
'source': 'fluview_meta'
102+
# Make the API call
103+
_request(callback, params)
104+
```
105+
106+
- [`delphi_epidata.js`](../src/client/delphi_epidata.js)
107+
108+
Note that this file _can and should be generated from
109+
`delphi_epidata.coffee`_. However, for trivial changes, like the addition
110+
of this very simple endpoint, it may be slightly faster, _though
111+
error-prone_, to just update the JavaScript manually.
112+
113+
```javascript
114+
Epidata.fluview_meta = function(callback) {
115+
var params;
116+
params = {
117+
'source': 'fluview_meta'
118+
};
119+
return _request(callback, params);
120+
};
121+
```
122+
123+
- [`delphi_epidata.py`](../src/client/delphi_epidata.py)
124+
125+
Note that this file, unlike the others, is released as a public package,
126+
available to install easily though Python's `pip` tool. That package should
127+
be updated once the code is committed, however that is outside of the scope
128+
of this tutorial.
129+
130+
```python
131+
# Fetch FluView metadata
132+
@staticmethod
133+
def fluview_meta():
134+
"""Fetch FluView metadata."""
135+
# Set up request
136+
params = {
137+
'source': 'fluview_meta',
138+
}
139+
# Make the API call
140+
return Epidata._request(params)
141+
```
142+
143+
- [`delphi_epidata.R`](../src/client/delphi_epidata.R)
144+
145+
```R
146+
# Fetch FluView metadata
147+
fluview_meta <- function() {
148+
# Set up request
149+
params <- list(
150+
source = 'fluview_meta'
151+
)
152+
# Make the API call
153+
return(.request(params))
154+
}
155+
```
156+
157+
**This file requires a second change: updating the list of exported
158+
functions.** This additional step only applies to this particular client
159+
library. At the bottom of the file, inside of `return(list(`, add the
160+
following line to make the function available to callers.
161+
162+
```R
163+
fluview_meta = fluview_meta,
164+
```
165+
166+
# add an integration test
167+
168+
Now that we've changed several files, we need to make sure that the changes
169+
work as intended _before_ submitting code for review or committing code to the
170+
repository. Given that the code spans multiple components and languages, this
171+
needs to be an integration test. See more about integration testing in Delphi's
172+
[frontend development guide](https://github.com/cmu-delphi/operations/blob/master/docs/frontend_development.md#integration).
173+
174+
Create an integration test for the new endpoint by creating a new file
175+
`integrations/server/test_fluview_meta.py`. There's a good amount of
176+
boilerplate, but fortunately this can be copied _almost_ verbatim from the
177+
[`fluview` endpoint integration test](../integrations/server/test_fluview.py).
178+
179+
Include the following pieces:
180+
181+
- top-level docstring (update name to `fluview_meta`)
182+
- the imports section (no changes needed)
183+
- the test class (update name and docstring for `fluview_meta`)
184+
- the methods `setUpClass`, `setUp`, and `tearDown` (no changes needed)
185+
186+
Add the following test method which creates some dummy data, fetches the new
187+
`fluview_meta` endpoint using the Python client library, and asserts that the
188+
returned value is what we expect.
189+
190+
```python
191+
def test_round_trip(self):
192+
"""Make a simple round-trip with some sample data."""
193+
194+
# insert dummy data
195+
self.cur.execute('''
196+
insert into fluview values
197+
(0, "2020-04-07", 202021, 202020, "nat", 1, 2, 3, 4, 3.14159, 1.41421,
198+
10, 11, 12, 13, 14, 15),
199+
(0, "2020-04-28", 202022, 202022, "hhs1", 5, 6, 7, 8, 1.11111, 2.22222,
200+
20, 21, 22, 23, 24, 25)
201+
''')
202+
self.cnx.commit()
203+
204+
# make the request
205+
response = Epidata.fluview_meta()
206+
207+
# assert that the right data came back
208+
self.assertEqual(response, {
209+
'result': 1,
210+
'epidata': [{
211+
'latest_update': '2020-04-28',
212+
'latest_issue': 202022,
213+
'table_rows': 2,
214+
}],
215+
'message': 'success',
216+
})
217+
```
218+
219+
# write documentation
220+
221+
This consists of two steps: add a new document for the `fluview_meta` endpoint,
222+
and add a new entry to the existing table of endpoints.
223+
224+
Create a new file `docs/api/fluview_meta.md`. Copy as much as needed from other
225+
endpoints, e.g. [the fluview documentation](api/fluview.md). Update the
226+
description, table of return values, and sample code and URLs as needed.
227+
228+
Edit the table of endpoints in [`docs/api/README.md`](api/README.md), adding
229+
the following row in the appropriate place (i.e. next to the row for
230+
`fluview`):
231+
232+
```
233+
| [`fluview_meta`](fluview_meta.md) | FluView Metadata | Summary data about [`fluview`](fluview.md). | no |
234+
```
235+
236+
# run tests
237+
238+
## unit
239+
240+
Finally, we just need to run all new and existing tests. It is recommended to
241+
start with the unit tests because they are faster to build, run, and either
242+
succeed or fail. Follow the
243+
[backend development guide](https://github.com/cmu-delphi/operations/blob/master/docs/backend_development.md#running-a-container).
244+
In summary:
245+
246+
```bash
247+
# build the image
248+
docker build -t delphi_python \
249+
-f repos/delphi/operations/dev/docker/python/Dockerfile .
250+
251+
# run epidata unit tests
252+
docker run --rm delphi_python \
253+
python3 -m undefx.py3tester.py3tester --color \
254+
repos/delphi/delphi-epidata/tests
255+
```
256+
257+
If all succeeds, output should look like this:
258+
259+
```
260+
[...]
261+
262+
✔ All 48 tests passed! 69% (486/704) coverage.
263+
```
264+
265+
## integration
266+
267+
Integration tests require more effort, and take longer to setup and run.
268+
However, they allow us to test that various pieces are working together
269+
correctly. Many of these pieces we can't test individually with unit tests
270+
(e.g. database, and the API server), so integration tests are the only way we
271+
can be confident that our changes won't break the API. Follow the [epidata
272+
development guide](epidata_development.md#test). In summary, assuming you have
273+
already built the `delphi_python` image above:
274+
275+
```bash
276+
# build web and database images for epidata
277+
docker build -t delphi_web \
278+
-f repos/delphi/operations/dev/docker/web/Dockerfile .
279+
docker build -t delphi_web_epidata \
280+
-f repos/delphi/delphi-epidata/dev/docker/web/epidata/Dockerfile .
281+
docker build -t delphi_database \
282+
-f repos/delphi/operations/dev/docker/database/Dockerfile .
283+
docker build -t delphi_database_epidata \
284+
-f repos/delphi/delphi-epidata/dev/docker/database/epidata/Dockerfile .
285+
286+
# launch web and database containers in separate terminals
287+
docker run --rm -p 13306:3306 \
288+
--network delphi-net --name delphi_database_epidata \
289+
delphi_database_epidata
290+
291+
docker run --rm -p 10080:80 \
292+
--network delphi-net --name delphi_web_epidata \
293+
delphi_web_epidata
294+
295+
# wait for the above containers to initialize (~15 seconds)
296+
297+
# run integration tests
298+
docker run --rm --network delphi-net delphi_python \
299+
python3 -m undefx.py3tester.py3tester --color \
300+
repos/delphi/delphi-epidata/integrations
301+
```
302+
303+
If all succeeds, output should look like this. Note also that our new
304+
integration test specifically passed.
305+
306+
```
307+
[...]
308+
309+
delphi.delphi-epidata.integrations.server.test_fluview_meta.FluviewMetaTests.test_round_trip: pass
310+
311+
[...]
312+
313+
✔ All 16 tests passed! 48% (180/372) coverage.
314+
```
315+
316+
# code review and submission
317+
318+
All tests pass, and the changes are working as intended. Now submit the code
319+
for review, e.g. by opening a pull request on GitHub. For an example, see the
320+
actual
321+
[pull request for the `fluview_meta` endpoint](https://github.com/cmu-delphi/delphi-epidata/pull/93)
322+
created in this tutorial.
323+
324+
Once it's approved, commit the code. Within a short amount of time (usually ~30
325+
seconds), the API will begin serving your new endpoint. Go ahead and give it a
326+
try: https://delphi.midas.cs.cmu.edu/epidata/api.php?source=fluview_meta
327+
328+
```
329+
{
330+
"result": 1,
331+
"epidata": [
332+
{
333+
"latest_update": "2020-04-24",
334+
"latest_issue": 202016,
335+
"table_rows": 957673
336+
}
337+
],
338+
"message": "success"
339+
}
340+
```

0 commit comments

Comments
 (0)