def create_app(database_uri): """ Creates a new flask app that exposes the database provided as a ReSTful Application :param str|unicode|sqlalchemy.engine.url.URL database_uri: The database URI in a manner that SQLAlchemy can understand :return: A flask app that exposes a database as a ReSTful API that can be accessed using either the Hal or SIREN protocol :rtype: Flask """ # Create the flask application app = Flask(__name__) # Setup SQLAlchemy to reflect the database engine = create_engine(database_uri) base = automap_base() base.prepare(engine, reflect=True) # Create the ripozo dispatcher and register the response formats dispatcher = FlaskDispatcher(app) dispatcher.register_adapters(adapters.HalAdapter, adapters.SirenAdapter) session_handler = ScopedSessionHandler(engine) # Create and register resources from the sqlalchemy models # We need to pass ``append_slash=True`` due to a quirk in how flask handles routing resources = [create_resource(model, session_handler, append_slash=True) for model in base.classes] dispatcher.register_resources(*resources) return app
def setUp(self): self.flask_app = Flask(__name__) self.url_prefix = '/testing/ripozo/api/v1.0' self.host = 'http://localhost' self.app = self.flask_app.test_client() self.dispatcher = FlaskDispatcher(self.flask_app, url_prefix=self.url_prefix) self.dispatcher.register_adapters(adapters.HalAdapter)
class FlaskBasketTest(unittest.TestCase): def setUp(self): self.flask_app = Flask(__name__) self.url_prefix = '/testing/ripozo/api/v1.0' self.host = 'http://localhost' self.app = self.flask_app.test_client() self.dispatcher = FlaskDispatcher(self.flask_app, url_prefix=self.url_prefix) self.dispatcher.register_adapters(adapters.HalAdapter) def get_rule_list(self): return [[each.rule, each.endpoint, each.methods] for each in self.app.application.url_map.iter_rules()] def test_hypermedia_shopping_basket(self): """ A hypermedia example that simulates a shopping basket with relatingships. The basket is a 'collection' while it contains individual items. First Verification: The collection has a trailing '/', the items don't. Second Assertion: The created links are callable. :return: """ class Hierarchy(): @classproperty def base_url(cls): """ Gets the base_url for the resource This is prepended to all routes indicated by an apimethod decorator. :return: The base_url for the resource(s) :rtype: unicode """ pks = cls.pks[-1:] or [] parts = ['<{0}>'.format(pk) for pk in pks] base_url = join_url_parts(cls.base_url_sans_pks, *parts).strip('/') return '/{0}'.format( base_url) if not cls.append_slash else '/{0}/'.format( base_url) @classproperty def base_url_sans_pks(cls): """ A class property that returns the base url without the pks. This is just the /{namespace}/{resource_name} For example if the _namespace = '/api' and the _resource_name = 'resource' this would return '/api/resource' regardless if there are pks or not. :return: The base url without the pks :rtype: unicode """ pks = cls.pks[:-1] or [ ] #only use the last primary key element parts = ['<{0}>'.format(pk) for pk in pks] base_url = join_url_parts(cls.namespace, cls.resource_name).lstrip('/') base_url = join_url_parts(base_url, *parts).strip('/') return '/{0}/'.format( base_url) if not cls.append_slash else '/{0}/'.format( base_url) # # # class Shop(Hierarchy, restmixins.RetrieveList, restmixins.Create): # """ # The shopping basket which will contain the items # # Retrieve: Collection of baskets # Create: New selection into existing or new basket # """ # manager = BasketManager # resource_name = 'basket' # # pks = ('basket_key',) # append_slash = True # # _relationships = ( # Relationship('basket_relation', relation='Basket', property_map=dict(basket_key='basket_key', relation_key='relation_key')), # ) # class BasketCollection(restmixins.RetrieveList): manager = BasketManager resource_name = 'basket' append_slash = True _relationships = (ListRelationship( 'baskets_relation', relation='Basket', property_map=dict(basket='basket')), ) class Basket(Hierarchy, restmixins.Create, restmixins.RetrieveList): """ The shopping basket which will contain the items Retrieve: Collection of baskets Create: New selection into existing or new basket """ manager = BasketManager resource_name = 'basket' pks = ('basket_key', ) append_slash = True _relationships = ( ListRelationship('item_relations', relation='ItemSelection', property_map=dict( basket='basket', basket_key='basket_key', relation_key='relation_key')), ListRelationship('object', relation='Item'), ) class ItemSelection(Hierarchy, restmixins.CreateRetrieve): """ The link between an Item object and the shopping basket 'The item is selected to be in this basket' Retrieve: Collection of selection items in basket """ manager = BasketManager resource_name = 'basket' pks = ( 'basket_key', 'relation_key', ) # Forced to use property map, otherwise the url is not properly built as the attribute is removed from # the list of attributes before the url is built. # The following does not work: # _relationships = (Relationship('basketid', relation='Basket'),) _relationships = ( Relationship('basket_relation', relation='Basket', property_map=dict(basket_key='basket_key')), # Relationship('item_relation', relation='Item') ) append_slash = False # For clarity - this is the default class Item(restmixins.Retrieve): resource_name = 'item' pks = ('object_id') self.dispatcher.register_resources(BasketCollection) self.dispatcher.register_resources(Basket) self.dispatcher.register_resources(ItemSelection) rules = self.get_rule_list() rule_list = [x[0] for x in rules] # Verify that the rules are created correctly self.assertIn('/testing/ripozo/api/v1.0/basket/', rule_list, 'The collection of baskets rule has a trailing slash') self.assertIn( '/testing/ripozo/api/v1.0/basket/<basket_key>/', rule_list, 'A specific basket is a collection of items and has a trailing slash' ) self.assertIn( '/testing/ripozo/api/v1.0/basket/<basket_key>/<relation_key>', rule_list, 'A specific item does not have a trailing slash') # Create a new basket and add/select new item 987 # Returns the created selection object data = json.dumps({"object_key": '987'}) rv_post_new_basket = self.app.post(self.url_prefix + '/basket/', data=data, content_type='application/json') self.assertEqual('201 CREATED', rv_post_new_basket.status, 'Successfully Created?') return_basket_data = rv_post_new_basket.data.decode('utf-8') # Parse return data and extract 'basket relation' and 'self' link of the new item return_basket_object = json.loads(return_basket_data) basket_link = return_basket_object['_links']['self']['href'] basket_item_link = return_basket_object['_links']['item_relations'][0][ 'href'] # The links must be valid calls rv_retrieve_item = self.app.get( basket_item_link) # Call GET with the returned item selection link rv_retrieve_item_object = json.loads( rv_retrieve_item.data.decode('utf-8')) print("", flush=True) rv_retrieve_basket = self.app.get( basket_link) # Call GET with the returned basket link rv_retrieve_basket_object = json.loads( rv_retrieve_basket.data.decode('utf-8')) print("", flush=True) self.assertEqual(self.host + self.url_prefix + '/basket/1/', basket_link) self.assertEqual(self.host + self.url_prefix + '/basket/1/1', basket_item_link) self.assertEqual('200 OK', rv_retrieve_item.status) # Now post a second relation into the same basket data = json.dumps({"object_key": '556'}) rv_post_second_relation = self.app.post( basket_link, data=data, content_type='application/json') rv_test = self.app.get(self.host + self.url_prefix + '/basket/') print("POST return:") pprint(json.loads(return_basket_data)) rv_retrieve_basket = self.app.get( basket_link) # Call GET with the returned basket link rv_retrieve_basket_object = json.loads( rv_retrieve_basket.data.decode('utf-8')) print(rv_retrieve_basket_object)
import os from importlib import import_module from ripozo import apimethod, ResourceBase from opbeat.contrib.flask import Opbeat from flask import Flask, json, jsonify, abort, request from flask.ext.cache import Cache from flask_ripozo import FlaskDispatcher from utils import CustomJSONEncoder, JSONRipozoAdapter app = Flask(__name__, instance_relative_config=True) dispatcher = FlaskDispatcher(app, url_prefix='/v1') dispatcher.register_adapters(JSONRipozoAdapter) app.root_dir = os.path.dirname(os.path.abspath(__file__)) app.data_dir = os.path.join(app.root_dir, 'data') app.json_encoder = CustomJSONEncoder app.static_url_path = os.path.join(app.root_dir, 'templates') cache = Cache(app, config={'CACHE_TYPE': 'simple'}) opbeat = Opbeat(app, organization_id=os.environ.get('OPBEAT_ORG_ID'), app_id=os.environ.get('OPBEAT_APP_ID'), secret_token=os.environ.get('OPBEAT_SECRET')) __version__ = '1.0.3'
class FlaskAppTest(unittest.TestCase): def setUp(self): # create our little application :) self.flask_app = Flask(__name__) self.url_prefix = '/testing/ripozo/api/v1.0' self.host = 'http://localhost' self.app = self.flask_app.test_client() self.dispatcher = FlaskDispatcher(self.flask_app, url_prefix=self.url_prefix) self.dispatcher.register_adapters(adapters.HalAdapter) def get_rule_list(self): return [each.rule for each in self.app.application.url_map.iter_rules()] # url_list = [] # for rule in self.app.application.url_map.iter_rules(): # url_list.append(rule.rule) # return url_list def test_append_slash(self): """ Verify that the 'append_slash' attribute works as expected. :return: """ class WithSlash(restmixins.Retrieve): manager = Manager resource_name = 'withslash' pks = ('withslash_is',) append_slash = True self.dispatcher.register_resources(WithSlash) class NoSlash(restmixins.RetrieveList): manager = Manager resource_name = 'noslash' pks = ('noslash_is',) append_slash = False self.dispatcher.register_resources(NoSlash) rule_list = self.get_rule_list() print(rule_list) self.assertIn('/testing/ripozo/api/v1.0/withslash/<withslash_is>/', rule_list) self.assertIn('/testing/ripozo/api/v1.0/noslash/<noslash_is>', rule_list) def test_relation_name_property(self): """ Dict lookup issue - this fails if the name of the relationship also exists as property. """ class MyResource(restmixins.Create): manager = Manager resource_name = 'myresource' pks = ('basketid',) _relationships = ( Relationship('value_1', relation='SomethingElse'), ) class SomethingElse(restmixins.Retrieve): manager = Manager resource_name = 'somethingelse' pks = ('ste_id',) self.dispatcher.register_resources(MyResource) self.dispatcher.register_resources(SomethingElse) rv_post = self.app.post(self.url_prefix+'/myresource', data=json.dumps({}), content_type='application/json') self.assertEqual('201 CREATED', rv_post.status) def test_relation_missing_pk(self): """ Expected to fail explicitly when pk is not found """ class MyResource(restmixins.Create): manager = Manager resource_name = 'myresource' pks = ('basketid',) _relationships = ( Relationship('get_another_object', relation='SomethingElse', property_map=dict(value_1='value_1_translated')), ) class SomethingElse(restmixins.Retrieve): manager = Manager resource_name = 'somethingelse' pks = ('value_axx',) self.dispatcher.register_resources(MyResource) self.dispatcher.register_resources(SomethingElse) rv_post = self.app.post(self.url_prefix+'/myresource', data=json.dumps({}), content_type='application/json') print(rv_post) obj = json.loads(rv_post.data) print(obj) print(obj['_links']) def test_relation_missing_relation(self): """ Expected to fail explicitly when related resource is not registered :return: """ class ItemSelection(restmixins.Retrieve): manager = Manager resource_name = 'basket' _relationships = (Relationship('testtesttest', relation='MissingObject'),) with self.assertRaises(KeyError): self.dispatcher.register_resources(ItemSelection) def test_relation_same_as_pk(self): """ A key should be usable both for the relation as for the primary key """ class MyResource(restmixins.Create): manager = Manager resource_name = 'myresource' pks = ('basketid',) _relationships = ( Relationship('get_another_object', relation='SomethingElse', property_map=dict(value_1='value_1_translated')), ) class SomethingElse(restmixins.Retrieve): manager = Manager resource_name = 'somethingelse' pks = ('value_axx',) self.dispatcher.register_resources(MyResource) self.dispatcher.register_resources(SomethingElse) rv_post = self.app.post(self.url_prefix+'/myresource', data=json.dumps({}), content_type='application/json') print(rv_post) obj = json.loads(rv_post.data) print(obj) print(obj['_links']) def test_hypermedia_relation_pk(self): class BasketFails(restmixins.Retrieve, restmixins.Create): manager = Manager resource_name = 'basket' pks = ('basketid',) _relationships = ( Relationship('basketid', relation='Item'), ) append_slash = False # Just for testing, so as not to get hit by the other issue class BasketWorks(restmixins.Retrieve, restmixins.Create): manager = Manager resource_name = 'basket' pks = ('basketid',) _relationships = ( Relationship('basket_relation', relation='Item', property_map=dict(basket_relation='basketid')), ) append_slash = True # Just for testing, so as not to get hit by the other issue class Item(restmixins.Retrieve): manager = Manager resource_name = 'basket' pks = ('basketid', 'itemid',) self.dispatcher.register_resources(BasketWorks) self.dispatcher.register_resources(Item) rule_list = self.get_rule_list() print(rule_list) # Create a new basket and add/select new item 987 # Returns the created selection object data = json.dumps({"item": '987'}) rv_post_new_basket = self.app.post(self.url_prefix+'/basket/', data=data, content_type='application/json') return_data = rv_post_new_basket.data.decode('utf-8') # Parse return data and extract 'basket relation' and 'self' link of the new item return_data_object = json.loads(return_data) print(return_data_object) def test_hypermedia_shopping_basket(self): """ A hypermedia example that simulates a shopping basket with relatingships. The basket is a 'collection' while it contains individual items. First Verification: The collection has a trailing '/', the items don't. Second Assertion: The created links are callable. :return: """ class Basket(restmixins.Retrieve, restmixins.Create): """ The shopping basket which will contain the items Retrieve: Collection of baskets Create: New selection into existing or new basket """ manager = Manager resource_name = 'basket' pks = ('basketid',) append_slash = True _relationships = ( Relationship('basket_relation', relation='ItemSelection', property_map=dict(basketid='basketid', itemid='itemid')), ) class ItemSelection(restmixins.Retrieve): """ The link between an Item object and the shopping basket 'The item is selected to be in this basket' Retrieve: Collection of selection items in basket """ manager = Manager resource_name = 'basket' pks = ('basketid', 'itemid',) # Forced to use property map, otherwise the url is not properly built as the attribute is removed from # the list of attributes before the url is built. # The following does not work: # _relationships = (Relationship('basketid', relation='Basket'),) _relationships = ( Relationship('basket_relation2', relation='Basket', property_map=dict(basket_relation='basketid')), ) append_slash = False # For clarity - this is the default self.dispatcher.register_resources(Basket) self.dispatcher.register_resources(ItemSelection) rule_list = self.get_rule_list() # Verify that the rules are created correctly self.assertIn('/testing/ripozo/api/v1.0/basket/', rule_list, 'The collection of baskets rule has a trailing slash') self.assertIn('/testing/ripozo/api/v1.0/basket/<basketid>/', rule_list, 'A specific basket is a collection of items and has a trailing slash') self.assertIn('/testing/ripozo/api/v1.0/basket/<basketid>/<itemid>', rule_list, 'A specific item does not have a trailing slash') # Create a new basket and add/select new item 987 # Returns the created selection object data = json.dumps({"item": '987'}) rv_post_new_basket = self.app.post(self.url_prefix+'/basket/', data=data, content_type='application/json') self.assertEqual('201 CREATED', rv_post_new_basket.status, 'Successfully Created?') return_data = rv_post_new_basket.data.decode('utf-8') # Parse return data and extract 'basket relation' and 'self' link of the new item return_data_object = json.loads(return_data) basket_selection_link = return_data_object['_links']['basket_relation']['href'] basket_self_link = return_data_object['_links']['self']['href'] # The links must be valid calls rv_retrieve_basket = self.app.get(basket_self_link) # Call GET with the returned selection link rv_retrieve_basket_object = json.loads(rv_retrieve_basket.data.decode('utf-8')) print("", flush=True) rv_retrieve_selection = self.app.get(basket_selection_link) # Call GET with the returned selection link rv_retrieve_selection_object = json.loads(rv_retrieve_selection.data.decode('utf-8')) print("", flush=True) self.assertEqual(self.host+self.url_prefix+'/basket/123/', basket_self_link) self.assertEqual(self.host+self.url_prefix+'/basket/123/987', basket_selection_link) self.assertEqual('200 OK', rv_retrieve_basket.status) print("POST return:") pprint(json.loads(return_data))
from __future__ import absolute_import from __future__ import division from __future__ import print_function from __future__ import unicode_literals from flask import Flask from flask_ripozo import FlaskDispatcher from ripozo import restmixins, apimethod, translate, ListRelationship, Relationship, adapters from ripozo.resources.fields.common import IntegerField from ripozo_sqlalchemy import create_resource, ScopedSessionHandler from common.models import Post, Comment, engine app = Flask('easyapp') session_handler = ScopedSessionHandler(engine) dispatcher = FlaskDispatcher(app, url_prefix='/api') dispatcher.register_resources(PostResource, CommentResource) dispatcher.register_adapters(adapters.SirenAdapter, adapters.HalAdapter) def main(): app.run(debug=True) if __name__ == '__main__': main()
def create_ripozo(app): return FlaskDispatcher(app, url_prefix="/api")
from flask import Flask from flask_ripozo import FlaskDispatcher from ripozo.adapters import BasicJSONAdapter, HalAdapter from myapp.models import DB from myapp_ripozo.resources import UserResource, UserGroupResource, GroupResource app = Flask('myapp_ripozo') app.config['SQLALCHEMY_DATABASER_USER'] = '******' # In memory database DB.init_app(app) dispatcher = FlaskDispatcher(app, url_prefix='/api') dispatcher.register_adapters(HalAdapter, BasicJSONAdapter) dispatcher.register_resources(UserResource, UserGroupResource, GroupResource) if __name__ == '__main__': with app.app_context(): DB.create_all() app.run(host='0.0.0.0', debug=True, port=4000)
dict or None: Properties of the retrieved item or None if no such item found. """ resp = items_conn.stub.GetItem( items_pb2.GetItemRequest(id=lookup_keys['id'])) item = resp.item if item: return dict(id=item.id, name=item.name) def retrieve_list(self, filters, *args, **kwargs): raise NotImplementedError() def update(self, lookup_keys, updates, *args, **kwargs): raise NotImplementedError() class ItemResource(restmixins.Retrieve): """Standard ripozo resource that can be used by ripozo adapters. Handles requests and constructs resources to return as a request. """ manager = ItemManager() pks = 'id', resource_name = 'items' # register resources and valid response types _dispatcher = FlaskDispatcher(items) _dispatcher.register_adapters(JSONAPIAdapter) _dispatcher.register_resources(ItemResource)
paginate_by = 20 class TaskBoardResource(restmixins.CRUDL): manager = TaskBoardManager(session_handler) resource_name = "taskboard" pks = ("id",) _relationships = (ListRelationship("tasks", relation="TaskResource"),) @apimethod(route="/addtask", methods=["POST"]) def add_task(cls, request): body_args = request.body_args body_args["task_board_id"] = request.get("id") request.body_args = body_args return TaskResource.create(request) class TaskResource(restmixins.CRUD): manager = TaskManager(session_handler) resource_name = "task" pks = ("id",) _relationships = (Relationship("task_board", property_map=dict(task_board_id="id"), relation="TaskBoardResource"),) dispatcher = FlaskDispatcher(app, url_prefix="/api") dispatcher.register_resources(TaskBoardResource, TaskResource) dispatcher.register_adapters(adapters.SirenAdapter, adapters.HalAdapter, adapters.BasicJSONAdapter) if __name__ == "__main__": app.run(debug=True, port=8000)
app = Flask('helloworld') class HelloResource(ResourceBase): append_slash = True resource_name = 'hello' pks = ('name',) @apimethod(methods=['GET'], no_pks=True) def hello_world(cls, request): return cls(properties=dict(content='Hello World!'), no_pks=True) @translate(fields=[fields.StringField('name', required=True)], validate=True) @apimethod(methods=['GET']) def hello_name(cls, request): name = request.url_params.get('name') content = 'Hello {}'.format(name) return cls(properties=dict(content=content, name=name)) dispatcher = FlaskDispatcher(app) dispatcher.register_adapters(adapters.SirenAdapter, adapters.HalAdapter) dispatcher.register_resources(HelloResource) def main(): app.run(debug=True) if __name__ == '__main__': main()
from __future__ import absolute_import from __future__ import division from __future__ import print_function from __future__ import unicode_literals from flask import Flask from flask_ripozo import FlaskDispatcher from ripozo import apimethod, ResourceBase, adapters app = Flask(__name__) class MyResource(ResourceBase): resource_name = 'my_resource' @apimethod(route='/hello/') def hello_world(cls, request): return cls(properties=dict(hello='world')) dispatcher = FlaskDispatcher(app) dispatcher.register_resources(MyResource) dispatcher.register_adapters(adapters.HalAdapter) if __name__ == '__main__': app.run(port=6000)
def initialize_ripozo(app): dispatcher = FlaskDispatcher(app) dispatcher.register_adapters(SirenAdapter, JSONAPIAdapter) # dispatcher.register_resources() Simply pass in your resource classes here return dispatcher
from __future__ import absolute_import from __future__ import division from __future__ import print_function from __future__ import unicode_literals from flask import Flask from flask_ripozo import FlaskDispatcher from ripozo import adapters from full.resources import PostResource, CommentResource app = Flask('fullexample') dispatcher = FlaskDispatcher(app, url_prefix='/api') dispatcher.register_adapters(adapters.SirenAdapter, adapters.HalAdapter) dispatcher.register_resources(PostResource, CommentResource) def main(): app.run(debug=True) if __name__ == '__main__': main()
class FlaskAppTest(unittest.TestCase): def setUp(self): # create our little application :) self.flask_app = Flask(__name__) self.url_prefix = '/testing/ripozo/api/v1.0' self.host = 'http://localhost' self.app = self.flask_app.test_client() self.dispatcher = FlaskDispatcher(self.flask_app, url_prefix=self.url_prefix) self.dispatcher.register_adapters(adapters.HalAdapter) def get_rule_list(self): return [ each.rule for each in self.app.application.url_map.iter_rules() ] # url_list = [] # for rule in self.app.application.url_map.iter_rules(): # url_list.append(rule.rule) # return url_list def test_append_slash(self): """ Verify that the 'append_slash' attribute works as expected. :return: """ class WithSlash(restmixins.Retrieve): manager = Manager resource_name = 'withslash' pks = ('withslash_is', ) append_slash = True self.dispatcher.register_resources(WithSlash) class NoSlash(restmixins.RetrieveList): manager = Manager resource_name = 'noslash' pks = ('noslash_is', ) append_slash = False self.dispatcher.register_resources(NoSlash) rule_list = self.get_rule_list() print(rule_list) self.assertIn('/testing/ripozo/api/v1.0/withslash/<withslash_is>/', rule_list) self.assertIn('/testing/ripozo/api/v1.0/noslash/<noslash_is>', rule_list) def test_relation_name_property(self): """ Dict lookup issue - this fails if the name of the relationship also exists as property. """ class MyResource(restmixins.Create): manager = Manager resource_name = 'myresource' pks = ('basketid', ) _relationships = (Relationship('value_1', relation='SomethingElse'), ) class SomethingElse(restmixins.Retrieve): manager = Manager resource_name = 'somethingelse' pks = ('ste_id', ) self.dispatcher.register_resources(MyResource) self.dispatcher.register_resources(SomethingElse) rv_post = self.app.post(self.url_prefix + '/myresource', data=json.dumps({}), content_type='application/json') self.assertEqual('201 CREATED', rv_post.status) def test_relation_missing_pk(self): """ Expected to fail explicitly when pk is not found """ class MyResource(restmixins.Create): manager = Manager resource_name = 'myresource' pks = ('basketid', ) _relationships = (Relationship( 'get_another_object', relation='SomethingElse', property_map=dict(value_1='value_1_translated')), ) class SomethingElse(restmixins.Retrieve): manager = Manager resource_name = 'somethingelse' pks = ('value_axx', ) self.dispatcher.register_resources(MyResource) self.dispatcher.register_resources(SomethingElse) rv_post = self.app.post(self.url_prefix + '/myresource', data=json.dumps({}), content_type='application/json') print(rv_post) obj = json.loads(rv_post.data) print(obj) print(obj['_links']) def test_relation_missing_relation(self): """ Expected to fail explicitly when related resource is not registered :return: """ class ItemSelection(restmixins.Retrieve): manager = Manager resource_name = 'basket' _relationships = (Relationship('testtesttest', relation='MissingObject'), ) with self.assertRaises(KeyError): self.dispatcher.register_resources(ItemSelection) def test_relation_same_as_pk(self): """ A key should be usable both for the relation as for the primary key """ class MyResource(restmixins.Create): manager = Manager resource_name = 'myresource' pks = ('basketid', ) _relationships = (Relationship( 'get_another_object', relation='SomethingElse', property_map=dict(value_1='value_1_translated')), ) class SomethingElse(restmixins.Retrieve): manager = Manager resource_name = 'somethingelse' pks = ('value_axx', ) self.dispatcher.register_resources(MyResource) self.dispatcher.register_resources(SomethingElse) rv_post = self.app.post(self.url_prefix + '/myresource', data=json.dumps({}), content_type='application/json') print(rv_post) obj = json.loads(rv_post.data) print(obj) print(obj['_links']) def test_hypermedia_relation_pk(self): class BasketFails(restmixins.Retrieve, restmixins.Create): manager = Manager resource_name = 'basket' pks = ('basketid', ) _relationships = (Relationship('basketid', relation='Item'), ) append_slash = False # Just for testing, so as not to get hit by the other issue class BasketWorks(restmixins.Retrieve, restmixins.Create): manager = Manager resource_name = 'basket' pks = ('basketid', ) _relationships = (Relationship( 'basket_relation', relation='Item', property_map=dict(basket_relation='basketid')), ) append_slash = True # Just for testing, so as not to get hit by the other issue class Item(restmixins.Retrieve): manager = Manager resource_name = 'basket' pks = ( 'basketid', 'itemid', ) self.dispatcher.register_resources(BasketWorks) self.dispatcher.register_resources(Item) rule_list = self.get_rule_list() print(rule_list) # Create a new basket and add/select new item 987 # Returns the created selection object data = json.dumps({"item": '987'}) rv_post_new_basket = self.app.post(self.url_prefix + '/basket/', data=data, content_type='application/json') return_data = rv_post_new_basket.data.decode('utf-8') # Parse return data and extract 'basket relation' and 'self' link of the new item return_data_object = json.loads(return_data) print(return_data_object) def test_hypermedia_shopping_basket(self): """ A hypermedia example that simulates a shopping basket with relatingships. The basket is a 'collection' while it contains individual items. First Verification: The collection has a trailing '/', the items don't. Second Assertion: The created links are callable. :return: """ class Basket(restmixins.Retrieve, restmixins.Create): """ The shopping basket which will contain the items Retrieve: Collection of baskets Create: New selection into existing or new basket """ manager = Manager resource_name = 'basket' pks = ('basketid', ) append_slash = True _relationships = (Relationship('basket_relation', relation='ItemSelection', property_map=dict( basketid='basketid', itemid='itemid')), ) class ItemSelection(restmixins.Retrieve): """ The link between an Item object and the shopping basket 'The item is selected to be in this basket' Retrieve: Collection of selection items in basket """ manager = Manager resource_name = 'basket' pks = ( 'basketid', 'itemid', ) # Forced to use property map, otherwise the url is not properly built as the attribute is removed from # the list of attributes before the url is built. # The following does not work: # _relationships = (Relationship('basketid', relation='Basket'),) _relationships = (Relationship( 'basket_relation2', relation='Basket', property_map=dict(basket_relation='basketid')), ) append_slash = False # For clarity - this is the default self.dispatcher.register_resources(Basket) self.dispatcher.register_resources(ItemSelection) rule_list = self.get_rule_list() # Verify that the rules are created correctly self.assertIn('/testing/ripozo/api/v1.0/basket/', rule_list, 'The collection of baskets rule has a trailing slash') self.assertIn( '/testing/ripozo/api/v1.0/basket/<basketid>/', rule_list, 'A specific basket is a collection of items and has a trailing slash' ) self.assertIn('/testing/ripozo/api/v1.0/basket/<basketid>/<itemid>', rule_list, 'A specific item does not have a trailing slash') # Create a new basket and add/select new item 987 # Returns the created selection object data = json.dumps({"item": '987'}) rv_post_new_basket = self.app.post(self.url_prefix + '/basket/', data=data, content_type='application/json') self.assertEqual('201 CREATED', rv_post_new_basket.status, 'Successfully Created?') return_data = rv_post_new_basket.data.decode('utf-8') # Parse return data and extract 'basket relation' and 'self' link of the new item return_data_object = json.loads(return_data) basket_selection_link = return_data_object['_links'][ 'basket_relation']['href'] basket_self_link = return_data_object['_links']['self']['href'] # The links must be valid calls rv_retrieve_basket = self.app.get( basket_self_link) # Call GET with the returned selection link rv_retrieve_basket_object = json.loads( rv_retrieve_basket.data.decode('utf-8')) print("", flush=True) rv_retrieve_selection = self.app.get( basket_selection_link) # Call GET with the returned selection link rv_retrieve_selection_object = json.loads( rv_retrieve_selection.data.decode('utf-8')) print("", flush=True) self.assertEqual(self.host + self.url_prefix + '/basket/123/', basket_self_link) self.assertEqual(self.host + self.url_prefix + '/basket/123/987', basket_selection_link) self.assertEqual('200 OK', rv_retrieve_basket.status) print("POST return:") pprint(json.loads(return_data))
_relationships = ( ListRelationship('tasks', relation='TaskResource'), ) @apimethod(route='/addtask', methods=['POST']) def add_task(cls, request): body_args = request.body_args body_args['task_board_id'] = request.get('id') request.body_args = body_args return TaskResource.create(request) class TaskResource(restmixins.CRUD): manager = TaskManager(session_handler) resource_name = 'task' pks = ('id',) _relationships = ( Relationship( 'task_board', property_map=dict(task_board_id='id'), relation='TaskBoardResource' ), ) dispatcher = FlaskDispatcher(app, url_prefix='/api') dispatcher.register_adapters(SirenAdapter, HalAdapter) dispatcher.register_resources(TaskBoardResource, TaskResource) if __name__ == '__main__': app.run(debug=True)
def hello_world(cls, request): return cls( properties=dict(content='Hello World!', name=request.get('name'))) class RelatedResource(ResourceBase): append_slash = True resource_name = 'related' pks = ('name', ) @translate(fields=[fields.StringField('name', minimum=3, required=True)], validate=True) @apimethod(methods=['GET']) def hello_name(cls, request): name = request.url_params.get('name') content = 'Your name is {}'.format(name) return cls(properties=dict(content=content, name=name)) dispatcher = FlaskDispatcher(app) dispatcher.register_adapters(adapters.SirenAdapter, adapters.HalAdapter) dispatcher.register_resources(HelloResource, RelatedResource) def main(): app.run(debug=True) if __name__ == '__main__': main()
class FlaskBasketTest(unittest.TestCase): def setUp(self): self.flask_app = Flask(__name__) self.url_prefix = '/testing/ripozo/api/v1.0' self.host = 'http://localhost' self.app = self.flask_app.test_client() self.dispatcher = FlaskDispatcher(self.flask_app, url_prefix=self.url_prefix) self.dispatcher.register_adapters(adapters.HalAdapter) def get_rule_list(self): return [[each.rule, each.endpoint, each.methods] for each in self.app.application.url_map.iter_rules()] def test_hypermedia_shopping_basket(self): """ A hypermedia example that simulates a shopping basket with relatingships. The basket is a 'collection' while it contains individual items. First Verification: The collection has a trailing '/', the items don't. Second Assertion: The created links are callable. :return: """ class Hierarchy(): @classproperty def base_url(cls): """ Gets the base_url for the resource This is prepended to all routes indicated by an apimethod decorator. :return: The base_url for the resource(s) :rtype: unicode """ pks = cls.pks[-1:] or [] parts = ['<{0}>'.format(pk) for pk in pks] base_url = join_url_parts(cls.base_url_sans_pks, *parts).strip('/') return '/{0}'.format(base_url) if not cls.append_slash else '/{0}/'.format(base_url) @classproperty def base_url_sans_pks(cls): """ A class property that returns the base url without the pks. This is just the /{namespace}/{resource_name} For example if the _namespace = '/api' and the _resource_name = 'resource' this would return '/api/resource' regardless if there are pks or not. :return: The base url without the pks :rtype: unicode """ pks = cls.pks[:-1] or [] #only use the last primary key element parts = ['<{0}>'.format(pk) for pk in pks] base_url = join_url_parts(cls.namespace, cls.resource_name).lstrip('/') base_url = join_url_parts(base_url, *parts).strip('/') return '/{0}/'.format(base_url) if not cls.append_slash else '/{0}/'.format(base_url) # # # class Shop(Hierarchy, restmixins.RetrieveList, restmixins.Create): # """ # The shopping basket which will contain the items # # Retrieve: Collection of baskets # Create: New selection into existing or new basket # """ # manager = BasketManager # resource_name = 'basket' # # pks = ('basket_key',) # append_slash = True # # _relationships = ( # Relationship('basket_relation', relation='Basket', property_map=dict(basket_key='basket_key', relation_key='relation_key')), # ) # class BasketCollection(restmixins.RetrieveList): manager = BasketManager resource_name = 'basket' append_slash = True _relationships = ( ListRelationship('baskets_relation', relation='Basket', property_map=dict(basket='basket')), ) class Basket(Hierarchy, restmixins.Create, restmixins.RetrieveList): """ The shopping basket which will contain the items Retrieve: Collection of baskets Create: New selection into existing or new basket """ manager = BasketManager resource_name = 'basket' pks = ('basket_key',) append_slash = True _relationships = ( ListRelationship('item_relations', relation='ItemSelection', property_map=dict(basket='basket', basket_key='basket_key', relation_key='relation_key')), ListRelationship('object', relation='Item'), ) class ItemSelection(Hierarchy, restmixins.CreateRetrieve): """ The link between an Item object and the shopping basket 'The item is selected to be in this basket' Retrieve: Collection of selection items in basket """ manager = BasketManager resource_name = 'basket' pks = ('basket_key', 'relation_key',) # Forced to use property map, otherwise the url is not properly built as the attribute is removed from # the list of attributes before the url is built. # The following does not work: # _relationships = (Relationship('basketid', relation='Basket'),) _relationships = ( Relationship('basket_relation', relation='Basket', property_map=dict(basket_key='basket_key')), # Relationship('item_relation', relation='Item') ) append_slash = False # For clarity - this is the default class Item(restmixins.Retrieve): resource_name = 'item' pks = ('object_id') self.dispatcher.register_resources(BasketCollection) self.dispatcher.register_resources(Basket) self.dispatcher.register_resources(ItemSelection) rules = self.get_rule_list() rule_list = [x[0] for x in rules] # Verify that the rules are created correctly self.assertIn('/testing/ripozo/api/v1.0/basket/', rule_list, 'The collection of baskets rule has a trailing slash') self.assertIn('/testing/ripozo/api/v1.0/basket/<basket_key>/', rule_list, 'A specific basket is a collection of items and has a trailing slash') self.assertIn('/testing/ripozo/api/v1.0/basket/<basket_key>/<relation_key>', rule_list, 'A specific item does not have a trailing slash') # Create a new basket and add/select new item 987 # Returns the created selection object data = json.dumps({"object_key": '987'}) rv_post_new_basket = self.app.post(self.url_prefix+'/basket/', data=data, content_type='application/json') self.assertEqual('201 CREATED', rv_post_new_basket.status, 'Successfully Created?') return_basket_data = rv_post_new_basket.data.decode('utf-8') # Parse return data and extract 'basket relation' and 'self' link of the new item return_basket_object = json.loads(return_basket_data) basket_link = return_basket_object['_links']['self']['href'] basket_item_link = return_basket_object['_links']['item_relations'][0]['href'] # The links must be valid calls rv_retrieve_item = self.app.get(basket_item_link) # Call GET with the returned item selection link rv_retrieve_item_object = json.loads(rv_retrieve_item.data.decode('utf-8')) print("", flush=True) rv_retrieve_basket = self.app.get(basket_link) # Call GET with the returned basket link rv_retrieve_basket_object = json.loads(rv_retrieve_basket.data.decode('utf-8')) print("", flush=True) self.assertEqual(self.host+self.url_prefix+'/basket/1/', basket_link) self.assertEqual(self.host+self.url_prefix+'/basket/1/1', basket_item_link) self.assertEqual('200 OK', rv_retrieve_item.status) # Now post a second relation into the same basket data = json.dumps({"object_key": '556'}) rv_post_second_relation = self.app.post(basket_link, data=data, content_type='application/json') rv_test = self.app.get(self.host+self.url_prefix+'/basket/') print("POST return:") pprint(json.loads(return_basket_data)) rv_retrieve_basket = self.app.get(basket_link) # Call GET with the returned basket link rv_retrieve_basket_object = json.loads(rv_retrieve_basket.data.decode('utf-8')) print(rv_retrieve_basket_object)
class TaskBoardResource(restmixins.CRUDL): manager = TaskBoardManager(session_handler) resource_name = 'taskboard' pks = ('id', ) _relationships = (ListRelationship('tasks', relation='TaskResource'), ) @apimethod(route='/addtask', methods=['POST']) def add_task(cls, request): body_args = request.body_args body_args['task_board_id'] = request.get('id') request.body_args = body_args return TaskResource.create(request) class TaskResource(restmixins.CRUD): manager = TaskManager(session_handler) resource_name = 'task' pks = ('id', ) _relationships = (Relationship('task_board', property_map=dict(task_board_id='id'), relation='TaskBoardResource'), ) dispatcher = FlaskDispatcher(app, url_prefix='/api') dispatcher.register_adapters(SirenAdapter, HalAdapter) dispatcher.register_resources(TaskBoardResource, TaskResource) if __name__ == '__main__': app.run(debug=True)
def register_resources(app): dispatcher = FlaskDispatcher(app, url_prefix='/api') dispatcher.register_resources(UserResource, RelationshipResource, ResourceResource)