Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 29 additions & 2 deletions docs/source/custom_sockets.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down Expand Up @@ -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
---------------------

Expand Down
2 changes: 1 addition & 1 deletion syncano/models/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
1 change: 1 addition & 0 deletions syncano/models/custom_sockets.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
55 changes: 46 additions & 9 deletions syncano/models/custom_sockets_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import six
from syncano.exceptions import SyncanoValueError

from .classes import Class
from .incentives import Script, ScriptEndpoint


Expand All @@ -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):
Expand Down Expand Up @@ -109,14 +111,19 @@ 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()

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):
"""
Expand Down Expand Up @@ -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):
Expand All @@ -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
Expand All @@ -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': '<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.
Expand Down Expand Up @@ -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)
Expand Down
4 changes: 2 additions & 2 deletions tests/integration_test_accounts.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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']

Expand Down
64 changes: 64 additions & 0 deletions tests/integration_test_custom_socket.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
import time

from syncano.models import (
Class,
ClassDependency,
CustomSocket,
Endpoint,
RuntimeChoices,
Expand Down Expand Up @@ -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"},
]
}
]
)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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(
Expand Down