class SettingsToTest(settings.Settings): schema: settings.SettingsSchema = { 'one': fields.Dictionary({ 'a': fields.ClassConfigurationSchema(base_class=ClassUsingAttrs27HintsToTest, description='Nifty schema.'), 'b': fields.PythonPath(value_schema=fields.UnicodeString(), description='Must be a path, yo.'), 'c': fields.TypeReference(base_classes=ClassHoldingSigsToTest, description='Refer to that thing!'), }), 'two': fields.SchemalessDictionary(key_type=fields.UnicodeString(), value_type=fields.Boolean()), 'three': fields.List(fields.Integer()), 'four': fields.Nullable(fields.Set(fields.ByteString())), 'five': fields.Any(fields.Integer(), fields.Float()), 'six': fields.ObjectInstance(valid_type=ClassUsingAttrs27HintsToTest, description='Y u no instance?'), 'seven': fields.Polymorph( 'thing', { 'thing1': fields.Dictionary({'z': fields.Boolean()}, allow_extra_keys=True), 'thing2': fields.Dictionary({'y': fields.Boolean()}, allow_extra_keys=True, optional_keys=('y', )), }, ), } defaults: settings.SettingsData = { 'one': { 'b': 'foo.bar:Class', }, 'three': [1, 5, 7], }
def test_schema_correct(self): assert SettingsOneTwoThree.schema == { 'foo': fields.UnicodeString(), 'bar': fields.Boolean(), 'baz': fields.Dictionary( { 'inner_foo': fields.UnicodeString(), 'inner_bar': fields.Boolean(), 'inner_baz': fields.List(fields.Integer()), 'inner_qux': fields.Dictionary( { 'most_inner_foo': fields.Boolean(), 'most_inner_bar': fields.UnicodeString(), }, ), }, ), 'qux': fields.Float(), }
class SettingsTwo(Settings): schema = { 'bar': fields.Integer(), 'baz': fields.Dictionary( { 'inner_foo': fields.UnicodeString(), 'inner_bar': fields.Boolean(), 'inner_baz': fields.List(fields.Integer()), 'inner_qux': fields.Dictionary( { 'most_inner_foo': fields.Boolean(), 'most_inner_bar': fields.UnicodeString(), }, ), }, ), } # type: SettingsSchema defaults = { 'bar': 1, 'baz': { 'inner_foo': 'Default inner', 'inner_qux': { 'most_inner_bar': 'Default most inner' } }, } # type: SettingsData
def test_schema_correct(self): assert SettingsTwoFour.schema == { 'bar': fields.Integer(), 'baz': fields.Dictionary( { 'inner_foo': fields.UnicodeString(), 'inner_bar': fields.Boolean(), 'inner_baz': fields.List(fields.Integer()), 'inner_qux': fields.Dictionary( { 'most_inner_foo': fields.Boolean(), 'most_inner_bar': fields.UnicodeString(), }, ), }, ), 'qux': fields.Decimal(), 'new': fields.ByteString(), 'old': fields.UnicodeString(), }
class MockingTestAction(Action): request_schema = fields.Dictionary({ 'min': fields.Integer(), 'max': fields.Integer(), 'kwargs': fields.SchemalessDictionary(key_type=fields.UnicodeString()), }) response_schema = fields.Dictionary({ 'random': fields.Integer(), 'response': fields.SchemalessDictionary(), 'extra': fields.UnicodeString(), }) def run(self, request): try: # noinspection PyUnresolvedReferences return { 'random': random.randint(request.body['min'], request.body['max']), 'response': function_which_shall_be_mocked( request.body['max'], request.body['min'], **request.body['kwargs'] ), 'extra': function_which_shall_be_mocked.extra.value().for_me, } except AttributeError: raise ActionError(errors=[Error('ATTRIBUTE_ERROR', 'An attribute error was raised')]) except BytesWarning: raise ActionError(errors=[Error('BYTES_WARNING', 'A bytes warning was raised')]) except ExpectedException: raise ActionError(errors=[Error('EXPECTED_EXCEPTION', 'An expected exception was raised')])
class TypesEchoAction(Action): request_schema = fields.Dictionary( { 'an_int': fields.Integer(), 'a_float': fields.Float(), 'a_bool': fields.Boolean(), 'a_bytes': fields.ByteString(), 'a_string': fields.UnicodeString(), 'a_datetime': fields.DateTime(), 'a_date': fields.Date(), 'a_time': fields.Time(), 'a_list': fields.List(fields.Anything(), max_length=0), 'a_dict': fields.Nullable(fields.Dictionary({})), }, optional_keys=( 'an_int', 'a_float', 'a_bool', 'a_bytes', 'a_string', 'a_datetime', 'a_date', 'a_time', 'a_list', 'a_dict', ), ) response_schema = fields.Dictionary( {'r_{}'.format(k): v for k, v in six.iteritems(request_schema.contents)}, optional_keys=('r_{}'.format(k) for k in request_schema.optional_keys), ) def run(self, request): return {'r_{}'.format(k): v for k, v in six.iteritems(request.body)}
class SquareAction(Action): """ This is an example of a simple action which takes a single input argument - a number to square - and returns the square of it. """ # The SOA library enables validation of both requests and responses using the "conformity" library. Simple schemas # can be specified inline like this; more complex ones should live in a separate schemas/ package (see other # examples). request_schema = fields.Dictionary({ # Request bodies are always dictionaries. Here we say we want a dict with exactly one input - a float called # "number" 'number': fields.Float(), }) response_schema = fields.Dictionary({ 'square': fields.Float(), }) def run(self, request): """ When an action is run, the request is validated, and then passed to you here in the run() method, where you can perform the business logic. """ # Request body is a dictionary, so it's easy to pull out fields. square = request.body['number'] ** 2 # Return the result return { 'square': square, }
class SettingsWithDefaults(Settings): schema = { 'complex_property': fields.Dictionary({ 'string_property': fields.UnicodeString(), 'int_property': fields.Integer(), 'kwargs': fields.Dictionary({ 'foo': fields.Integer(), 'bar': fields.UnicodeString(), }), }), 'simple_property': fields.Integer(), } defaults = { 'simple_property': 0, 'complex_property': { 'string_property': 'default_string', 'kwargs': { 'foo': 1, }, }, }
class LoginAction(Action): request_schema = fields.Dictionary({'username': fields.UnicodeString()}) response_schema = fields.Dictionary( {'session': fields.Dictionary({'session_id': fields.UnicodeString(), 'user': fields.UnicodeString()})}, ) def run(self, request): return {'session': {'session_id': six.text_type(uuid.uuid4().hex), 'user': request.body['username']}}
class StubClientTransportSchema(BasicClassSchema): contents = { 'path': fields.UnicodeString( description= 'The path to the stub client transport, in the format `module.name:ClassName`', ), 'kwargs': fields.Dictionary( { 'action_map': fields.SchemalessDictionary( key_type=fields.UnicodeString( description='The name of the action to stub', ), value_type=fields.Dictionary( { 'body': fields.SchemalessDictionary( description= 'The body with which the action should respond', ), 'errors': fields.List( fields.Any( fields.ObjectInstance(Error), fields.Dictionary( { 'code': fields.UnicodeString(), 'message': fields.UnicodeString(), 'field': fields.UnicodeString(), 'traceback': fields.UnicodeString(), 'variables': fields.SchemalessDictionary(), 'denied_permissions': fields.List( fields.UnicodeString()), }, ), ), description='The ', ), }, description= 'A dictionary containing either a body dict or an errors list, providing an ' 'instruction on how the stub action should respond to requests', optional_keys=('body', 'errors'), ), ), }, ), } optional_keys = () description = 'The settings for the local transport'
class RootAction(Action): request_schema = fields.Dictionary( {'number': fields.Integer(), 'base': fields.Integer()}, optional_keys=('base', ), ) response_schema = fields.Dictionary({'number_root': fields.Integer()}) def run(self, request): base = request.body.get('base', 2) return {'number_root': int(round(request.body['number'] ** (1 / float(base))))}
class StubbingTwoCallsTestAction(Action): request_schema = fields.Dictionary({'one': fields.SchemalessDictionary(), 'two': fields.SchemalessDictionary()}) response_schema = fields.Dictionary({'one': fields.SchemalessDictionary(), 'two': fields.SchemalessDictionary()}) def run(self, request): try: return { 'one': request.client.call_action('examiner', 'roll', body=request.body['one']).body, 'two': request.client.call_action('player', 'pitch', body=request.body['two']).body, } except request.client.CallActionError as e: raise ActionError(errors=e.actions[0].errors)
class ActionOne(Action): request_schema = fields.Dictionary({'planet': fields.UnicodeString()}) response_schema = fields.Dictionary({ 'planet_response': fields.UnicodeString(), 'settings': fields.Anything() }) def run(self, request): return { 'planet_response': request.body['planet'], 'settings': self.settings }
class TestAction(Action): __test__ = False # So that PyTest doesn't try to collect this and spit out a warning request_schema = fields.Dictionary({ 'string_field': fields.UnicodeString(), }) response_schema = fields.Dictionary({ 'boolean_field': fields.Boolean(), }) _return = None def run(self, request): return self._return
class TestAction(Action): __test__ = False # So that PyTest doesn't try to collect this and spit out a warning request_schema = fields.Dictionary({ 'string_field': fields.UnicodeString(), }) # type: Optional[fields.Dictionary] response_schema = fields.Dictionary({ 'boolean_field': fields.Boolean(), }) # type: Optional[fields.Dictionary] _return = None # type: Optional[Dict[six.text_type, Any]] def run(self, request): return self._return
class GetTinyImageAction(Action): response_schema = fields.Dictionary({'tiny_image': fields.ByteString()}) def run(self, request): with open(os.path.dirname(__file__) + '/tiny-image.gif', 'rb') as file_input: return {'tiny_image': file_input.read()}
class LocalClientTransportSchema(BasicClassSchema): contents = { 'path': fields.UnicodeString( description='The path to the local client transport, in the format `module.name:ClassName`', ), 'kwargs': fields.Dictionary({ # server class can be an import path or a class object 'server_class': fields.Any( fields.UnicodeString( description='The path to the `Server` class, in the format `module.name:ClassName`', ), fields.ObjectInstance( six.class_types, description='A reference to the `Server`-extending class/type', ), description='The path to the `Server` class to use locally (as a library), or a reference to the ' '`Server`-extending class/type itself', ), # No deeper validation because the Server will perform its own validation 'server_settings': fields.SchemalessDictionary( key_type=fields.UnicodeString(), description='The settings to use when instantiating the `server_class`' ), }), } optional_keys = () description = 'The settings for the local client transport'
class MySettings(SettingsWithDefaults): schema = { 'complex_property': fields.Dictionary({ 'another_property': fields.Integer(), }), }
class WalkAction(Action): request_schema = fields.Dictionary({'value': fields.Any(fields.Integer(), fields.Float())}) response_schema = request_schema add = 1 def run(self, request): return {'value': request.body['value'] + self.add}
class LocalServerTransportSchema(BasicClassSchema): contents = { 'path': fields.UnicodeString( description='The path to the local server transport, in the format `module.name:ClassName`', ), 'kwargs': fields.Dictionary({}), } description = 'The settings for the local client transport'
class Http2TransportSchema(fields.Dictionary): contents = { 'backend_layer_kwargs': fields.Dictionary( { 'http_host': fields.UnicodeString(), 'http_port': fields.UnicodeString(), }, optional_keys=(), allow_extra_keys=False, ), 'backend_type': fields.Constant( *HTTP2_BACKEND_TYPES, description= 'Which backend (hyper-h2 or twisted) should be used for this Http2 transport' ), 'message_expiry_in_seconds': fields.Integer( description= 'How long after a message is sent that it is considered expired, dropped from queue', ), 'queue_capacity': fields.Integer( description= 'The capacity of the message queue to which this transport will send messages', ), 'queue_full_retries': fields.Integer( description= 'How many times to retry sending a message to a full queue before giving up', ), 'receive_timeout_in_seconds': fields.Integer( description='How long to block waiting on a message to be received', ), 'default_serializer_config': fields.ClassConfigurationSchema( base_class=BaseSerializer, description= 'The configuration for the serializer this transport should use.', ), } optional_keys = ( 'backend_layer_kwargs', 'message_expiry_in_seconds', 'queue_capacity', 'queue_full_retries', 'receive_timeout_in_seconds', 'default_serializer_config', ) description = 'The constructor kwargs for the Http2 client and server transports.'
class StubbingOneCallTestAction(Action): request_schema = fields.SchemalessDictionary() response_schema = fields.Dictionary({'forwarded_response': fields.SchemalessDictionary()}) def run(self, request): try: return { 'forwarded_response': request.client.call_action('examiner', 'magnify', body=request.body).body } except request.client.CallActionError as e: raise ActionError(errors=e.actions[0].errors)
class FakeActionTwo(object): """Test action documentation""" request_schema = fields.UnicodeString(description='Be weird.') response_schema = fields.Dictionary({ 'okay': fields.Boolean(description='Whether it is okay'), 'reason': fields.Nullable( fields.UnicodeString(description='Why it is not okay')), })
class ServerSettings(SOASettings): """ Settings specific to servers """ schema = { 'transport': BasicClassSchema(BaseServerTransport), 'middleware': fields.List(BasicClassSchema(ServerMiddleware)), 'client_routing': fields.SchemalessDictionary(), 'logging': fields.SchemalessDictionary(), 'harakiri': fields.Dictionary({ 'timeout': fields.Integer( gte=0 ), # seconds of inactivity before harakiri is triggered, 0 to disable 'shutdown_grace': fields.Integer( gte=0 ), # seconds to gracefully shutdown after harakiri is triggered }), } defaults = { 'client_routing': {}, 'logging': { 'version': 1, 'formatters': { 'console': { 'format': '%(asctime)s %(levelname)7s: %(message)s' }, }, 'handlers': { 'console': { 'level': 'INFO', 'class': 'logging.StreamHandler', 'formatter': 'console', }, }, 'root': { 'handlers': ['console'], 'level': 'INFO', }, }, 'harakiri': { 'timeout': 300, 'shutdown_grace': 30, }, }
class LocalTransportSchema(BasicClassSchema): contents = { 'path': fields.UnicodeString(), 'kwargs': fields.Dictionary({ # server class can be an import path or a class object 'server_class': fields.Any(fields.UnicodeString(), fields.ObjectInstance(six.class_types)), # No deeper validation because the Server will perform its own validation 'server_settings': fields.SchemalessDictionary(key_type=fields.UnicodeString()), }), }
class HelloAction(Action): request_schema = fields.Dictionary( { 'name': fields.UnicodeString(), 'optional': fields.Integer(), 'errors': fields.Integer() }, optional_keys=('optional', 'errors')) def run(self, request): if request.body.get('errors') == 1: raise ActionError([Error('FOO', 'Foo error')]) if request.body.get('errors') == 2: raise ActionError( [Error('BAZ', 'Baz error'), Error('QUX', 'Qux error')]) return {'salutation': 'Hello, {}'.format(request.body['name'])}
class EchoAction(Action): request_schema = fields.SchemalessDictionary() response_schema = fields.Dictionary( { 'request_body': fields.SchemalessDictionary(), 'request_context': fields.SchemalessDictionary(), 'request_switches': fields.List(fields.Integer()), 'request_control': fields.SchemalessDictionary(), }, ) def run(self, request): return { 'request_body': request.body, 'request_context': request.context, 'request_switches': sorted(list(request.switches)), 'request_control': request.control, }
class MetricsSchema(BasicClassSchema): contents = { 'path': fields.UnicodeString( description='The path to the class extending `MetricsRecorder`, in the format `module.name:ClassName`', ), 'kwargs': fields.Dictionary( { 'config': fields.SchemalessDictionary(description='Whatever metrics configuration is required'), }, optional_keys=[ 'config', ], allow_extra_keys=True, description='The keyword arguments that will be passed to the constructed metrics recorder', ), } description = 'Configuration for defining a usage and performance metrics recorder' object_type = MetricsRecorder
class MetricsSchema(BasicClassSchema): contents = { 'path': fields.UnicodeString( description='The module.name:ClassName path to the metrics recorder' ), 'kwargs': fields.Dictionary( { 'config': fields.SchemalessDictionary(), }, optional_keys=[ 'config', ], allow_extra_keys=True, description= 'The keyword arguments that will be passed to the constructed metrics recorder', ), } object_type = MetricsRecorder
class CallServiceAction(Action): """ This is an example of a simple action which chains down to call another service; in this case, ourselves, which is unusual, but it's an easier example than creating another service. """ response_schema = fields.Dictionary({ 'square': fields.Float(), }) def run(self, request): """ When an action is run, the request is validated, and then passed to you here in the run() method, where you can perform the business logic. """ # The client, which lets us call other services, is always available on the request object if any client routing # settings are configured in the service settings. In this case, the service is calling itself, which can only # work if there are two or more server processes running. result = request.client.call_action('example', 'square', body={'number': 42}) return { 'square': result.body['square'], }