$30 off During Our Annual Pro Sale. View Details »

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

Brian Zambrano

October 06, 2016
Tweet

More Decks by Brian Zambrano

Other Decks in Programming

Transcript

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

    View Slide

  2. About me
    Brian Zambrano
    [email protected]
    https://github.com/brianz/
    • Originally from San Francisco Bay Area
    • Moved to Ft. Collins, CO in 2014
    • Have been writing Python since 2001

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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,
    }

    View Slide

  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

    View Slide

  8. Demo

    View Slide

  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)

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  14. Demo

    View Slide

  15. Transports and Brokers
    Currently, servant supports two
    brokers for communication:
    • local library
    • HTTP
    Pretty easy to add others

    View Slide

  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

    View Slide

  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)

    View Slide

  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")])

    View Slide

  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

    View Slide

  20. Handing Errors
    def run(self, **kwargs):
    if self.denominator == 0:
    raise ActionError('Cannot divide by zero')
    self.quotient = self.numerator / self.denominator

    View Slide

  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}}

    View Slide