Exemplo n.º 1
0
 def load_doc(self, docid):
     url = urlparse.urljoin(self.root_url,"/bokeh/getdocapikey/%s" % docid)
     resp = self.http_session.get(url, verify=False)
     if resp.status_code == 401:
         raise Exception('HTTP Unauthorized accessing DocID "%s"' % docid)
     apikey = utils.get_json(resp)
     if 'apikey' in apikey:
         self.docid = docid
         self.apikey = apikey['apikey']
         logger.info('got read write apikey')
     else:
         self.docid = docid
         self.apikey = apikey['readonlyapikey']
         logger.info('got read only apikey')
     url = urlparse.urljoin(self.root_url, "/bokeh/bb/")
     # TODO: Load the full document. For now, just load the PlotContext
     url = urlparse.urljoin(self.base_url, self.docid+"/PlotContext/")
     attrs = protocol.deserialize_json(self.http_session.get(url).content)
     if len(attrs) == 0:
         logger.warning("Unable to load PlotContext for doc ID %s" % self.docid)
     else:
         self.plotcontext = PlotServerSession.PlotContext(id=attrs[0]["id"])
         if len(attrs) > 1:
             logger.warning("Found more than one PlotContext for doc ID %s; " \
                     "Using PlotContext ID %s" % (self.docid, attrs[0]["id"]))
     return
Exemplo n.º 2
0
 def load_obj(self, ref, asdict=False, modelattrs={}):
     """loads an object from the server.
     if asdict:
         only the json is returned.
     else:
         update the existing copy in _models if it is present
         instantiate a new one if it is not
         and make sure to convert all references into models
     in the conversion from json to objects, sometimes references
     to models need to be resolved.  If there are any json attributes
     being processed, you can pass them in as modelattrs
     """
     typename = ref["type"]
     ref_id = ref["id"]
     url = utils.urljoin(self.base_url, self.docid + "/" + ref["type"] +\
                         "/" + ref["id"] + "/")
     attr = protocol.deserialize_json(self.http_session.get(url).content)
     if not asdict:
         m = PlotObject.get_obj(typename, attr)
         self.add(m)
         m.finalize(self._models)
         m.dirty = False
         return m
     else:
         return attr
Exemplo n.º 3
0
def bulk_upsert(request):
    ''' Update or insert new objects for a given :class:`Document <bokeh.document.Document>`.

    :param docid: id of the :class:`Document <bokeh.document.Document>`
        to update or insert into

    :status 200: when user is authorized
    :status 401: when user is not authorized

    '''
    # endpoint is only used by python, therefore we don't process
    # callbacks here
    docid = request.matchdict['docid']
    client = request.headers.get('client', 'python')
    servermodel_storage = request.registry.servermodel_storage
    doc = docs.Doc.load(servermodel_storage, docid)
    bokehuser = request.current_user()
    temporary_docid = get_temporary_docid(request, docid)
    t = BokehServerTransaction(
        request, bokehuser, doc, 'rw', temporary_docid=temporary_docid
    )
    t.load()
    clientdoc = t.clientdoc
    data = protocol.deserialize_json(request.data.decode('utf-8'))
    if client == 'python':
        clientdoc.load(*data, events='none', dirty=True)
    else:
        clientdoc.load(*data, events='existing', dirty=True)
    t.save()
    msg = ws_update(request, clientdoc, t.write_docid, t.changed)
    return msg
Exemplo n.º 4
0
def bulk_upsert(docid):
    ''' Update or insert new objects for a given :class:`Document <bokeh.document.Document>`.

    :param docid: id of the :class:`Document <bokeh.document.Document>`
        to update or insert into

    :status 200: when user is authorized
    :status 401: when user is not authorized

    '''
    client = request.headers.get('client', 'python')
    doc = docs.Doc.load(bokeh_app.servermodel_storage, docid)
    bokehuser = bokeh_app.current_user()
    temporary_docid = get_temporary_docid(request, docid)
    t = BokehServerTransaction(
        bokehuser, doc, 'rw', temporary_docid=temporary_docid
    )
    t.load()
    clientdoc = t.clientdoc
    data = protocol.deserialize_json(request.data.decode('utf-8'))
    if client == 'python':
        clientdoc.load(*data, events='none', dirty=True)
    else:
        clientdoc.load(*data, events='existing', dirty=True)
    t.save()
    msg = ws_update(clientdoc, t.write_docid, t.changed)
    return make_json(msg)
