You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
121 lines
3.7 KiB
Python
121 lines
3.7 KiB
Python
4 years ago
|
import sys
|
||
|
from .models import *
|
||
|
from flask import Flask, request, jsonify
|
||
|
from typing import Dict, Type, List, Final
|
||
|
from datetime import datetime
|
||
|
|
||
|
|
||
|
class ApiError(BaseException):
|
||
|
def __init__(self, msg, status=400):
|
||
|
super().__init__()
|
||
|
self.msg = msg
|
||
|
self.status = status
|
||
|
|
||
|
|
||
|
def connect_callback(attr, target):
|
||
|
"""returns a callback to connect attr with target"""
|
||
|
return lambda: attr.connect(target)
|
||
|
|
||
|
|
||
|
def construct_object_from_request(cls, uid=None):
|
||
|
if uid:
|
||
|
obj: Final = cls.nodes.get_or_none(uid=uid)
|
||
|
|
||
|
if not obj:
|
||
|
raise ApiError(f"No {cls.__name__} found with uid {request.form.get('uid')}!", status=404)
|
||
|
else:
|
||
|
obj: Final = cls()
|
||
|
|
||
|
relationship_attach_callbacks = list()
|
||
|
|
||
|
for name, value in cls.__dict__.items():
|
||
|
if name[0] == '_' or not name[0].islower():
|
||
|
continue
|
||
|
|
||
|
cls_name = value.__class__.__name__
|
||
|
convert_with = None
|
||
|
|
||
|
if cls_name == 'StringProperty':
|
||
|
convert_with = str
|
||
|
elif cls_name == 'DateProperty':
|
||
|
convert_with = datetime.fromisoformat
|
||
|
elif cls_name == 'IntegerProperty':
|
||
|
convert_with = int
|
||
|
elif cls_name == 'FloatProperty':
|
||
|
convert_with = float
|
||
|
elif cls_name == 'RelationshipDefinition':
|
||
|
if name not in request.form:
|
||
|
continue
|
||
|
|
||
|
target_cls = value._raw_class
|
||
|
# convert class string to class by loading it from module object
|
||
|
if isinstance(target_cls, str):
|
||
|
target_cls = getattr(cls.__module__, target_cls)
|
||
|
|
||
|
for val in request.form.getlist(name):
|
||
|
target = target_cls.nodes.first_or_none(uid=val)
|
||
|
if not target:
|
||
|
raise ApiError(f'Cannot find referenced object uid={val} of type {target_cls.__name__}!')
|
||
|
|
||
|
relationship_attach_callbacks.append(connect_callback(getattr(obj, name), target))
|
||
|
if not convert_with:
|
||
|
# skip this property
|
||
|
continue
|
||
|
|
||
|
if name in request.form:
|
||
|
setattr(obj, name, convert_with(request.form.get(name)))
|
||
|
|
||
|
return obj, lambda: [c() for c in relationship_attach_callbacks]
|
||
|
|
||
|
|
||
|
def func_api_obj(cls: Type):
|
||
|
"""basic method for handling /api/type calls"""
|
||
|
if request.method == 'GET':
|
||
|
return jsonify([p.json(include_relations=False) for p in cls.nodes.all()])
|
||
|
if request.method == 'POST':
|
||
|
p, attach_relationships = construct_object_from_request(cls)
|
||
|
p.save()
|
||
|
attach_relationships()
|
||
|
return jsonify(p.json())
|
||
|
|
||
|
|
||
|
def func_api_obj_id(cls: Type, uid: str):
|
||
|
if request.method == 'GET':
|
||
|
person = Person.nodes.get_or_none(uid=uid)
|
||
|
if not person:
|
||
|
return "Not found", 404
|
||
|
return jsonify(person.json())
|
||
|
if request.method == 'PUT':
|
||
|
p, attach_relationships = construct_object_from_request(cls, uid=uid)
|
||
|
p.save()
|
||
|
attach_relationships()
|
||
|
return jsonify(p.json())
|
||
|
|
||
|
|
||
|
def attach_to_flask(app: Flask):
|
||
|
@app.route('/api/person', methods=['POST', 'GET'])
|
||
|
def func_api_person():
|
||
|
return func_api_obj(Person)
|
||
|
|
||
|
@app.route('/api/person/<uid>', methods=['PUT', 'GET'])
|
||
|
def func_api_person_id(uid):
|
||
|
return func_api_obj_id(Person, uid)
|
||
|
|
||
|
@app.route('/api/event', methods=['POST', 'GET'])
|
||
|
def func_api_event():
|
||
|
return func_api_obj(Event)
|
||
|
|
||
|
@app.route('/api/event/<uid>', methods=['PUT', 'GET'])
|
||
|
def func_api_event_id(uid):
|
||
|
return func_api_obj_id(Event, uid)
|
||
|
|
||
|
@app.route('/api/place', methods=['POST', 'GET'])
|
||
|
def func_api_place():
|
||
|
return func_api_obj(Place)
|
||
|
|
||
|
@app.route('/api/place/<uid>', methods=['PUT', 'GET'])
|
||
|
def func_api_place_id(uid):
|
||
|
return func_api_obj_id(Place, uid)
|
||
|
|
||
|
|