Skip to content

Commit 91211f2

Browse files
committed
init qb response
1 parent e65dc72 commit 91211f2

File tree

3 files changed

+194
-93
lines changed

3 files changed

+194
-93
lines changed

quickbase_json/client.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
import json
33

44

5+
6+
57
class QuickbaseJSONClient:
68
def __init__(self, realm, auth, **kwargs):
79
self.realm = realm

quickbase_json/qb_response.py

Lines changed: 154 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -1,60 +1,104 @@
1-
test_data = {
2-
"data": [
3-
{
4-
"6": {
5-
"value": "Andre Harris"
6-
},
7-
"7": {
8-
"value": 10
9-
},
10-
"8": {
11-
"value": "2019-12-18T08:00:00.000Z"
12-
}
13-
}
14-
],
15-
"fields": [
16-
{
17-
"id": 6,
18-
"label": "Full Name",
19-
"type": "text"
20-
},
21-
{
22-
"id": 7,
23-
"label": "Amount",
24-
"type": "numeric"
25-
},
26-
{
27-
"id": 8,
28-
"label": "Date time",
29-
"type": "date time"
30-
}
31-
],
32-
"metadata": {
33-
"totalRecords": 10,
34-
"numRecords": 1,
35-
"numFields": 3,
36-
"skip": 0
37-
}
38-
}
1+
import datetime
2+
3+
4+
class bcolors:
5+
HEADER = '\033[95m'
6+
OKBLUE = '\033[94m'
7+
OKCYAN = '\033[96m'
8+
OKGREEN = '\033[92m'
9+
WARNING = '\033[93m'
10+
FAIL = '\033[91m'
11+
ENDC = '\033[0m'
12+
BOLD = '\033[1m'
13+
UNDERLINE = '\033[4m'
3914

4015

4116
class QBResponse(dict):
42-
def __init__(self, response_type):
17+
def __init__(self, response_type, **kwargs):
4318
self.response_type = response_type
19+
self.transformations = {}
20+
# potential to load sample data for testing
21+
if kwargs.get('sample_data'):
22+
self.update(kwargs.get('sample_data'))
4423
super().__init__()
4524

25+
def info(self):
26+
"""
27+
Prints information about the response.
28+
"""
29+
if self.response_type == 'records':
30+
31+
print(f'{bcolors.OKBLUE}Sample Data:\n')
32+
try:
33+
print('\t', self.get('data')[0], '\n')
34+
except KeyError as e:
35+
print('\t', self.get('data'), '\n')
36+
print(bcolors.ENDC)
37+
print(f'{bcolors.OKGREEN}Fields:\n')
38+
39+
fields_info = []
40+
for field in self.get('fields'):
41+
field_info = []
42+
# build field info
43+
for k, v in field.items():
44+
field_info.append(f'{k}: {v}')
45+
fields_info.append(field_info)
46+
47+
# print field info
48+
for fi in fields_info:
49+
print('\t', '{:<16s} {:<32s} {:<16s}'.format(fi[0], fi[1], fi[2]))
50+
print(bcolors.ENDC)
51+
52+
print(f'\n{bcolors.OKCYAN}Metadata:\n')
53+
for k, v in self.get('metadata').items():
54+
print('\t{:<16s} {:<16s}'.format(k, str(v)))
55+
print(bcolors.ENDC)
56+
57+
def fields(self, prop):
58+
"""
59+
Gets fields, given property, i.e. label, id or type
60+
:param prop: property of field
61+
:return: list of fields
62+
"""
63+
return [i.get(prop) for i in self.get('fields')]
64+
65+
def data(self):
66+
return self.get('data')
67+
68+
def prd(self, data):
69+
"""
70+
Print relevant data, simple print function.
71+
:param data: data to print
72+
"""
73+
print("\033[91m", 'Relevant Data:\n\n', data, '\n', "\033[0m")
74+
4675
def denest(self):
47-
denested_list = []
48-
for record in self.get('data'):
49-
denested = {}
50-
for k, v in record.items():
51-
denested.update({k: v.get('value')})
52-
denested_list.append(denested)
53-
self.update({'data': denested_list})
76+
"""
77+
Denests data, i.e. if in {'key': {'value': actual_value}} format. -> {'key': actual_value}
78+
:return: QBResponse
79+
"""
80+
data = self.get('data')
81+
# ugly handling for now, need to fix
82+
if type(data) is dict:
83+
new_records = {}
84+
for record, record_value in data.items():
85+
new_record_value = {}
86+
for k, v in record_value.items():
87+
new_record = {}
88+
new_record.update({k: v.get('value')})
89+
new_record_value.update(new_record)
90+
new_records.update({record: new_record_value})
91+
92+
self.update({'data': new_records})
5493
return self
5594

5695
def orient(self, orient, **kwargs):
57-
96+
"""
97+
Orients the data, given orientation argument.
98+
:param orient: type of orientation, 'records' is the only orientation for now.
99+
:param kwargs: 'records' argument needs 'key' argument, to determine key for records.
100+
:return: QBResponse
101+
"""
58102
if orient == 'records':
59103
if not kwargs.get('key'):
60104
raise ValueError('Missing required "key" argument for records orientation')
@@ -67,10 +111,23 @@ def orient(self, orient, **kwargs):
67111