Exemplo n.º 5
0
def update(request, docid, typename, id):
    """we need to distinguish between writing and patching models
    namely in writing, we shouldn't remove unspecified attrs
    (we currently don't handle this correctly)
    """
    doc = docs.Doc.load(request.registry.servermodel_storage, docid)
    bokehuser = request.current_user()
    temporary_docid = get_temporary_docid(request, docid)
    t = BokehServerTransaction(
        request, bokehuser, doc, 'rw', temporary_docid=temporary_docid
    )
    t.load()
    modeldata = protocol.deserialize_json(request.body.decode('utf-8'))
    ### horrible hack, we need to pop off the noop object if it exists
    modeldata.pop('noop', None)
    clientdoc = t.clientdoc
    log.info("loading done %s", len(clientdoc._models.values()))
    # patch id is not passed...
    modeldata['id'] = id
    modeldata = {'type' : typename,
                 'attributes' : modeldata}
    clientdoc.load(modeldata, events='existing', dirty=True)
    t.save()
    ws_update(request, clientdoc, t.write_docid, t.changed)
    # backbone expects us to send back attrs of this model, but it doesn't
    # make sense to do so because we modify other models, and we want this to
    # all go out over the websocket channel
    return make_json(protocol.serialize_json({'noop' : True}))
Exemplo n.º 6
0
def bulk_upsert(docid):
    ''' Update or insert new objects for a given :class:`Document <bokeh.document.Document>`.

    :param docid: id of the :class:`Document <bokeh.document.Document>`
        to update or insert into

    :status 200: when user is authorized
    :status 401: when user is not authorized

    '''
    client = request.headers.get('client', 'python')
    doc = docs.Doc.load(bokeh_app.servermodel_storage, docid)
    bokehuser = bokeh_app.current_user()
    temporary_docid = get_temporary_docid(request, docid)
    t = BokehServerTransaction(bokehuser,
                               doc,
                               'rw',
                               temporary_docid=temporary_docid)
    t.load()
    clientdoc = t.clientdoc
    data = protocol.deserialize_json(request.data.decode('utf-8'))
    if client == 'python':
        clientdoc.load(*data, events='none', dirty=True)
    else:
        clientdoc.load(*data, events='existing', dirty=True)
    t.save()
    msg = ws_update(clientdoc, t.write_docid, t.changed)
    return make_json(msg)
Exemplo n.º 7
0
def update(docid, typename, id):
    """we need to distinguish between writing and patching models
    namely in writing, we shouldn't remove unspecified attrs
    (we currently don't handle this correctly)
    """
    clientdoc = bokeh_app.backbone_storage.get_document(docid)
    log.info("loading done %s", len(clientdoc._models.values()))
    prune(clientdoc)
    init_bokeh(clientdoc)
    log.info("updating")
    modeldata = protocol.deserialize_json(request.data.decode('utf-8'))
    # patch id is not passed...
    modeldata['id'] = id
    modeldata = {'type' : typename,
                 'attributes' : modeldata}
    clientdoc.load(modeldata, events='existing', dirty=True)
    log.info("done")
    log.info("saving")
    changed = bokeh_app.backbone_storage.store_document(clientdoc)
    log.debug("changed, %s", str(changed))
    ws_update(clientdoc, changed)
    log.debug("update, %s, %s", docid, typename)
    # backbone expects us to send back attrs of this model, but it doesn't
    # make sense to do so because we modify other models, and we want this to
    # all go out over the websocket channel
    return make_json(protocol.serialize_json({'noop' : True}))
Exemplo n.º 8
0
def create(request):
    ''' Update or insert new objects for a given :class:`Document <bokeh.document.Document>`.

    :param docid: id of the :class:`Document <bokeh.document.Document>`
        to update or insert into

    :status 200: when user is authorized
    :status 401: when user is not authorized

    '''
    docid, typename = request.POST['docid'], request.POST['typename']
    doc = docs.Doc.load(request.registry.servermodel_storage, docid)
    bokehuser = request.current_user()
    temporary_docid = get_temporary_docid(request, docid)
    t = BokehServerTransaction(
        request, bokehuser, doc, 'rw', temporary_docid=temporary_docid
    )
    t.load()
    modeldata = protocol.deserialize_json(request.body.decode('utf-8'))
    modeldata = [{'type' : typename,
                  'attributes' : modeldata}]
    t.clientdoc.load(*modeldata, dirty=True)
    t.save()
    ws_update(request, t.clientdoc, t.write_docid, modeldata)
    return make_json(protocol.serialize_json(modeldata[0]['attributes']))
