22import traceback
33import json
44
5- from flask import request , Blueprint , jsonify
5+ from flask import request , Blueprint , jsonify , make_response
66from flask_restplus import Api , Resource , fields
77import iso8601
88from datetime import datetime , timedelta
2626blueprint = Blueprint ('api' , __name__ , url_prefix = '/api' )
2727api = Api (blueprint , doc = '/' )
2828
29+
2930# TODO: Clean up JSONEncoder code?
3031class CustomJSONEncoder (json .JSONEncoder ):
3132 def __init__ (self , * args , ** kwargs ):
@@ -40,6 +41,8 @@ def default(self, obj, *args, **kwargs):
4041 except TypeError :
4142 pass
4243 return json .JSONEncoder .default (self , obj )
44+
45+
4346app .json_encoder = CustomJSONEncoder
4447
4548app .register_blueprint (blueprint )
@@ -54,37 +57,32 @@ def format(self, value):
5457 return json .loads (value )
5558
5659
57- # Loads event schema from JSONSchema in aw_core
60+ # Loads event and bucket schema from JSONSchema in aw_core
5861event = api .schema_model ('Event' , schema .get_json_schema ("event" ))
62+ bucket = api .schema_model ('Bucket' , schema .get_json_schema ("bucket" ))
63+ buckets_export = api .schema_model ('Export' , schema .get_json_schema ("export" ))
5964
6065# TODO: Construct all the models from JSONSchema?
6166# A downside to contructing from JSONSchema: flask-restplus does not have marshalling support
67+
6268info = api .model ('Info' , {
6369 'hostname' : fields .String (),
6470 'version' : fields .String (),
6571 'testing' : fields .Boolean (),
6672})
6773
68- bucket = api .model ('Bucket' , {
69- 'id' : fields .String (required = True , description = 'The buckets unique id' ),
70- 'name' : fields .String (required = False , description = 'The buckets readable and renameable name' ),
71- 'type' : fields .String (required = True , description = 'The buckets event type' ),
72- 'client' : fields .String (required = True , description = 'The name of the watcher client' ),
73- 'hostname' : fields .String (required = True , description = 'The hostname of the client that the bucket belongs to' ),
74- 'created' : fields .DateTime (required = True , description = 'The creation datetime of the bucket' ),
75- })
76-
7774create_bucket = api .model ('CreateBucket' , {
7875 'client' : fields .String (required = True ),
7976 'type' : fields .String (required = True ),
8077 'hostname' : fields .String (required = True ),
8178})
8279
8380query = api .model ('Query' , {
84- 'timeperiods' : fields .List (fields .String , required = True , description = 'List of periods to query' ),
81+ 'timeperiods' : fields .List (fields .String , required = True , description = 'List of periods to query' ),
8582 'query' : fields .List (fields .String , required = True , description = 'String list of query statements' ),
8683})
8784
85+
8886def copy_doc (api_method ):
8987 """Decorator that copies another functions docstring to the decorated function.
9088 Used to copy the docstrings in ServerAPI over to the flask-restplus Resources.
@@ -117,7 +115,7 @@ def get(self) -> Dict[str, Dict]:
117115
118116@api .route ("/0/buckets/<string:bucket_id>" )
119117class BucketResource (Resource ):
120- @api .marshal_with ( bucket )
118+ @api .doc ( model = bucket )
121119 @copy_doc (ServerAPI .get_bucket_metadata )
122120 def get (self , bucket_id ):
123121 return app .api .get_bucket_metadata (bucket_id )
@@ -254,21 +252,53 @@ def post(self):
254252 return {"type" : type (qe ).__name__ , "message" : str (qe )}, 400
255253
256254
257- # EXPORTING
255+ # EXPORT AND IMPORT
256+
258257
259258@api .route ("/0/export" )
260259class ExportAllResource (Resource ):
260+ @api .doc (model = buckets_export )
261261 @copy_doc (ServerAPI .export_all )
262262 def get (self ):
263- return app .api .export_all (), 200
263+ buckets_export = app .api .export_all ()
264+ payload = {"buckets" : buckets_export }
265+ response = make_response (json .dumps (payload ))
266+ filename = "aw-buckets-export.json"
267+ response .headers ["Content-Disposition" ] = "attachment; filename={}" .format (filename )
268+ return response
264269
265270
266271# TODO: Perhaps we don't need this, could be done with a query argument to /0/export instead
267272@api .route ("/0/buckets/<string:bucket_id>/export" )
268273class BucketExportResource (Resource ):
274+ @api .doc (model = buckets_export )
269275 @copy_doc (ServerAPI .export_bucket )
270276 def get (self , bucket_id ):
271- return app .api .export_bucket (bucket_id )
277+ bucket_export = app .api .export_bucket (bucket_id )
278+ payload = {"buckets" : {bucket_export ["id" ]: bucket_export }}
279+ response = make_response (json .dumps (payload ))
280+ filename = "aw-bucket-export_{}.json" .format (bucket_export ["id" ])
281+ response .headers ["Content-Disposition" ] = "attachment; filename={}" .format (filename )
282+ return response
283+
284+
285+ @api .route ("/0/import" )
286+ class ImportAllResource (Resource ):
287+ @api .expect (buckets_export )
288+ @copy_doc (ServerAPI .import_all )
289+ def post (self ):
290+ # If import comes from a form in th web-ui
291+ if len (request .files ) > 0 :
292+ # web-ui form only allows one file, but technically it's possible to
293+ # upload multiple files at the same time
294+ for filename , f in request .files .items ():
295+ buckets = json .loads (f .stream .read ())["buckets" ]
296+ app .api .import_all (buckets )
297+ # Normal import from body
298+ else :
299+ buckets = request .get_json ()["buckets" ]
300+ app .api .import_all (buckets )
301+ return None , 200
272302
273303
274304# LOGGING
0 commit comments