def __init__(self, url, config, name): """ A Object which holds the connection to the database and arbitrary numbers of services. It works like a proxy for the request. It decides which service will be finally called by reading the requested url parts and calls the appropriate service method. In addition you can implement api wide behaviour like authorization be subclassing this class and adding some pre or post processing to the particular methods. :param url: The connection string which is used to let the api connect with the desired database. It must have the form as described here: http://docs.sqlalchemy.org/en/latest/core/engines.html :type url: str :param config: The config of the hosting pyramid application. :type config: pyramid.config.Configurator :param name: The name which is used internally as an identifier of the api, to make it selectable between other api's. This name must be unique all over the application. If not an error will be thrown on application start up. :type name: str :raises: LookupError """ connection_already_exists = False for key, value in config.registry.pyramid_rest_database_connections.iteritems(): if url in key: connection_already_exists = True self.connection = value if not connection_already_exists: self.connection = Connection(url) config.registry.pyramid_rest_database_connections[url] = self.connection self.services = {} if name not in config.registry.pyramid_rest_apis: config.registry.pyramid_rest_apis[name] = self else: log.error( "The Api-Object you created seems to already exist in the registry. It has to be unique at all. " "Couldn't be added. Sorry..." ) raise LookupError()
class Api(object): def __init__(self, url, config, name): """ A Object which holds the connection to the database and arbitrary numbers of services. It works like a proxy for the request. It decides which service will be finally called by reading the requested url parts and calls the appropriate service method. In addition you can implement api wide behaviour like authorization be subclassing this class and adding some pre or post processing to the particular methods. :param url: The connection string which is used to let the api connect with the desired database. It must have the form as described here: http://docs.sqlalchemy.org/en/latest/core/engines.html :type url: str :param config: The config of the hosting pyramid application. :type config: pyramid.config.Configurator :param name: The name which is used internally as an identifier of the api, to make it selectable between other api's. This name must be unique all over the application. If not an error will be thrown on application start up. :type name: str :raises: LookupError """ connection_already_exists = False for key, value in config.registry.pyramid_rest_database_connections.iteritems(): if url in key: connection_already_exists = True self.connection = value if not connection_already_exists: self.connection = Connection(url) config.registry.pyramid_rest_database_connections[url] = self.connection self.services = {} if name not in config.registry.pyramid_rest_apis: config.registry.pyramid_rest_apis[name] = self else: log.error( "The Api-Object you created seems to already exist in the registry. It has to be unique at all. " "Couldn't be added. Sorry..." ) raise LookupError() def add_service(self, service): """ Add's a service to the api. :param service: The service which should be added to the api. :type service: Service :raises: LookupError """ if service.name not in self.services: self.services[service.name] = service else: log.error( "The Service {name} was defined for this API already. Use the defined one.".format( name=service.name ) ) raise LookupError() def provide_session(self, request): """ This method provides a usable SQLAlchemy session instance. It is ensured, that this session is doomed independent from the behavior of the request (it installs a finished listener to the request) :param request: The request of the pyramid web framework :type request: Request :return: a usable instance of a SQLAlchemy Session :rtype : Session """ session_instance = self.connection.session() inner_scoped_session = self.connection.session def cleanup(request): if request.exception is None: transaction.commit() else: transaction.abort() inner_scoped_session.remove() request.add_finished_callback(cleanup) return session_instance def find_service_by_definition(self, schema_name, table_name): """ Little helper method to obtain a service from the api's service list by it's unique schema+table name combination. :param schema_name: str :param table_name: str :return: Service or None :rtype: Service """ return self.services.get(Service.name_from_definition(schema_name, table_name)) def find_service_by_request(self, request): """ Little helper method to scrabble the requested service directly from the url which was requested. :param request: The request which comes all the way through the application from the client :type request: pyramid.request.Request :return: The service. :rtype: Service :raises: HTTPNotFound """ schema_name = request.matchdict['schema_name'] table_name = request.matchdict['table_name'] service = self.find_service_by_definition(schema_name, table_name) if service is None: text = 'Service with schema {schema_name} and table {table_name} could not be found.'.format( schema_name=schema_name, table_name=table_name ) log.error(text) raise HTTPNotFound( detail=text ) return service def read(self, request): """ The api wide method to receive the read request and passing it to the correct service. At this point it is possible to implement some post or pre processing by overwriting this method. The most common use case for this will be the implementation of an authorisation mechanism which has influence on the whole api. To have influence on special services please see the service class implementations read method. :param request: The request which comes all the way through the application from the client :type request: pyramid.request.Request :return: An pyramid response object :rtype: pyramid.response.Response """ return self.find_service_by_request(request).read( request, self.provide_session(request) ) def show(self, request): """ The api wide method to receive the show request and passing it to the correct service. At this point it is possible to implement some post or pre processing by overwriting this method. The most common use case for this will be the implementation of an authorisation mechanism which has influence on the whole api. To have influence on special services please see the service class implementations read method. :param request: The request which comes all the way through the application from the client :type request: pyramid.request.Request :return: An pyramid response object :rtype: pyramid.response.Response """ return self.find_service_by_request(request).show( request, self.provide_session(request) ) def create(self, request): """ The api wide method to receive the create request and passing it to the correct service. At this point it is possible to implement some post or pre processing by overwriting this method. The most common use case for this will be the implementation of an authorisation mechanism which has influence on the whole api. To have influence on special services please see the service class implementations read method. :param request: The request which comes all the way through the application from the client :type request: pyramid.request.Request :return: An pyramid response object :rtype: pyramid.response.Response """ return self.find_service_by_request(request).create( request, self.provide_session(request) ) def delete(self, request): """ The api wide method to receive the delete request and passing it to the correct service. At this point it is possible to implement some post or pre processing by overwriting this method. The most common use case for this will be the implementation of an authorisation mechanism which has influence on the whole api. To have influence on special services please see the service class implementations read method. :param request: The request which comes all the way through the application from the client :type request: pyramid.request.Request :return: An pyramid response object :rtype: pyramid.response.Response """ return self.find_service_by_request(request).delete( request, self.provide_session(request) ) def update(self, request): """ The api wide method to receive the update request and passing it to the correct service. At this point it is possible to implement some post or pre processing by overwriting this method. The most common use case for this will be the implementation of an authorisation mechanism which has influence on the whole api. To have influence on special services please see the service class implementations read method. :param request: The request which comes all the way through the application from the client :type request: pyramid.request.Request :return: An pyramid response object :rtype: pyramid.response.Response """ return self.find_service_by_request(request).update( request, self.provide_session(request) ) def model(self, request): """ The api wide method to receive the model request and passing it to the correct service. At this point it is possible to implement some post or pre processing by overwriting this method. The most common use case for this will be the implementation of an authorisation mechanism which has influence on the whole api. To have influence on special services please see the service class implementations read method. :param request: The request which comes all the way through the application from the client :type request: pyramid.request.Request :return: An pyramid response object :rtype: pyramid.response.Response """ return self.find_service_by_request(request).model( request )