Exemplo n.º 9
0
def update(docid, typename, id):
    """we need to distinguish between writing and patching models
    namely in writing, we shouldn't remove unspecified attrs
    (we currently don't handle this correctly)
    """
    doc = docs.Doc.load(bokeh_app.servermodel_storage, docid)
    bokehuser = bokeh_app.current_user()
    temporary_docid = get_temporary_docid(request, docid)
    t = BokehServerTransaction(bokehuser,
                               doc,
                               'rw',
                               temporary_docid=temporary_docid)
    t.load()
    modeldata = protocol.deserialize_json(request.data.decode('utf-8'))
    ### horrible hack, we need to pop off the noop object if it exists
    modeldata.pop('noop', None)
    clientdoc = t.clientdoc
    log.info("loading done %s", len(clientdoc._models.values()))
    # patch id is not passed...
    modeldata['id'] = id
    modeldata = {'type': typename, 'attributes': modeldata}
    clientdoc.load(modeldata, events='existing', dirty=True)
    t.save()
    ws_update(clientdoc, t.write_docid, t.changed)
    # backbone expects us to send back attrs of this model, but it doesn't
    # make sense to do so because we modify other models, and we want this to
    # all go out over the websocket channel
    return make_json(protocol.serialize_json({'noop': True}))
Exemplo n.º 10
0
def connect(sock, addr, topic, auth):
    sock.timeout = 2.0
    sock.connect(addr)
    msgobj = dict(msgtype='subscribe', topic=topic, auth=auth)
    sock.send(protocol.serialize_json(msgobj))
    msg = sock.recv()
    msg = msg.split(":", 2)[-1]
    msgobj = protocol.deserialize_json(msg)
    assert msgobj['status'][:2] == ['subscribesuccess', topic]
Exemplo n.º 11
0
 def load_all_callbacks(self, get_json=False):
     """get_json = return json of callbacks, rather than
     loading them into models
     """
     url = utils.urljoin(self.base_url, self.docid + "/", "callbacks")
     data = protocol.deserialize_json(self.http_session.get(url).content)
     if get_json:
         return data
     self.load_callbacks_json(data)
Exemplo n.º 12
0
 def load_all_callbacks(self, get_json=False):
     """get_json = return json of callbacks, rather than
     loading them into models
     """
     url = utils.urljoin(self.base_url, self.docid + "/", "callbacks")
     data = protocol.deserialize_json(self.http_session.get(url).content)
     if get_json:
         return data
     self.load_callbacks_json(data)
Exemplo n.º 13
0
def callbacks(docid):
    #broken...
    clientdoc = bokeh_app.backbone_storage.get_document(docid)
    prune(clientdoc)
    if request.method == 'POST':
        jsondata = protocol.deserialize_json(request.data.decode('utf-8'))
        bokeh_app.backbone_storage.push_callbacks(jsondata)
    else:
        jsondata = bokeh_app.backbone_storage.load_callbacks()
    return make_json(protocol.serialize_json(jsondata))
Exemplo n.º 14
0
def create(docid, typename):
    clientdoc = bokeh_app.backbone_storage.get_document(docid)
    prune(clientdoc)
    modeldata = protocol.deserialize_json(request.data.decode('utf-8'))
    modeldata = {'type' : typename,
                 'attributes' : modeldata}
    clientdoc.load(modeldata, dirty=True)
    bokeh_app.backbone_storage.store_document(clientdoc)
    ws_update(clientdoc, modeldata)
    return protocol.serialize_json(modeldata[0]['attributes'])
Exemplo n.º 15
0
 def load_type(self, typename, asdict=False):
     url = utils.urljoin(self.base_url, self.docid + "/", typename + "/")
     attrs = protocol.deserialize_json(self.http_session.get(url).content)
     if not asdict:
         models = self.load_attrs(typename, attrs)
         for m in models:
             m._dirty = False
         return models
     else:
         models = attrs
     return models