68112
# loop data list, create dict
69113
records = {}
70-
for i in self.get('data'):
71-
print(i)
72-
key = i.pop(selector).get('value') if self.get('data') else i.pop(selector)
73-
records.update({key: i})
114+
try:
115+
for i in self.get('data'):
116+
key = i.pop(selector).get('value') if self.get('data') else i.pop(selector)
117+
records.update({key: i})
118+
except KeyError as e:
119+
print('KeyError:', e, 'Attempting to use Record ID#')
120+
self.prd(self.get('data')[0])
121+
122+
# attempt to find selector in fields
123+
for i in self.get('fields'):
124+
if i.get('id') == int(selector):
125+
selector = i.get('label')
126+
break
127+
128+
for i in self.get('data'):
129+
key = i.pop(selector)
130+
records.update({key: i})
74131

75132
# set data to records
76133
self.update({'data': records})
@@ -84,6 +141,11 @@ def currency(self, currency_type):
84141
return self
85142

86143
def transform(self, transformation):
144+
"""
145+
Transforms the data, given a transformation argument.
146+
:param transformation: type of transformation.
147+
:return: QBResponse
148+
"""
87149
if transformation == 'labels':
88150

89151
# transform fids into labels
@@ -92,61 +154,60 @@ def transform(self, transformation):
92154
'Try transforming the data before applying additional methods.')
93155

94156
data = self.get('data')
95-
print(data)
96157
fields = self.get('fields')
97158

98159
fields_dict = {i['id']: i for i in fields}
99160

161+
# replace record id numbers with record labels
100162
records = []
101163
for idx, d in enumerate(data):
102164
record = {}
103-
print('data', d)
104165
for k, v in d.items():
105166
record.update({
106167
fields_dict[int(k)].get('label'): v if v.get('value') is None else v.get('value')
107168
})
108-
records.append(record)
109-
print(k, v)
110169

170+
records.append(record)
111171
self.update({'data': records})
112172

113-
return self
173+
if transformation == 'datetime':
174+
"""Finds columns that are of type datetime and converts them into python datetime objects"""
175+
# transform fids into labels
176+
177+
data = self.get('data')
178+
fields = self.get('fields')
114179

180+
for field in fields:
181+
if field.get('type') == 'date time':
115182

116-
qbr = QBResponse('records')
117-
qbr.update(test_data)
118-
qbr.transform('labels').orient('records', key=6)
119-
120-
print(qbr)
121-
122-
# # handle transform
123-
# if kwargs.get('transform'):
124-
# # labels
125-
# if kwargs.get('transform') == 'labels':
126-
#
127-
# # transform fids into labels
128-
# data = json_data.get('data')
129-
# fields = json_data.get('fields')
130-
#
131-
# fields_dict = {i['id']: i for i in fields}
132-
# print(fields_dict)
133-
#
134-
# records = []
135-
# for idx, d in enumerate(data):
136-
# record = {}
137-
# print('data', d)
138-
# for k, v in d.items():
139-
# record.update({
140-
# fields_dict[int(k)].get('label'): v if kwargs.get('denested') else v.get('value')
141-
# })
142-
# records.append(record)
143-
# print(k, v)
144-
#
145-
# json_data.update({'data': records})
146-
#
147-
148-
#
149-
# # handle data orientation
150-
# if kwargs.get('orient'):
151-
# if kwargs.get('orient') == 'records':
152-
#
183+
if type(self.get('data') == list):
184+
for row in data:
185+
dt_field = row.get(str(field.get('id')))
186+
if dt_field.get('value') is None:
187+
str_dt = dt_field
188+
dt = datetime.datetime.strptime(str_dt, '%Y-%m-%dT%H:%M:%S.%fZ')
189+
row.update({str(field.get('id')): dt})
190+
else:
191+
str_dt = dt_field.get('value')
192+
dt = datetime.datetime.strptime(str_dt, '%Y-%m-%dT%H:%M:%S.%fZ')
193+
row.update({str(field.get('id')): {'value': dt}})
194+
195+
if transformation == 'intround':
196+
"""Rounds numbers and transforms them to ints"""
197+
198+
data = self.get('data')
199+
fields = self.get('fields')
200+
201+
for field in fields:
202+
if field.get('type') == 'numeric':
203+
204+
if type(self.get('data') == list):
205+
for row in data:
206+
numeric_field = row.get(str(field.get('id')))
207+
if numeric_field.get('value') is None:
208+
row.update({str(field.get('id')): int(round(numeric_field))})
209+
else:
210+
numeric_field = numeric_field.get('value')
211+
row.update({str(field.get('id')): {'value': int(round(numeric_field))}})
212+
213+
return self

tests/sample_data.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
record_data = {
2+
"data": [
3+
{
4+
"6": {
5+
"value": "Andre Harris"
6+
},
7+
"7": {
8+
"value": 10
9+
},
10+
"8": {
11+
"value": "2019-12-18T08:00:00.000Z"
12+
}
13+
}
14+
],
15+
"fields": [
16+
{
17+
"id": 6,
18+
"label": "Full Name",
19+
"type": "text"
20+
},
21+
{
22+
"id": 7,
23+
"label": "Amount",
24+
"type": "numeric"
25+
},
26+
{
27+
"id": 8,
28+
"label": "Date time",
29+
"type": "date time"
30+
}
31+
],
32+
"metadata": {
33+
"totalRecords": 10,
34+
"numRecords": 1,
35+
"numFields": 3,
36+
"skip": 0
37+
}
38+
}

0 commit comments

Comments
 (0)