A toolkit to translate lymph service interfaces to machine-readeable schemas (and potentially vice versa)
lymph-schema allows developers to generate a programmable schema from a service's interface.
A schema is a representation of lymph service's RPC interfaces.
Example
{ "orders": { "0.1.0": { "methods": { "submit": { "args": ["values"], "kwargs": {}, "doc": "Submit an order", "raises": ["ValidationError"], "returns": {"type": "number", "format": "integer"} }, "get": { "args": ["id"], "kwargs": {"name": ""}, "doc": "Get an order", "raises": ["NotFound"], "returns": { "title": "OrderSchema", "type": "object", "properties": { "name": {"type": "string"} } } } } } } }
Note: We use json-schema (http://json-schema.org/) to represent returns of RPC methods.
Sometimes we need to annotate our interfaces for better schemas. To get the schema from above the service's interface should look like this:
import lymph from lymph.schema.decorator import spec class Orders(lymph.Interface): @lymph.rpc(raises=NotFound) @spec(returns=OrderSchema) def get(self, id, name=""): "Get an order" # Code ... @lymph.rpc(raises=ValidationError) @spec(returns=int) def submit(self, values): "Submit an order" # Code ...
Note: Because of dynamic typing in Python we need to inform lymph-schema about
the return type of an RPC function. This's what the @spec
decorator allow
us to do.
To generate a schema from a service configuration on the command line do:
lymph gen-schema conf/orders.yml { "orders": ... }
To generate schema programmatically do:
from lymph.schema import generator from lymph.orders.interfaces import Orders generator.generate(Orders, 'orders@0.1.0')
To expose a schema via RPC do:
import lymph from lymph.schema import generator from lymph.schema.decorator import spec class Orders(lymph.Interface): @lymph.rpc() @spec(returns=dict) def get_schema(self): return generator.generate_from_interface(self)
To expose more complex types we use the typing(https://pypi.python.org/pypi/typing) library as you can see here:
import uuid import typing import lymph from lymph.schema import generator from lymph.schema.decorator import spec class Orders(lymph.Interface): @lymph.rpc() @spec(returns=typing.List[uuid.UUID]) def search(self): return [uuid.uuid4() for _ in range(3)]
One of the use cases of a programmable schema is to be able to write good unit tests such that the consumer is decoupled from its producer.
lymph-schema can generate fake services as an hermetic server from a schema for testing purposes:
from lymph.schema import fake orders = fake.build(SCHEMA, "orders@0.1.0") print orders.submit(values={"a": 1})
Note: This code will print the same return type as the real interface but generated randomly.
lymph-schema also contains a toolkit to help writing unit test and extensions for lymph's mocking helpers with a better API:
from lymph.schema import testcase, Schema class TestAPIEndpoint(testcase.MockServiceTester): rpc_schema = Schema({...}) def setUp(self): super(TestAPIEndpoint, self).setUp() self.mocker.on('orders@0.1.0', 'get').returns({'name': 'foo'}) self.mocker.on('orders@0.1.0', 'submit').raises(ValueError) def test_some_endpoint(self): # Do some stuff ... self.assert_not_called('orders@0.1.0', 'get', values={}) # Assert called any number of times >= 1. self.assert_called('orders', 'submit', id=1)