Exemplo n.º 16
0
 def load_type(self, typename, asdict=False):
     url = utils.urljoin(self.base_url, self.docid +"/", typename + "/")
     attrs = protocol.deserialize_json(self.http_session.get(url).content)
     if not asdict:
         models = self.load_attrs(typename, attrs)
         for m in models:
             m._dirty = False
         return models
     else:
         models = attrs
     return models
Exemplo n.º 17
0
 def load_all_callbacks(self, get_json=False):
     """get_json = return json of callbacks, rather than
     loading them into models
     """
     doc_keys = self.r.smembers(dockey(self.docid))
     callback_keys = [x.replace("bbmodel", "bbcallback") for x in doc_keys]
     callbacks = self.r.mget(callback_keys)
     callbacks = [x for x in callbacks if x]
     callbacks = [protocol.deserialize_json(x) for x in callbacks]
     if get_json:
         return callbacks
     self.load_callbacks_json(callbacks)
Exemplo n.º 18
0
def connect(sock, addr, topic, auth):
    sock.timeout = 2.0
    sock.connect(addr)
    msgobj = dict(msgtype='subscribe',
                  topic=topic,
                  auth=auth
                  )
    sock.send(protocol.serialize_json(msgobj))
    msg = sock.recv()
    msg = msg.split(":", 2)[-1]
    msgobj = protocol.deserialize_json(msg)
    assert msgobj['status'][:2] == ['subscribesuccess', topic]
Exemplo n.º 19
0
 def load_all_callbacks(self, get_json=False):
     """get_json = return json of callbacks, rather than
     loading them into models
     """
     doc_keys = self.r.smembers(dockey(self.docid))
     callback_keys = [x.replace("bbmodel", "bbcallback") for x in doc_keys]
     callbacks = self.r.mget(callback_keys)
     callbacks = [x for x in callbacks if x]
     callbacks = [protocol.deserialize_json(x) for x in callbacks]
     if get_json:
         return callbacks
     self.load_callbacks_json(callbacks)
Exemplo n.º 20
0
def rpc(docid, typename, id, funcname):
    clientdoc = bokeh_app.backbone_storage.get_document(docid)
    prune(clientdoc)
    model = clientdoc._models[id]
    data = protocol.deserialize_json(request.data.decode('utf-8'))
    args = data.get('args', [])
    kwargs = data.get('kwargs', {})
    result = getattr(model, funcname)(*args, **kwargs)
    log.debug("rpc, %s, %s", docid, typename)
    changed = bokeh_app.backbone_storage.store_document(clientdoc)
    ws_update(clientdoc, changed)
    return make_json(protocol.serialize_json(result))
Exemplo n.º 21
0
def connect(sock, addr, topic, auth):
    # TODO (bev) increasing timeout due to failing TravisCI tests
    # investigate if this is the real solution or if there is a
    # deeper problem
    sock.timeout = 4.0
    sock.connect(addr)
    msgobj = dict(msgtype='subscribe', topic=topic, auth=auth)
    sock.send(protocol.serialize_json(msgobj))
    msg = sock.recv()
    msg = msg.split(":", 2)[-1]
    msgobj = protocol.deserialize_json(msg)
    assert msgobj['status'][:2] == ['subscribesuccess', topic]
Exemplo n.º 22
0
 def pull(self, docid, typename=None, objid=None):
     """you need to call this with either typename AND objid
     or leave out both.  leaving them out means retrieve all
     otherwise, retrieves a specific object
     """
     doc_keys = self.smembers(dockey(docid))
     attrs = self.mget(doc_keys)
     data = []
     for k, attr in zip(doc_keys, attrs):
         typename, _, modelid = parse_modelkey(k)
         attr = protocol.deserialize_json(decode_utf8(attr))
         data.append({'type': typename, 'attributes': attr})
     return data
Exemplo n.º 23
0
 def pull(self, docid, typename=None, objid=None):
     """you need to call this with either typename AND objid
     or leave out both.  leaving them out means retrieve all
     otherwise, retrieves a specific object
     """
     doc_keys = self.smembers(dockey(docid))
     attrs = self.mget(doc_keys)
     data = []
     for k, attr in zip(doc_keys, attrs):
         typename, _, modelid = parse_modelkey(k)
         attr = protocol.deserialize_json(decode_utf8(attr))
         data.append({'type': typename, 'attributes': attr})
     return data
