1
1
import os
2
2
from pathlib import Path
3
3
from contextlib import contextmanager
4
+ import datetime
4
5
5
6
import requests
6
7
import json
8
+ from hashlib import md5
7
9
8
10
from flask import Blueprint , abort , current_app , request
9
11
from flask_jsonpify import jsonpify
15
17
ROOT_DIR = Path (__file__ ).parent
16
18
17
19
18
- class SimpleDBBlueprint ( Blueprint ) :
20
+ class TableHolder :
19
21
20
22
TABLES = [
21
23
'budget_items_data' ,
@@ -28,33 +30,49 @@ class SimpleDBBlueprint(Blueprint):
28
30
]
29
31
30
32
DATAPACKAGE_URL = 'https://next.obudget.org/datapackages/simpledb'
33
+ TIMEOUT = 600
31
34
32
- def __init__ (self , connection_string , search_blueprint ):
33
- super ().__init__ ('simpledb' , 'simpledb' )
35
+ def __init__ (self , connection_string ):
34
36
self .connection_string = connection_string
35
- self .tables , self .search_params = self .process_tables ()
36
- self .search_blueprint = search_blueprint
37
- self .add_url_rule (
38
- '/tables/<table>/info' ,
39
- 'table-info' ,
40
- self .get_table ,
41
- methods = ['GET' ]
42
- )
43
-
44
- self .add_url_rule (
45
- '/tables' ,
46
- 'table-list' ,
47
- self .get_tables ,
48
- methods = ['GET' ]
49
- )
50
-
51
- if search_blueprint :
52
- self .add_url_rule (
53
- '/tables/<table>/search' ,
54
- 'table-search' ,
55
- self .simple_search ,
56
- methods = ['GET' ]
57
- )
37
+ self .infos = dict ()
38
+ self .schemas = dict ()
39
+
40
+ def get_info (self , table ):
41
+ info , _ = self .get_table_data (table )
42
+ info ['schema' ] = self .get_schema (table )
43
+ return info
44
+
45
+ def get_schema (self , table ):
46
+ if table not in self .schemas :
47
+ self .schemas [table ] = self .get_schema_from_db (table )
48
+ return self .schemas [table ]
49
+
50
+ def get_search_params (self , table ):
51
+ _ , search = self .get_table_data (table )
52
+ return search
53
+
54
+ def get_table_data (self , table ):
55
+ fetch = True
56
+ current_hash = None
57
+ if table in self .infos :
58
+ info , search , ts , current_hash = self .infos [table ]
59
+ if (datetime .datetime .now () - ts ).seconds < self .TIMEOUT :
60
+ fetch = False
61
+ if fetch :
62
+ info , search , hash = self .fetch_table (table )
63
+ if info is not None :
64
+ self .infos [table ] = (info , search , datetime .datetime .now (), hash )
65
+ if current_hash != hash :
66
+ self .schemas [table ] = None
67
+ info , search , ts = self .infos [table ]
68
+ return info , search
69
+
70
+ def get_schema_from_db (self , table ):
71
+ with self .connect_db () as connection :
72
+ query = text (f"select generate_create_table_statement('{ table } ')" )
73
+ result = connection .execute (query )
74
+ create_table = result .fetchone ()[0 ]
75
+ return create_table
58
76
59
77
@contextmanager
60
78
def connect_db (self ):
@@ -67,45 +85,67 @@ def connect_db(self):
67
85
engine .dispose ()
68
86
del engine
69
87
70
- def process_tables (self ):
71
- ret = dict ()
72
- sp = dict ()
73
- with self .connect_db () as connection :
74
- for table in self .TABLES :
88
+ def fetch_table (self , table ):
89
+ if table in self .TABLES :
90
+ with self .connect_db () as connection :
75
91
try :
76
92
rec = {}
77
93
datapackage_url = f'{ self .DATAPACKAGE_URL } /{ table } /datapackage.json'
78
- package_descriptor = requests .get (datapackage_url ).json ()
94
+ response = requests .get (datapackage_url )
95
+ hash = md5 (response .content ).hexdigest ()
96
+ package_descriptor = response .json ()
79
97
description = package_descriptor ['resources' ][0 ]['description' ]
80
98
fields = package_descriptor ['resources' ][0 ]['schema' ]['fields' ]
81
99
rec ['description' ] = description
82
100
rec ['fields' ] = [dict (
83
101
name = f ['name' ],
84
102
** f .get ('details' , {})
85
103
) for f in fields ]
86
- rec ['schema' ] = self .get_schema (connection , table )
87
- ret [table ] = rec
88
- sp [table ] = package_descriptor ['resources' ][0 ]['search' ]
104
+ return rec , package_descriptor ['resources' ][0 ]['search' ], hash
89
105
except Exception as e :
90
106
print (f'Error processing table { table } : { e } ' )
91
- return ret , sp
107
+ return None , None , None
92
108
93
- def get_schema (self , connection , table ):
94
- query = text (f"select generate_create_table_statement('{ table } ')" )
95
- result = connection .execute (query )
96
- create_table = result .fetchone ()[0 ]
97
- return create_table
109
+ class SimpleDBBlueprint (Blueprint ):
110
+
111
+ def __init__ (self , connection_string , search_blueprint ):
112
+ super ().__init__ ('simpledb' , 'simpledb' )
113
+ self .tables = TableHolder (connection_string )
114
+
115
+ self .search_blueprint = search_blueprint
116
+ self .add_url_rule (
117
+ '/tables/<table>/info' ,
118
+ 'table-info' ,
119
+ self .get_table ,
120
+ methods = ['GET' ]
121
+ )
122
+
123
+ self .add_url_rule (
124
+ '/tables' ,
125
+ 'table-list' ,
126
+ self .get_tables ,
127
+ methods = ['GET' ]
128
+ )
129
+
130
+ if search_blueprint :
131
+ self .add_url_rule (
132
+ '/tables/<table>/search' ,
133
+ 'table-search' ,
134
+ self .simple_search ,
135
+ methods = ['GET' ]
136
+ )
98
137
99
138
def get_table (self , table ):
100
- if table not in self .tables :
101
- abort (404 , f'Table { table } not found. Available tables: { ", " .join (self .tables .keys ())} ' )
102
- return jsonpify (self .tables [table ])
139
+ ret = self .tables .get_info (table )
140
+ if ret is None :
141
+ abort (404 , f'Table { table } not found. Available tables: { ", " .join (self .tables .TABLES )} ' )
142
+ return jsonpify (ret )
103
143
104
144
def get_tables (self ):
105
- return jsonpify (list ( self .tables .keys ()) )
145
+ return jsonpify (self .tables .TABLES )
106
146
107
147
def simple_search (self , table ):
108
- params = self .search_params [ table ]
148
+ params = self .tables . get_search_params ( table )
109
149
110
150
q = request .args .get ('q' , '' )
111
151
filters = params .get ('filters' , {}) or {}
@@ -141,5 +181,5 @@ def simple_search(self, table):
141
181
142
182
def setup_simpledb (app , es_blueprint ):
143
183
sdb = SimpleDBBlueprint (os .environ ['DATABASE_READONLY_URL' ], es_blueprint )
144
- add_cache_header (sdb , 600 )
184
+ add_cache_header (sdb , TableHolder . TIMEOUT )
145
185
app .register_blueprint (sdb , url_prefix = '/api/' )
0 commit comments