class User(Model, MongoRepository, Service, IdentityMixin): id = Property(str, required=True, generator=create_uuid_generator('U')) name = Property(str, required=True, validators=[NotEmpty, Regexp('[A-Za-z0-9-_]')], index=UniqueIndex) password = Property(str, required=True, validators=[NotEmpty], converter=content_hasher(rounds=10), omit=True) description = Property(str, index=TextIndex) roles = Property(list, sub_type=str) created = Property(datetime, required=True, validators=[Past], generator=date_now_generator) last_login = Property(datetime, marshaller=TimestampMarshaller) sequence = Property(int, index=Index) @link(rel='change_password', http_method='POST', require=[CurrentSubject(), Role('admin')]) def change_p(self, current_password, new_password): if not pbkdf2_sha256.verify(current_password, self.password): raise ServiceException(403, _('Current password is not correct')) else: self.password = new_password self.save() return _('Password changed') @link(require=Anonymous()) def get_description(self): return self.description
class PaymentService(object): @resource(method='POST', require=[Role('user')]) def authorise(self, payload): print(f'\n--> received as payload: {payload}\n') self.sink(payload) return payload @resource(method='POST', path='/authorise/form', require=[Role('user')]) def authorise_payment(self, product_id, card_number, amount): print( f'\n--> received as payload: {product_id} / {card_number} / {amount}\n' ) self.sink(product_id, card_number, amount) return {'authorisation_id': 'xxx-yyy-zzz'} @resource(method='GET', path='./<authorisation_id>', require=[Role('user')]) def check_status(self, authorisation_id): if hasattr(self, 'sink'): self.sink(authorisation_id) return {'id': authorisation_id, 'status': 'OK'} @resource(method='GET', query_params=['start', 'stop'], require=[Role('user')]) def list_payments(self, start=None, stop=None): self.sink(start, stop) return {'start': start, 'stop': stop} @resource(method='GET', path='./multiple/<authorisation_id>', query_params=['start', 'stop'], require=[Role('user')]) def check_multiple_status(self, authorisation_id, start=None, stop=None): self.sink(authorisation_id, start, stop) return {'id': authorisation_id, 'start': start, 'stop': stop} @resource(method='DELETE', path='./<authorisation_id>', require=[Role('user')]) def reverse(self, authorisation_id): self.sink(authorisation_id) return {'id': authorisation_id, 'status': 'OK'} @resource(method='DELETE', path='/cancel/<payment_ids>', require=[Role('user')]) def delete_many(self, payment_ids): self.sink(payment_ids) return {'deleted': [pid for pid in payment_ids.split(',')]} @resource(method='PUT', path='./<payment_ids>', require=[Role('user')]) def blow(self, payment_ids): raise AppKernelException('throwing some custom exception')
def setup_function(function): """ executed before each method call """ print('\n\nSETUP ==> ') global flask_app global kernel flask_app = Flask(__name__) flask_app.config['SECRET_KEY'] = 'S0m3S3cr3tC0nt3nt!' flask_app.testing = True kernel = AppKernelEngine('test_app', app=flask_app, cfg_dir='{}/../'.format(current_file_path()), development=True) kernel.enable_security() kernel.register(payment_service).require(Role('admin'), methods=['GET', 'PUT', 'POST', 'PATCH', 'DELETE']) User.delete_all()
class User(Model, MongoRepository, Service): id = Property(str) name = Property(str, required=True, index=UniqueIndex) email = Property(str, validators=[Email], index=UniqueIndex) password = Property(str, validators=[Regexp('(?=.{8,})')], converter=content_hasher(), omit=True) roles = Property(list, sub_type=str, default_value=['Login']) @link(http_method='POST', require=[CurrentSubject(), Role('admin')]) def change_password(self, current_password, new_password): if not pbkdf2_sha256.verify(current_password, self.password): raise ServiceException(403, _('Current password is not correct')) else: self.password = new_password self.save() return _('Password changed') @link(require=Anonymous()) def get_description(self): return self.description
class ShippingService(object): def ship(self, address, products): print(f'request shipping to: {address} | products: {products}') return str(uuid.uuid4()) @resource(method='POST', path='./', require=[Role('user')]) def shipping_request(self, request: Shipping): code, reservation = client.wrap( f'/reservations/{request.reservation_id}/commit').patch() if code == 200: tracking_id = self.ship(request.delivery_address, reservation.products) code, reservation = client.wrap( f'/reservations/{request.reservation_id}/execute').patch( {'tracking_id': tracking_id}) return reservation else: msg = reservation.get('message') if hasattr( reservation, 'message') else 'Error while calling reservation service.' return create_custom_error(code, msg, upstream_service='ShippingService')
class PaymentService(object): @resource(http_method='POST', require=[Role('user')]) def authorize(self, authorisation: AuthorisationRequest): assert authorisation is not None return {'auth_id': str(uuid.uuid4())}
def default_config(): user_service = kernel.register(User, methods=['GET', 'PUT', 'POST', 'PATCH', 'DELETE']) user_service.deny_all().require(Role('user'), methods='GET').require(Role('admin'), methods=['PUT', 'POST', 'PATCH', 'DELETE']) return create_basic_user()
class Reservation(Model, MongoRepository): id = Property(str, generator=create_uuid_generator('R')) order_id = Property(str, required=True) order_date = Property(datetime, required=True, generator=date_now_generator) products = Property(list, sub_type=Product, required=True, validators=NotEmpty()) state = Property(ReservationState, required=True, default_value=ReservationState.RESERVED) stocks = Property(dict, sub_type=dict) tracking_id = Property(str) def group_products_by_code(self): products_by_code = dict() for product in self.products: size_and_quantity = products_by_code.get(product.code, {product.size.name: 0}) size_and_quantity[product.size.name] = size_and_quantity.get(product.size.name, 0) + 1 products_by_code[product.code] = size_and_quantity return products_by_code @classmethod def before_post(cls, *args, **kwargs): # method called before the reservation id is sent for pcode, size_and_quantity in kwargs['model'].group_products_by_code().items(): for psize, quantity in size_and_quantity.items(): # todo: what if there are multiple stock items with the same product code query = Stock.where( (Stock.product.code == pcode) & (Stock.product.size == psize) & (Stock.available >= quantity)) reserved_stock = query.find_one_and_update(available=Stock.available - quantity, reserved=Stock.reserved + quantity) if not reserved_stock: raise ReservationException(f"There's no stock available for code: {pcode} and size: {psize}.") if hasattr(kwargs['model'], 'stocks') and kwargs['model'].stocks is not None: size_and_qty = kwargs['model'].stocks.get(pcode, {psize: {'qty': quantity}}) if psize not in size_and_qty: size_and_qty[psize] = {'qty': quantity} else: size_and_qty = {psize: {'qty': quantity}} kwargs['model'].stocks = {} size_and_qty[psize]['stock'] = reserved_stock.id kwargs['model'].stocks[pcode] = size_and_qty @classmethod def after_post(cls, *args, **kwargs): reservation: Reservation = kwargs.get('model') print(f'reservation id: {reservation.id}') @action(method='PATCH', require=[Role('user')]) def commit(self): self.state = ReservationState.COMMITTED self.save() return self @action(method='PATCH', require=[Role('user')]) def execute(self, tracking_id): self.state = ReservationState.EXECUTED self.tracking_id = tracking_id self.save() for pcode, sizes in self.stocks.items(): for size, quantity_and_stock in sizes.items(): stock_id = quantity_and_stock.get('stock') query = Stock.where(Stock.id == stock_id) modified_count = query.update_one(reserved=Stock.reserved - quantity_and_stock.get('qty')) if modified_count != 1: config.app_engine.logger.warn(f'quantities were not reduced for stock: {stock_id}') return self @action(method='PATCH', require=[Role('user')]) def cancel(self): error = False for pcode, sizes in self.stocks.items(): for size, quantity_and_stock in sizes.items(): stock_id = quantity_and_stock.get('stock') query = Stock.where(Stock.id == stock_id) qty = quantity_and_stock.get('qty') modified_count = query.update_one(reserved=Stock.reserved - qty, available=Stock.available + qty) if modified_count != 1: error = True config.app_engine.logger.warn(f'quantities were not reduced for stock: {stock_id}') if not error: self.state = ReservationState.CANCELLED self.save() return self