Exemplo n.º 24
0
 def load_all(self, asdict=False):
     doc_keys = self.r.smembers(dockey(self.docid))
     attrs = self.r.mget(doc_keys)
     if asdict:
         return attrs
     data = []
     for k, attr in zip(doc_keys, attrs):
         typename, _, modelid = parse_modelkey(k)
         attr = protocol.deserialize_json(attr)
         data.append({'type': typename, 'attributes': attr})
     models = self.load_broadcast_attrs(data, events=None)
     for m in models:
         m._dirty = False
     return models
Exemplo n.º 25
0
def bulk_upsert(docid):
    # endpoint is only used by python, therefore we don't process
    # callbacks here
    client = request.headers.get('client', 'python')
    clientdoc = bokeh_app.backbone_storage.get_document(docid)
    prune(clientdoc)
    data = protocol.deserialize_json(request.data.decode('utf-8'))
    if client == 'python':
        clientdoc.load(*data, events='none', dirty=True)
    else:
        clientdoc.load(*data, events='existing', dirty=True)
    changed = bokeh_app.backbone_storage.store_document(clientdoc)
    msg = ws_update(clientdoc, changed)
    return make_json(msg)
Exemplo n.º 26
0
 def load_all(self, asdict=False):
     doc_keys = self.r.smembers(dockey(self.docid))
     attrs = self.r.mget(doc_keys)
     if asdict:
         return attrs
     data = []
     for k, attr in zip(doc_keys, attrs):
         typename, _, modelid = parse_modelkey(k)
         attr = protocol.deserialize_json(attr)
         data.append({"type": typename, "attributes": attr})
     models = self.load_broadcast_attrs(data, events=None)
     for m in models:
         m._dirty = False
     return models
Exemplo n.º 27
0
 def load_all(self, asdict=False):
     """the json coming out of this looks different than that coming
     out of load_type, because it contains id, type, attributes, whereas
     the other one just contains attributes directly
     """
     url = utils.urljoin(self.base_url, self.docid + "/")
     attrs = protocol.deserialize_json(self.http_session.get(url).content)
     if not asdict:
         models = self.load_broadcast_attrs(attrs)
         for m in models:
             m._dirty = False
         return models
     else:
         models = attrs
     return models
Exemplo n.º 28
0
 def on_message(self, message):
     msgobj = protocol.deserialize_json(message)
     msgtype = msgobj.get('msgtype')
     if msgtype == 'subscribe':
         auth = msgobj['auth']
         topic = msgobj['topic']
         if self.manager.auth(auth, topic):
             self.manager.subscribe(self.clientid, topic)
             msg = protocol.serialize_json(
                 protocol.status_obj(['subscribesuccess', topic, self.clientid])
             )
             self.write_message(topic + ":" + msg)
         else:
             msg = protocol.serialize_web(protocol.error_obj('unauthorized'))
             self.write_message(topic + ":" + msg)
Exemplo n.º 29
0
def connect(sock, addr, topic, auth):
    # TODO (bev) increasing timeout due to failing TravisCI tests
    # investigate if this is the real solution or if there is a
    # deeper problem
    sock.timeout = 4.0
    sock.connect(addr)
    msgobj = dict(msgtype='subscribe',
                  topic=topic,
                  auth=auth
                  )
    sock.send(protocol.serialize_json(msgobj))
    msg = sock.recv()
    msg = msg.split(":", 2)[-1]
    msgobj = protocol.deserialize_json(msg)
    assert msgobj['status'][:2] == ['subscribesuccess', topic]
Exemplo n.º 30
0
 def test_merge(self):
     d1 = document.Document()
     d2 = document.Document()
     p1 = circle([1], [2])
     p2 = circle([1], [2])
     d1.add(p1)
     d2.add(p2)
     json_objs = d1.dump()
     json_objs = protocol.deserialize_json(protocol.serialize_json(json_objs))
     d2.merge(json_objs)
     assert d2.context._id == d1.context._id
     assert len(d2.context.children) == 2
     assert d2.context is d2._models[d2.context._id]
     pcs = [x for x in d2._models.values() if x.__view_model__ == "PlotContext"]
     assert len(pcs) == 1
