diff --git a/docs/source/custom_sockets.rst b/docs/source/custom_sockets.rst index f61c811..787e564 100644 --- a/docs/source/custom_sockets.rst +++ b/docs/source/custom_sockets.rst @@ -25,8 +25,8 @@ To create a custom Socket follow these steps:: # 2. Define endpoints. my_endpoint = Endpoint(name='my_endpoint') # no API call here - my_endpoint.add_call(ScriptCall(name='custom_script'), methods=['GET']) - my_endpoint.add_call(ScriptCall(name='another_custom_script'), methods=['POST']) + my_endpoint.add_call(ScriptCall(name='custom_script', methods=['GET'])) + my_endpoint.add_call(ScriptCall(name='another_custom_script', methods=['POST'])) # What happened here: # - We defined a new endpoint that will be visible under the name `my_endpoint` @@ -224,6 +224,33 @@ You can overwrite the Script name in the following way:: name='custom_name' ) +** Class dependency ** + +Custom socket with this dependency will check if this class is defined - if not then will create it; +This allows you to define which classes are used to store data for this particular custom socket. + +:: + + custom_socket.add_dependency( + ClassDependency( + Class( + name='class_dep_test', + schema=[ + {'name': 'test', 'type': 'string'} + ] + ), + ) + ) + +Existing class:: + + class_instance = Class.plase.get(name='user_profile') + custom_socket.add_dependency( + ClassDependency( + class_instance + ) + ) + Custom Socket recheck --------------------- diff --git a/syncano/models/base.py b/syncano/models/base.py index d8c4db9..015217b 100644 --- a/syncano/models/base.py +++ b/syncano/models/base.py @@ -14,4 +14,4 @@ from .hosting import * # NOQA from .data_views import DataEndpoint as EndpointData # NOQA from .custom_sockets import * # NOQA -from .custom_sockets_utils import Endpoint, ScriptCall, ScriptDependency # NOQA +from .custom_sockets_utils import Endpoint, ScriptCall, ScriptDependency, ClassDependency # NOQA diff --git a/syncano/models/custom_sockets.py b/syncano/models/custom_sockets.py index 441d59c..363ce66 100644 --- a/syncano/models/custom_sockets.py +++ b/syncano/models/custom_sockets.py @@ -25,6 +25,7 @@ class CustomSocket(EndpointMetadataMixin, DependencyMetadataMixin, Model): endpoints = fields.JSONField() dependencies = fields.JSONField() metadata = fields.JSONField(required=False) + config = fields.JSONField(required=False) status = fields.StringField(read_only=True, required=False) status_info = fields.StringField(read_only=True, required=False) created_at = fields.DateTimeField(read_only=True, required=False) diff --git a/syncano/models/custom_sockets_utils.py b/syncano/models/custom_sockets_utils.py index 96eaf6e..45d1196 100644 --- a/syncano/models/custom_sockets_utils.py +++ b/syncano/models/custom_sockets_utils.py @@ -2,6 +2,7 @@ import six from syncano.exceptions import SyncanoValueError +from .classes import Class from .incentives import Script, ScriptEndpoint @@ -17,6 +18,7 @@ class DependencyType(object): The type of the dependency object used in the custom socket; """ SCRIPT = 'script' + CLASS = 'class' class BaseCall(object): @@ -109,7 +111,9 @@ def to_dependency_data(self): return dependency_data def get_name(self): - raise NotImplementedError() + if self.name is not None: + return {'name': self.name} + return {'name': self.dependency_object.name} def get_dependency_data(self): raise NotImplementedError() @@ -117,6 +121,9 @@ def get_dependency_data(self): def create_from_raw_data(self, raw_data): raise NotImplementedError() + def _build_dict(self, instance): + return {field_name: getattr(instance, field_name) for field_name in self.fields} + class ScriptDependency(BaseDependency): """ @@ -147,11 +154,6 @@ def __init__(self, script_or_script_endpoint, name=None): self.dependency_object = script_or_script_endpoint self.name = name - def get_name(self): - if self.name is not None: - return {'name': self.name} - return {'name': self.dependency_object.name} - def get_dependency_data(self): if isinstance(self.dependency_object, ScriptEndpoint): @@ -161,9 +163,7 @@ def get_dependency_data(self): script = self.dependency_object dependency_data = self.get_name() - dependency_data.update({ - field_name: getattr(script, field_name) for field_name in self.fields - }) + dependency_data.update(self._build_dict(script)) return dependency_data @classmethod @@ -174,6 +174,41 @@ def create_from_raw_data(cls, raw_data): }) +class ClassDependency(BaseDependency): + """ + Class dependency object; + + The JSON format is as follows:: + { + 'type': 'class', + 'name': '', + 'schema': [ + {"name": "f1", "type": "string"}, + {"name": "f2", "type": "string"}, + {"name": "f3", "type": "integer"} + ], + } + """ + dependency_type = DependencyType.CLASS + fields = [ + 'name', + 'schema' + ] + + def __init__(self, class_instance): + self.dependency_object = class_instance + self.name = class_instance.name + + def get_dependency_data(self): + data_dict = self._build_dict(self.dependency_object) + data_dict['schema'] = data_dict['schema'].schema + return data_dict + + @classmethod + def create_from_raw_data(cls, raw_data): + return cls(**{'class_instance': Class(**raw_data)}) + + class EndpointMetadataMixin(object): """ A mixin which allows to collect Endpoints objects and transform them to the appropriate JSON format. @@ -239,6 +274,8 @@ def update_dependencies(self): def _get_depedency_klass(cls, depedency_type): if depedency_type == DependencyType.SCRIPT: return ScriptDependency + elif depedency_type == DependencyType.CLASS: + return ClassDependency def add_dependency(self, depedency): self._dependencies.append(depedency) diff --git a/tests/integration_test_accounts.py b/tests/integration_test_accounts.py index ac467db..44988c3 100644 --- a/tests/integration_test_accounts.py +++ b/tests/integration_test_accounts.py @@ -14,7 +14,7 @@ def setUpClass(cls): cls.INSTANCE_NAME = os.getenv('INTEGRATION_INSTANCE_NAME') cls.USER_NAME = os.getenv('INTEGRATION_USER_NAME') cls.USER_PASSWORD = os.getenv('INTEGRATION_USER_PASSWORD') - cls.CLASS_NAME = cls.INSTANCE_NAME + cls.CLASS_NAME = "login_class_test" instance = cls.connection.Instance.please.create(name=cls.INSTANCE_NAME) api_key = instance.api_keys.create(allow_user_create=True, @@ -35,7 +35,7 @@ def tearDownClass(cls): cls.connection = None def check_connection(self, con): - response = con.request('GET', '/v1.1/instances/test_login/classes/') + response = con.request('GET', '/v1.1/instances/{}/classes/'.format(self.INSTANCE_NAME)) obj_list = response['objects'] diff --git a/tests/integration_test_custom_socket.py b/tests/integration_test_custom_socket.py index 2ddd2f3..16b4623 100644 --- a/tests/integration_test_custom_socket.py +++ b/tests/integration_test_custom_socket.py @@ -2,6 +2,8 @@ import time from syncano.models import ( + Class, + ClassDependency, CustomSocket, Endpoint, RuntimeChoices, @@ -44,6 +46,14 @@ def test_creating_raw_data(self): "runtime_name": "python_library_v5.0", "name": "script_123", "source": "print(123)" + }, + { + "type": "class", + "name": "klass", + "schema": [ + {"name": "fieldA", "type": "string"}, + {"name": "fieldB", "type": "integer"}, + ] } ] ) @@ -91,6 +101,16 @@ def test_custom_socket_update(self): socket_to_update.reload() self.assertIn('my_endpoint_new_to_update', socket_to_update.endpoints) + def test_class_dependency_new(self): + suffix = 'new_class' + custom_socket = self._create_custom_socket(suffix, self._define_dependencies_new_class) + self._assert_custom_socket(custom_socket) + + def test_class_dependency_existing(self): + suffix = 'existing_class' + custom_socket = self._create_custom_socket(suffix, self._define_dependencies_new_class) + self._assert_custom_socket(custom_socket) + def assert_custom_socket(self, suffix, dependency_method): custom_socket = self._create_custom_socket(suffix, dependency_method=dependency_method) self._assert_custom_socket(custom_socket) @@ -122,6 +142,38 @@ def _define_endpoints(cls, suffix, custom_socket): ) custom_socket.add_endpoint(endpoint) + @classmethod + def _define_dependencies_new_class(cls, suffix, custom_socket): + cls._add_base_script(suffix, custom_socket) + custom_socket.add_dependency( + ClassDependency( + Class( + name="test_class_{}".format(suffix), + schema=[ + {"name": "testA", "type": "string"}, + {"name": "testB", "type": "integer"}, + ] + ) + ) + ) + + @classmethod + def _define_dependencies_existing_class(cls, suffix, custom_socket): + cls._add_base_script(suffix, custom_socket) + klass = Class( + name="test_class_{}".format(suffix), + schema=[ + {"name": "testA", "type": "string"}, + {"name": "testB", "type": "integer"}, + ] + ) + klass.save() + custom_socket.add_dependency( + ClassDependency( + klass + ) + ) + @classmethod def _define_dependencies_new_script_endpoint(cls, suffix, custom_socket): script = cls._create_script(suffix) @@ -171,6 +223,18 @@ def _define_dependencies_existing_script_endpoint(cls, suffix, custom_socket): ) ) + @classmethod + def _add_base_script(cls, suffix, custom_socket): + custom_socket.add_dependency( + ScriptDependency( + Script( + source='print("{}")'.format(suffix), + runtime_name=RuntimeChoices.PYTHON_V5_0 + ), + name='script_endpoint_{}'.format(suffix), + ) + ) + @classmethod def _create_script(cls, suffix): return Script.please.create(