class BookingService: name = "booking_service" db = Database(Base) dispatcher = EventDispatcher() @http("POST", "/restaurants/<int:restaurant_id>") def book_table(self, request: Request, restaurant_id: int) -> Response: request_params = json.loads(request.data) command = BookTableCommand(restaurant_id=restaurant_id, persons=request_params["persons"]) booking_table_service = BookingTableApplicationService( SQLAlchemyRestaurantRepository(self.db.session), SQLAlchemyUnitOfWork(self.db.session), NamekoEventPublisher(self.dispatcher), ) booking_table_service.book_table(command) return Response(f"Restaurant: {restaurant_id} table booked") @http("GET", "/up") def up(self, request: Request) -> Response: return Response("I'm alive") @http("GET", "/restaurants") def restaurants(self, request: Request) -> Response: repo = SQLAlchemyRestaurantRepository(self.db.session) return Response( json.dumps([restaurant_serializer(r) for r in repo.all()]))
class LoginService: name = "auth_service" secret = "aYoXW26E7w3wiVOq4TnHGEkx0OB4cdHx" db = Database(Base) @staticmethod def create_token(): arr = bytearray(32) for i in range(0, 32): arr[i] = random.randint(0, 255) return base64.encodebytes(arr).decode().rstrip() @rpc @timeout(3) def login(self, username, password): try: rec = self.db.get_session().query(User).filter( User.username == username).one() if password == rec.password: payload = {"userId": rec.userid, "token": self.create_token()} return { "access_token": jwt.encode(payload, self.secret, algorithm="HS256").decode().rstrip() } else: raise NoResultFound() except NoResultFound: return {"error": {"code": 403, "message": "Invalid credentials"}}
class ProductsService: name = "products" db = Database(DeclarativeBase) @http("POST", "/products/") def create_product(self, request): payload = json.loads(request.get_data(as_text=True)) product = Product(**payload) with self.db.get_session() as session: session.add(product) return json.dumps({"id": product.id, "name": product.name})
class ExampleService(object): name = 'exampleservice' db = Database(DeclarativeBase) @dummy def write(self, id_, name): obj = User(id=id_, name=name) self.db.session.add(obj) self.db.session.commit() @dummy def read(self, id_): return self.db.session.query(User).get(id_).name
class ProjectService: name = 'project' config = Config() database = Database(DeclarativeBase) @rpc def get(self, project): logger.debug(project) _project = None with self.database.get_session() as session: _project = session.query(Project).get(project) if _project is None: raise NotFound('{} not found'.format(project)) return ProjectSchema().dump(_project)
class ExampleServiceWithDatabase: name = 'exampleservice' db = Database(DeclBase) @dummy def create_record(self): with self.db.get_session() as session: session.add(ExampleModel(data='hello')) @dummy @transaction_retry def get_record_count(self): with self.db.get_session() as session: return session.query(ExampleModel).count() @dummy def get_record_count_retry_inside(self): with self.db.get_session() as session: @transaction_retry(session=session) def foo(): return session.query(ExampleModel).count() return foo() @dummy def get_record_count_no_retry(self): with self.db.get_session() as session: return session.query(ExampleModel).count() @dummy @transaction_retry def create_without_context_manager(self): session = self.db.get_session() session.add(ExampleModel(data='created without context manager')) session.commit() @dummy @transaction_retry(session=operator.attrgetter('db.session')) def create_with_worker_scoped_session(self): self.db.session.add(ExampleModel(data='created in worker scope')) self.db.session.commit()
class ContactsService: name = 'contacts' db = Database(DeclarativeBase) dispatch = EventDispatcher() @rpc def get_contact(self, id_): with self.db.get_session() as session: contact = session.query(Contact).get(id_) return contact.to_dict() @rpc def create_contact(self, data): with self.db.get_session() as session: contact = Contact(**data) session.add(contact) self.dispatch('contact_created', {'contact': contact.to_dict()}) return contact.to_dict()
class LoginService: name = "auth_service" secret = settings["JWT_SECRET"] db = Database(Base) sentry = SentryReporter() @staticmethod def create_token(): arr = bytearray(32) for i in range(0, 32): arr[i] = random.randint(0, 255) return base64.encodebytes(arr).decode().rstrip() @rpc(expected_exceptions=InvalidCredentials) @timeout(3) def login(self, username, password): try: rec = self.db.get_session().query(User).filter( User.username == username).one() if password == rec.password: payload = {"userId": rec.userid, "token": self.create_token()} return { "access_token": jwt.encode(payload, self.secret, algorithm="HS256").decode().rstrip() } else: raise InvalidCredentials("Wrong password") except NoResultFound: raise InvalidCredentials("User not found") @event_handler("heartbeat_service", "heartbeat", handler_type=BROADCAST, reliable_delivery=False) def on_heartbeat(self, ts): print("Received Heartbeat with timestamp {}".format(ts), flush=True)
class TodoService: """ Service class that is instantiated by nameko to run the todo api """ name = "TodoService" db = Database(DeclBase) @http('POST', '/todo/add/') @transaction_retry() def add(self, request): """ Method for adding a new todo to the database via POST request Example request :: { "name": "EnglishLesson", "date": "2017-01-12 12:00:00" } The response contains the posted data with the parsed date and an added id column :: { "id" : 1, "name": "EnglishLesson", "date": "2017-01-12T12:00:00+00:00" } """ dict = json.loads(request.get_data(as_text=True)) try: #Get the name and date values from request. This might raise KeyError name = dict['name'] date = dict['date'] #If date value is a string try converting to datetime. This might raise Value Error if isinstance(date, str): date = parse(date) #Create SQL Alchemy Todo Object and add new entry to the database obj = Todo(name=name, date=date) schema = TodoSchema() session = self.db.get_session() session.add(obj) session.commit() #Prepare response data using marshmallow schema response = json.dumps(schema.dump(obj).data) session.close() return 201, response # Handle errors resulting from invalid data in the request except (ValueError, KeyError): return 400, 'Bad Request' @http('GET', '/todo/delete/<int:id>') @transaction_retry() def delete(self, request, id): """ Method for deleting a single todo referenced by the id Example request :: /todo/delete/1 The response contains data which was deleted from the database :: { "id" : 1, "name": "EnglishLesson", "date": "2017-01-12T12:00:00+00:00" } """ schema = TodoSchema() session = self.db.get_session() #Get data for response obj = session.query(Todo).filter(Todo.id == id).first() response = json.dumps(schema.dump(obj).data) #Delete entry from DB session.query(Todo).filter(Todo.id == id).delete() session.commit() session.close() return response @http('GET', '/todo/get/<int:id>') @transaction_retry() def get(self, request, id): """ Method retrieving a single todo referenced with its id Example request :: /todo/get/1 The response contains data which was deleted from the database :: { "id" : 1, "name": "EnglishLesson", "date": "2017-01-12T12:00:00+00:00" } """ session = self.db.get_session() results = session.query(Todo).filter(Todo.id == id).first() session.close() schema = TodoSchema() return json.dumps(schema.dump(results).data) @http('GET', '/todo/list/') @transaction_retry() def list(self, request): """ Returns list of all todos in the database Example request :: /todo/list The response contains all todos that are saved in the database:: [ { "id" : 1, "name": "EnglishLesson", "date": "2017-01-12T12:00:00+00:00" }, { "id" : 2, "name": "FrenchLesson", "date": "2017-01-13T13:00:00+00:00" } ] """ session = self.db.get_session() results = session.query(Todo).all() session.close() schema = TodoSchema(many=True) return json.dumps(schema.dump(results).data)
class UserStats: """ User stats service """ name = "userstats" config = Config() db = Database(DeclarativeBase) @consume("user_event", group_id="user_event") def consume_user_event(self, new_user_event: bytes): """ Subcribe user_event topic and update database """ user = self._deserialise_message(message=new_user_event.value) record_id = f'{new_user_event.topic}-{new_user_event.offset}-{new_user_event.timestamp}' self._insert_new_user(record_id, user) @rpc def report(self): """ Return user count by city """ return self._get_report() @timer(interval=REPORT_PERIOD) def email_report(self): """ Email will be sent only if SEND_EMAIL false """ report = self._get_report() to = ['*****@*****.**'] subject = 'report' content = str(report) if not SEND_EMAIL: logging.info( f"New report generated. Fake email sent. to: {to}, subject: {subject}, content: {content}" ) return yag = yagmail.SMTP(EMAIL_SOURCE, EMAIL_PASSWORD) yag.send(cc=to.encode('utf-8'), subject=subject.encode('utf-8'), contents=content.encode('utf-8')) logging.info( f"Email sent. to: {to}, subject: {subject}, content: {content}") def _deserialise_message(self, message: bytes) -> dict: return json.loads(message.decode('utf-8')) def _insert_new_user(self, record_id: str, user: dict): user_db_record = User(seq_id=record_id, user_id=user['id'], city=user.get('address', {}).get('city', ''), state=user.get('address', {}).get('state', ''), country=user.get('address', {}).get('country', ''), post_code=user.get('address', {}).get('postCode', ''), datetime=user.get('datetime', '')) with self.db.get_session() as session: try: session.add(user_db_record) session.commit() except IntegrityError: logging.info('Duplicated event') logging.info(f'New user inserted: {user}') def _get_user_count_by_city(self): with self.db.get_session() as session: result = (session \ .query( User.city, func.count(User.user_id.distinct()).label('n_count')) .group_by(User.city) .order_by('n_count')).all() return dict(result) def _get_report(self): report_datetime = datetime.datetime.now() report = { 'timestamp': str(report_datetime), 'stats': self._get_user_count_by_city() } return report
class AppointmentsService: """ This Service is responsible for the management of appointments. """ name = 'appointments' # api = OpenApi('appointments.yaml') db = Database(DeclarativeBase) dispatch = EventDispatcher() #statsd = StatsD('prod') tracer = Tracer() # @api.operation('get_appointment') # @statsd.timer('get_appointment') @http("GET", "/appointments/<int:appointment_id>") def get_appointment(self, request, appointment_id): """ Returns all Information for the reqeusted Appointment :param request: http request :param appointment_id: the ID of a Appointment :return: 200 Details for the requested Appointment :return: 404 No Appointment for given ID :return: 500 Connection Error to Database """ try: appointment = self.db.session.query(Appointment).get( appointment_id) formatted_appointment = "\n\n" formatted_appointment += "Name: " + appointment.customer_name + "\nTreatment ID: " + str( appointment.treatment_id ) + "\nTreatment Name: " + appointment.treatment_name + "\nDate: " + appointment.date.strftime( "%d.%m.%Y") + "\nTime: " + str( appointment.start_time) + ":00 - " + str( appointment.end_time) + ":00\n\n" return 200, u"\nAppointment: {}".format(formatted_appointment) except AttributeError: return 404, "No Treatment exists for the given ID.\n" except exc.SQLAlchemyError: return 500, "Could not read Appointment.\n" # @api.operation('get_appointments') # @statsd.timer('get_appointments') @http("GET", "/appointments/list") def get_appointments(self, request): """ Shows a list of all settled Appointments :param request: http request :return: 200 List of all booked Appointments :return: 404 No stored Appointments :return: 500 Connection Error to Database """ try: appointments = self.db.session.query(Appointment).all() formatted_appointments = "\n\n" for appointment in appointments: formatted_appointments += "Name: " + appointment.customer_name + "\nTreatment ID: " + str( appointment.treatment_id ) + "\nTreatment Name: " + appointment.treatment_name + "\nDate: " + appointment.date.strftime( "%d.%m.%Y") + "\nTime: " + str( appointment.start_time) + ":00 - " + str( appointment.end_time) + ":00\n\n" if formatted_appointments == "\n\n": return 404, "No Appointments exist.\n" else: return 200, u"\nAppointment: {}".format(formatted_appointments) except exc.SQLAlchemyError: return 500, "Could not read Appointments.\n" # @api.operation('create_appointment') # @statsd.timer('create_appointment') @http("POST", "/appointments/") def create_appointment(self, request): """ Saves a new Appointment :param request: new Appointment that should be saved :return: 201 Detailed information for newly created Appointment :return: 404 No Treatment for given TreatmentID :return: 409 Appointment Details do not match the requirements :return: 500 Connection Error """ appointment_detail = json.loads(request.get_data()) if appointment_detail['start_time'] >= appointment_detail['end_time']: return 409, "Start time has to be before end time.\n" elif appointment_detail['start_time'] < 8 or appointment_detail[ 'start_time'] > 17 or appointment_detail[ 'end_time'] < 9 or appointment_detail['end_time'] > 18: return 409, "Appointments are only available from 8 - 18. Please choose another timeslot.\n" try: URL = "http://" + config.get( 'TREATMENTS_SERVICE') + "/treatments/" + str( appointment_detail['treatment_id']) t_response = requests.get(url=URL) t_response.encoding = 'utf-8' treatment_data = literal_eval(t_response.text) except SyntaxError: return 404, "No Treatment exists for given TreatmentID.\n" except requests.exceptions.ConnectionError: return 500, "Could not read Treatments.\n" if treatment_data['minduration'] > (appointment_detail['end_time'] - appointment_detail['start_time']): return 409, "An appointment for " + treatment_data[ 'name'] + " takes at least " + str( treatment_data['minduration'] ) + " hour(s). Please choose another timeslot.\n" elif treatment_data['maxduration'] < ( appointment_detail['end_time'] - appointment_detail['start_time']): return 409, "An appointment for " + treatment_data[ 'name'] + " takes maximum " + str( treatment_data['maxduration'] ) + " hour(s). Please choose another timeslot.\n" try: conflicts = 0 appointments = self.db.session.query(Appointment).all() for appointment in appointments: if treatment_data['name'] == appointment.treatment_name: if appointment_detail['date'] == appointment.date.strftime( "%Y-%m-%d"): if not (appointment_detail['start_time'] <= appointment.start_time and appointment_detail['end_time'] <= appointment.start_time) and not ( appointment.end_time <= appointment_detail['end_time'] and appointment.end_time <= appointment_detail['start_time']): conflicts += 1 if conflicts == 0: newappointment = Appointment( treatment_id=appointment_detail['treatment_id'], treatment_name=treatment_data['name'], customer_name=appointment_detail['customer_name'], date=appointment_detail['date'], start_time=appointment_detail['start_time'], end_time=appointment_detail['end_time'], duration=appointment_detail['end_time'] - appointment_detail['start_time']) with self.db.get_session() as session: session.add(newappointment) appointment_detail['treatment_name'] = treatment_data['name'] self.dispatch("booked_appointment", appointment_detail) return 201, u"\nAppointment: {}".format(appointment_detail) else: return 409, "There were " + str( conflicts ) + " conflicts with other appointments. Please choose a free timeslot.\n" except exc.SQLAlchemyError: return 500, "Could not save Appointment.\n"