From 0559505b3bab54da50ab1fdd6eb82df3c18ba307 Mon Sep 17 00:00:00 2001
From: Leo Singer <leo.singer@ligo.org>
Date: Thu, 27 Feb 2020 17:53:26 -0500
Subject: [PATCH] Work around GPS time validation bug in GraceDB server

Work around a bug in GraceDB where normalization of floating-point
GPS times to fixed-precision decimal representation is applied to
JSON-encoded requests but not form-encoded requests. This bug
caused superevent API requests with GPS times specified with more
than 6 decimal places to fail.

See https://git.ligo.org/lscsoft/gracedb/issues/195.
---
 CHANGES.rst                   |  6 +++++-
 gracedb_sdk/api/events.py     | 19 ++++++++++++-------
 gracedb_sdk/tests/test_api.py | 16 +++++++++++-----
 3 files changed, 28 insertions(+), 13 deletions(-)

diff --git a/CHANGES.rst b/CHANGES.rst
index b91d7af..e2d2d8d 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -4,7 +4,11 @@ Changelog
 0.1.3 (unreleased)
 ------------------
 
--   No changes yet.
+-   Work around a bug in GraceDB where normalization of floating-point GPS
+    times to fixed-precision decimal representation is applied to JSON-encoded
+    requests but not form-encoded requests. This bug caused superevent API
+    requests with GPS times specified with more than 6 decimal places to fail.
+    See https://git.ligo.org/lscsoft/gracedb/issues/195.
 
 0.1.2 (2020-02-20)
 ------------------
diff --git a/gracedb_sdk/api/events.py b/gracedb_sdk/api/events.py
index 6f5e486..232669d 100644
--- a/gracedb_sdk/api/events.py
+++ b/gracedb_sdk/api/events.py
@@ -18,7 +18,7 @@ from os.path import join
 
 from .base import Deletable, Mapping, Mutable, Resource
 from .event import Event, Superevent
-from .util import field_collection
+from .util import field_collection, str_or_collection
 
 
 class BaseEvents(Deletable, Mapping, Mutable, Resource):
@@ -69,20 +69,25 @@ class Superevents(BaseEvents):
 
     def create_or_update(self, superevent_id, *,
                          events=None, labels=None, **kwargs):
-        data = (*field_collection('events', events),
-                *field_collection('labels', labels),
-                *kwargs.items())
+        data = {key: value for key, value in kwargs.items()
+                if value is not None}
+        if events:
+            data['events'] = str_or_collection(events)
+        if labels:
+            data['labels'] = str_or_collection(labels)
 
         # Automatically guess category based on prefix of preferred event
         preferred_event = kwargs.get('preferred_event')
         if preferred_event is not None:
             category = SUPEREVENT_CATEGORIES[preferred_event[0]]
-            data += (('category', category),)
+            data['category'] = category
 
+        # FIXME: superevent creation requests must be JSON-encoded rather than
+        # form-encoded due to https://git.ligo.org/lscsoft/gracedb/issues/195
         if superevent_id is None:
-            return super().create_or_update(superevent_id, data=data)
+            return super().create_or_update(superevent_id, json=data)
         else:
             # FIXME: GraceDB does not support 'put' here, only 'patch'!
             # This is inconsistent between events and superevents.
             url = join(self.url, superevent_id) + '/'
-            return self.session.patch(url, data=data)
+            return self.session.patch(url, json=data)
diff --git a/gracedb_sdk/tests/test_api.py b/gracedb_sdk/tests/test_api.py
index b572b60..299c4ea 100644
--- a/gracedb_sdk/tests/test_api.py
+++ b/gracedb_sdk/tests/test_api.py
@@ -51,7 +51,10 @@ def skip_if_not_authorized(client):
             group='Test', pipeline='gstlal')
         graceid = event['graceid']
         superevent = client.superevents.create(
-            preferred_event=graceid, t_0=1e9, t_start=1e9, t_end=1e9)
+            preferred_event=graceid,
+            t_start=1000000000.12345678,
+            t_0=1000000001.12345678,
+            t_end=1000000002.12345678)
         superevent_id = superevent['superevent_id']
         client.superevents[superevent_id].expose()
     except HTTPError as e:
@@ -225,15 +228,18 @@ def test_events_voevents_get(client, events_create, events_voevents_create):
 def superevents_create(client, events_create):
     event_id = events_create['graceid']
     return client.superevents.create(
-        preferred_event=event_id, t_0=1e9, t_start=1e9, t_end=1e9)
+        preferred_event=event_id,
+        t_start=1000000000.12345678,
+        t_0=1000000001.12345678,
+        t_end=1000000002.12345678)
 
 
 def test_superevents_create(client, events_create, superevents_create):
     event_id = events_create['graceid']
     assert superevents_create['preferred_event'] == event_id
-    assert superevents_create['t_start'] == 1e9
-    assert superevents_create['t_0'] == 1e9
-    assert superevents_create['t_end'] == 1e9
+    assert superevents_create['t_start'] == 1000000000.123457
+    assert superevents_create['t_0'] == 1000000001.123457
+    assert superevents_create['t_end'] == 1000000002.123457
 
 
 def test_superevents_get(client, superevents_create):
-- 
GitLab