Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Servant - a Python framework for building (micro)services

Servant - a Python framework for building (micro)services

Talk given at Denver Python Meetup about Servant, a Python framework for building (micro)services.

https://www.meetup.com/Denver-Python-Meetup/events/233917210/
https://github.com/brianz/servant

Cb383c8d8e93cc662e484c149cb3b96b?s=128

Brian Zambrano

October 06, 2016
Tweet

Transcript

  1. Servant a Python framework for building (micro)services

  2. About me Brian Zambrano brianz@gmail.com https://github.com/brianz/ • Originally from San

    Francisco Bay Area • Moved to Ft. Collins, CO in 2014 • Have been writing Python since 2001
  3. What is Servant? • Servant is a Python library/framework for

    authoring and communicating with services • Servant is to services what Django, Rails or Flask are to web applications • Primary use case is internal services https://github.com/brianz/servant
  4. Why Servant? • REST is great, but has many limitations

    and problems • HTTP by definition • Requires a running server • Usually end up using a wrapper library • Various interpretations • Can be challenging to get your Resources right
  5. Using servant as a client import servant.client client = servant.client.Client(

    'calculator_service', version=1) response = client.add(number1=10, number2=15) if response.is_error(): print response.errors, response.field_errors else: print response.result
  6. Authoring a service from servant.service.base import Service import actions class

    Calculator(Service): name = 'calculator_service' version = 1 action_map = { 'add': actions.AddAction, 'subtract': actions.SubtractAction, }
  7. Implement an Action import servant.fields from servant.service.actions import Action class

    AddAction(Action): number1 = servant.fields.IntField( required=True, in_response=True, ) number2 = servant.fields.IntField( required=True, in_response=True, ) result = servant.fields.IntField( in_response=True, ) def run(self, **kwargs): self.result = self.number1 + self.number2
  8. Demo

  9. Issues with Servant • Currently Python only. Other languages would

    need a Servant library implemented. • No mechanism for exploration...need knowledge of service before hitting it • Can't simply use curl to hit an endpoint • Need another layer to expose a service publicly (i.e., hitting from Javascript)
  10. Using servant as a client import servant.client client = servant.client.Client(

    'calculator_service', version=1) response = client.add(number1=10, number2=15) if response.is_error(): print response.errors, response.field_errors else: print response.result
  11. Using servant as a client import servant.client client = servant.client.Client(

    'calculator_service', version=1) response = client.add(number1=10, number2=15) if response.is_error(): print response.errors, response.field_errors else: print response.result
  12. Using servant as a client import servant.client client = servant.client.Client(

    'calculator_service', version=1) client.configure('http', host='192.168.88.100', port=8888) response = client.add(number1=10, number2=15) if response.is_error(): print response.errors, response.field_errors else: print response.result
  13. Using servant as a client import servant.client client = servant.client.Client(

    'calculator_service', version=1) client.configure('http', host='192.168.88.100', port=8888) response = client.add(number1=10, number2=15) if response.is_error(): print response.errors, response.field_errors else: print response.result
  14. Demo

  15. Transports and Brokers Currently, servant supports two brokers for communication:

    • local library • HTTP Pretty easy to add others
  16. HTTP Transport from .base import BaseTransport import requests class HttpTransport(BaseTransport):

    def __init__(self): self.__url = 'http://localhost:8000' def __repr__(self): return 'HttpTransport at %s' % (self.__url, ) def configure(self, host='localhost', port=8000, scheme='http'): self.__url = '%s://%s:%s' % (scheme, host, port) def is_connected(self): return True def send(self, data): headers = {'content-type': 'application/json'} response = requests.post(self.__url, data=data, headers=headers) return response.text
  17. Local Transport class LocalTransport(BaseTransport): def __init__(self): super(LocalTransport, self).__init__() self.__service =

    None def __repr__(self): return self.__class__.__name__ def configure(self, service_name='', service_version='', service_meta=None): self.service = self._import_and_instantiate_service(service_name, service_version) @property def service(self): raise AttributeError("Cannot access service property directly") @service.setter def service(self, service_instance): self.__service = service_instance def _import_and_instantiate_service(self, service_name, service_version): # Do a bunch of stuff to import and instantiate the service. def send(self, request): return self.__service.handle_request(request)
  18. Field validation def do_divide(): response = client.divide( numerator=100, denominator='abc') if

    response.is_error(): print response.errors, response.field_errors else: print response.quotient None, Bunch(denominator=[Bunch(error=u"Number 'abc' failed to convert to a decimal", hint=u"Number 'abc' failed to convert to a decimal")])
  19. Errors def do_divide(): response = client.divide( numerator=100, denominator=0) if response.is_error():

    print response.errors, response.field_errors else: print response.quotient [u'Unexpected service error: x / 0'], None
  20. Handing Errors def run(self, **kwargs): if self.denominator == 0: raise

    ActionError('Cannot divide by zero') self.quotient = self.numerator / self.denominator
  21. Service protocol {u'actions': [{u'action_name': u'charge_credit_card', u'errors': [{u'error': u'Expired Card', u'error_type':

    u'WARNING', u'hint': u''}], u'field_errors': None, u'results': {u'approved_amount': 0.0, u'processor_response_code': 21, u'transaction_number': 20, u'transaction_status': u'Declined'}}], u'response': {u'correlation_id': u'c1fe850b-d449-45d7- b8be-9b1ff7c7108b', u'errors': [{u'error': u'Expired Card', u'error_type': u'WARNING', u'hint': u''}], u'name': u'payment_service', u'response_time': u'5.64651', u'version': 1}}