Skip to content

Commit c370c7b

Browse files
Type bug fix and regression test for types (#65)
* Adds a conversion function for json.dumps that handles unsupported types. * Adds regression tests for all types.
1 parent 9e7ec77 commit c370c7b

File tree

8 files changed

+413
-13
lines changed

8 files changed

+413
-13
lines changed

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -128,15 +128,15 @@ TABLESCHEMA_TO_SQLALCHEMY_TYPES = {
128128
'integer': Integer,
129129
'boolean': Boolean,
130130
'object': String,
131-
'array': String,
131+
'array': String, # Not supported yet
132132
'date': Date,
133-
'time': DateTime,
133+
'time': DateTime, # Not supported yet
134134
'datetime': DateTime,
135135
'year': Integer,
136-
'yearmonth': Integer,
136+
'yearmonth': Integer, # Not supported yet
137137
'duration': Integer,
138138
'geopoint': String,
139-
'geojson': String,
139+
'geojson': String, # Not supported yet
140140
'any': String
141141
}
142142
```

data_resource_api/app/data_managers/data_resource_manager.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,13 @@
66
"""
77
from threading import Thread
88
from time import sleep
9-
from flask import Flask
9+
from flask import Flask, make_response
1010
from flask_restful import Api, Resource
1111
from data_resource_api.factories import DataResourceFactory
1212
from data_resource_api.db import Base, Session, Checksum
1313
from data_resource_api.app.utils.exception_handler import handle_errors
1414
from data_resource_api.app.utils.descriptor import Descriptor
15+
from data_resource_api.app.utils.json_converter import safe_json_dumps
1516
from data_resource_api.utils import exponential_backoff
1617
from data_resource_api.app.data_managers.data_manager import DataManager
1718

@@ -156,6 +157,12 @@ def create_app(self):
156157
'/', endpoint='all_services_ep')
157158
self.app.register_error_handler(Exception, handle_errors)
158159

160+
@self.api.representation('application/json')
161+
def output_json(data, code, headers=None):
162+
resp = make_response(safe_json_dumps(data), code)
163+
resp.headers.extend(headers or {})
164+
return resp
165+
159166
return self.app
160167

161168
def process_descriptor(self, descriptor: Descriptor):
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import json
2+
import datetime
3+
4+
5+
def unknown_field_json_converter(o):
6+
if isinstance(o, datetime.datetime):
7+
return str(o.isoformat()) + "Z"
8+
if isinstance(o, datetime.date):
9+
return str(o.isoformat())
10+
11+
12+
def safe_json_dumps(json_dict):
13+
"""This will add explicit conversions for types to json.dumps
14+
"""
15+
return json.dumps(json_dict, default=unknown_field_json_converter)

tests/conftest.py

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@
1212
skills_descriptor,
1313
credentials_descriptor,
1414
programs_descriptor,
15-
json_descriptor)
15+
json_descriptor,
16+
everything_descriptor)
1617

1718
from sqlalchemy.ext.declarative import declarative_base
1819
from data_resource_api.logging import LogFactory
@@ -209,11 +210,18 @@ def json_client():
209210

210211

211212
@pytest.fixture(scope='module')
212-
def no_db_dmm():
213-
dmm = DataModelManagerSync(
214-
use_local_dirs=False,
215-
descriptors=None)
216-
yield dmm
213+
def everything_client():
214+
client = Client([everything_descriptor])
215+
yield client.run_and_return_test_client()
216+
client.stop_container()
217+
218+
219+
# @pytest.fixture(scope='module')
220+
# def no_db_dmm():
221+
# dmm = DataModelManagerSync(
222+
# use_local_dirs=False,
223+
# descriptors=None)
224+
# yield dmm
217225

218226

219227
@pytest.fixture

tests/end_to_end/test_all_types.py

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
import pytest
2+
import json
3+
from tests.service import ApiHelper
4+
from expects import expect, be_an, raise_error, have_property, equal, be_empty
5+
6+
7+
ROUTE = '/alltypes'
8+
9+
10+
def run_query(client, key, value, expected_value=None):
11+
if not expected_value:
12+
expected_value = value
13+
post_body = {
14+
key: value
15+
}
16+
id_ = ApiHelper.everything_post(ROUTE, client, post_body)
17+
resp_data = ApiHelper.everything_get(ROUTE, client, id_)
18+
resp = json.loads(resp_data)
19+
20+
# response = json.dumps(resp, sort_keys=True)
21+
# expected_output = json.dumps(expected_string, sort_keys=True)
22+
expect(resp[key]).to(equal(expected_value))
23+
assert type(resp[key]) == type(expected_value)
24+
25+
26+
def test_string(everything_client):
27+
# {
28+
# "name": "string",
29+
# "title": "string",
30+
# "type": "string",
31+
# "required": False
32+
# },
33+
run_query(everything_client, "string", "asdf1234")
34+
35+
36+
# @pytest.mark.xfail
37+
def test_number(everything_client):
38+
# {
39+
# "name": "number",
40+
# "title": "number",
41+
# "type": "number",
42+
# "required": False
43+
# },
44+
run_query(everything_client, "number", 1234.0)
45+
run_query(everything_client, "number", 1234, 1234.0)
46+
47+
48+
# @pytest.mark.xfail
49+
def test_integer(everything_client):
50+
# {
51+
# "name": "integer",
52+
# "title": "integer",
53+
# "type": "integer",
54+
# "required": False
55+
# },
56+
run_query(everything_client, "integer", 1234)
57+
58+
59+
# @pytest.mark.xfail
60+
def test_boolean(everything_client):
61+
# {
62+
# "name": "boolean",
63+
# "title": "boolean",
64+
# "type": "boolean",
65+
# "required": False
66+
# },
67+
run_query(everything_client, "boolean", False)
68+
run_query(everything_client, "boolean", True)
69+
70+
71+
# @pytest.mark.xfail
72+
def test_object(everything_client):
73+
# {
74+
# "name": "object",
75+
# "title": "object",
76+
# "type": "object",
77+
# "required": False
78+
# },
79+
run_query(everything_client, "object", {"json": "test"})
80+
81+
82+
@pytest.mark.skip # Unsure how this should return
83+
def test_array(everything_client):
84+
# {
85+
# "name": "array",
86+
# "title": "array",
87+
# "type": "array",
88+
# "required": False
89+
# },
90+
run_query(everything_client, "array", ['one', 'two', 'three'])
91+
92+
93+
# @pytest.mark.xfail
94+
def test_date(everything_client):
95+
# {
96+
# "name": "date",
97+
# "title": "date",
98+
# "type": "date",
99+
# "required": False
100+
# },
101+
run_query(everything_client, "date", "2012-04-23")
102+
103+
104+
@pytest.mark.xfail # TODO cannot save to database
105+
def test_time(everything_client):
106+
# {
107+
# "name": "time",
108+
# "title": "time",
109+
# "type": "time",
110+
# "required": False
111+
# },
112+
run_query(everything_client, "time", "18:25:43.511Z")
113+
114+
115+
# @pytest.mark.xfail
116+
def test_datetime(everything_client):
117+
# {
118+
# "name": "datetime",
119+
# "title": "datetime",
120+
# "type": "datetime",
121+
# "required": False
122+
# },
123+
run_query(everything_client, "datetime", "2012-04-23T18:25:43Z")
124+
125+
126+
@pytest.mark.xfail
127+
def test_datetime_with_miliseconds(everything_client):
128+
# {
129+
# "name": "datetime",
130+
# "title": "datetime",
131+
# "type": "datetime",
132+
# "required": False
133+
# },
134+
run_query(everything_client, "datetime", "2012-04-23T18:25:43.511Z")
135+
136+
137+
# @pytest.mark.xfail
138+
def test_year(everything_client):
139+
# {
140+
# "name": "year",
141+
# "title": "year",
142+
# "type": "year",
143+
# "required": False
144+
# },
145+
run_query(everything_client, "year", 2012)
146+
147+
148+
@pytest.mark.xfail # TODO does not save to database
149+
def test_yearmonth(everything_client):
150+
# {
151+
# "name": "yearmonth",
152+
# "title": "yearmonth",
153+
# "type": "yearmonth",
154+
# "required": False
155+
# },
156+
run_query(everything_client, "yearmonth", "2012-11")
157+
158+
159+
# @pytest.mark.xfail
160+
def test_duration(everything_client):
161+
# {
162+
# "name": "duration",
163+
# "title": "duration",
164+
# "type": "duration",
165+
# "required": False
166+
# },
167+
run_query(everything_client, "duration", 11)
168+
169+
170+
# @pytest.mark.xfail
171+
def test_geopoint(everything_client):
172+
# {
173+
# "name": "geopoint",
174+
# "title": "geopoint",
175+
# "type": "geopoint",
176+
# "required": False
177+
# },
178+
run_query(everything_client, "geopoint", "41.12,-71.34")
179+
180+
181+
# @pytest.mark.xfail
182+
def test_geojson(everything_client):
183+
# {
184+
# "name": "geojson",
185+
# "title": "geojson",
186+
# "type": "geojson",
187+
# "required": False
188+
# },
189+
run_query(everything_client, "geopoint", "41.12,-71.34")
190+
# https://geojson.org/ # TODO we don't support this
191+
192+
193+
# @pytest.mark.xfail
194+
def test_any(everything_client):
195+
# {
196+
# "name": "any",
197+
# "title": "any",
198+
# "type": "any",
199+
# "required": False
200+
# }
201+
run_query(everything_client, "any", "asdf1234")

0 commit comments

Comments
 (0)