# Copyright (C) 2017-2018 Lars Wirzenius # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . import ick2 class APIbase: def __init__(self, state): assert state is None or isinstance(state, ick2.FilePersistentState) self._trans = ick2.TransactionalState(state) def get_routes(self, path): resource_path = '{}/'.format(path) return [ { 'method': 'POST', 'path': path, 'callback': self.POST(self.create), }, { 'method': 'GET', 'path': path, 'callback': self.GET(self.list), }, { 'method': 'GET', 'path': resource_path, 'callback': self.GET(self.show), }, { 'method': 'PUT', 'path': resource_path, 'callback': self.PUT(self.update), }, { 'method': 'DELETE', 'path': resource_path, 'callback': self.DELETE(self.delete), }, ] def GET(self, callback): def wrapper(content_type, body, **kwargs): ick2.log.log( 'trace', msg_text='GET called', kwargs=kwargs, content_type=content_type, body=body) try: if 'raw_uri_path' in kwargs: del kwargs['raw_uri_path'] body = callback(**kwargs) except ick2.ParametersMissing as e: ick2.log.log('error', msg_text=str(e), kwargs=kwargs) return ick2.conflict(str(e)) except ick2.NotFound as e: ick2.log.log( 'error', msg_text='GET Not found', kwargs=kwargs, exc_info=True) return ick2.not_found(e) if isinstance(body, dict): return ick2.OK(body) if isinstance(body, str): return ick2.text_plain(body) raise Exception('this must not happen') return wrapper def POST(self, callback): def wrapper(content_type, body, **kwargs): ick2.log.log( 'trace', msg_text='POST called', kwargs=kwargs, content_type=content_type, body=body) try: body = callback(body, **kwargs) except ick2.ExistsAlready as e: ick2.log.log('error', msg_text=str(e), kwargs=kwargs) return ick2.conflict(str(e)) return ick2.created(body) return wrapper def PUT(self, callback): def wrapper(content_type, body, **kwargs): ick2.log.log( 'trace', msg_text='PUT called', kwargs=kwargs, content_type=content_type, body=body) if 'raw_uri_path' in kwargs: del kwargs['raw_uri_path'] try: body = callback(body, **kwargs) except ick2.NotFound as e: ick2.log.log( 'warning', msg_text='PUT Not found', kwargs=kwargs) return ick2.not_found(e) ick2.log.log('trace', msg_text='returned body', body=repr(body)) return ick2.OK(body) return wrapper def DELETE(self, callback): def wrapper(content_type, body, **kwargs): ick2.log.log( 'trace', msg_text='DELETE called', kwargs=kwargs, content_type=content_type, body=body) try: if 'raw_uri_path' in kwargs: del kwargs['raw_uri_path'] body = callback(**kwargs) except ick2.NotFound as e: ick2.log.log( 'warning', msg_text='DELETE Not found', kwargs=kwargs) return ick2.not_found(e) return ick2.OK(body) return wrapper def create(self, body, **kwargs): raise NotImplementedError() def update(self, body, name, **kwargs): raise NotImplementedError() def delete(self, name, **kwargs): raise NotImplementedError() def list(self, **kwargs): raise NotImplementedError() def show(self, name, **kwargs): raise NotImplementedError() class ResourceApiBase(APIbase): def __init__(self, type_name, state): super().__init__(state) self._type_name = type_name def list(self, **kwargs): resources = self._trans.get_resources(self._type_name) return { self._type_name: [r.as_dict() for r in resources] } def show(self, name, **kwargs): return self._trans.get_resource(self._type_name, name).as_dict() def create(self, body, **kwargs): ick2.log.log( 'trace', msg_text='create resource', resource_type=self._type_name, body=body, kwargs=kwargs) as_dict = self.mangle_new_resource(body) rid = self.get_resource_name(as_dict) if self._trans.has_resource(self._type_name, rid): raise ick2.ExistsAlready(rid) with self._trans.new(self._type_name, rid) as resource: resource.from_dict(as_dict) return as_dict def mangle_new_resource(self, resource): # pragma: no cover return resource def get_resource_name(self, resource): # pragma: no cover raise NotImplementedError() def update(self, body, name, **kwargs): rid = self.get_resource_name(body) if not self._trans.has_resource(self._type_name, rid): raise ick2.NotFound(kind=self._type_name, rid=rid) with self._trans.modify(self._type_name, rid) as resource: as_dict = self.mangle_updated_resource(resource.as_dict(), body) resource.from_dict(as_dict) return as_dict def mangle_updated_resource(self, old, new): # pragma: no cover return new def delete(self, name, **kwargs): if not self._trans.has_resource(self._type_name, name): raise ick2.NotFound(kind=self._type_name, rid=name) self._trans.remove_resource(self._type_name, name)