Exemplo n.º 31
0
 def load_all(self, asdict=False):
     """the json coming out of this looks different than that coming
     out of load_type, because it contains id, type, attributes, whereas
     the other one just contains attributes directly
     """
     url = utils.urljoin(self.base_url, self.docid +"/")
     attrs = protocol.deserialize_json(self.http_session.get(url).content)
     if not asdict:
         models = self.load_broadcast_attrs(attrs)
         for m in models:
             m._dirty = False
         return models
     else:
         models = attrs
     return models
Exemplo n.º 32
0
def callbacks_post(docid):
    ''' Update callbacks for a given :class:`Document <bokeh.document.Document>`.

    :param docid: id of the :class:`Document <bokeh.document.Document>`
        to update callbacks for

    :status 200: when user is authorized
    :status 401: when user is not authorized

    '''
    # broken...
    clientdoc = bokeh_app.backbone_storage.get_document(docid)
    prune(clientdoc)
    jsondata = protocol.deserialize_json(request.data.decode('utf-8'))
    bokeh_app.backbone_storage.push_callbacks(jsondata)
    return make_json(protocol.serialize_json(jsondata))
Exemplo n.º 33
0
def create(docid, typename):
    ''' Update or insert new objects for a given :class:`Document <bokeh.document.Document>`.

    :param docid: id of the :class:`Document <bokeh.document.Document>`
        to update or insert into

    :status 200: when user is authorized
    :status 401: when user is not authorized

    '''
    clientdoc = bokeh_app.backbone_storage.get_document(docid)
    prune(clientdoc)
    modeldata = protocol.deserialize_json(request.data.decode('utf-8'))
    modeldata = {'type' : typename,
                 'attributes' : modeldata}
    clientdoc.load(modeldata, dirty=True)
    bokeh_app.backbone_storage.store_document(clientdoc)
    ws_update(clientdoc, modeldata)
    return protocol.serialize_json(modeldata[0]['attributes'])
Exemplo n.º 34
0
 def load_obj(self, ref, asdict=False):
     """ Unserializes the object given by **ref**, into a new object
     of the type in the serialization.  If **asdict** is True,
     then the raw dictionary (including object type and ref) is 
     returned, and no new object is instantiated.
     """
     # TODO: Do URL and path stuff to read json data from persistence 
     # backend into jsondata string
     jsondata = None
     attrs = protocol.deserialize_json(jsondata)
     if asdict:
         return attrs
     else:
         from bokeh.objects import PlotObject
         objtype = attrs["type"]
         ref_id = attrs["id"]
         cls = PlotObject.get_class(objtype)
         newobj = cls(id=ref_id)
         # TODO: finish this...
         return newobj
Exemplo n.º 35
0
def bulk_upsert(docid):
    ''' Update or insert new objects for a given :class:`Document <bokeh.document.Document>`.

    :param docid: id of the :class:`Document <bokeh.document.Document>`
        to update or insert into

    :status 200: when user is authorized
    :status 401: when user is not authorized

    '''
    # endpoint is only used by python, therefore we don't process
    # callbacks here
    client = request.headers.get('client', 'python')
    clientdoc = bokeh_app.backbone_storage.get_document(docid)
    prune(clientdoc)
    data = protocol.deserialize_json(request.data.decode('utf-8'))
    if client == 'python':
        clientdoc.load(*data, events='none', dirty=True)
    else:
        clientdoc.load(*data, events='existing', dirty=True)
    changed = bokeh_app.backbone_storage.store_document(clientdoc)
    msg = ws_update(clientdoc, changed)
    return make_json(msg)
Exemplo n.º 36
0
def create(docid, typename):
    ''' Update or insert new objects for a given :class:`Document <bokeh.document.Document>`.

    :param docid: id of the :class:`Document <bokeh.document.Document>`
        to update or insert into

    :status 200: when user is authorized
    :status 401: when user is not authorized

    '''
    doc = docs.Doc.load(bokeh_app.servermodel_storage, docid)
    bokehuser = bokeh_app.current_user()
    temporary_docid = get_temporary_docid(request, docid)
    t = BokehServerTransaction(bokehuser,
                               doc,
                               'rw',
                               temporary_docid=temporary_docid)
    t.load()
    modeldata = protocol.deserialize_json(request.data.decode('utf-8'))
    modeldata = [{'type': typename, 'attributes': modeldata}]
    t.clientdoc.load(*modeldata, dirty=True)
    t.save()
    ws_update(t.clientdoc, t.write_docid, modeldata)
    return protocol.serialize_json(modeldata[0]['attributes'])