OCCI 1.2 json rendering
Render OCCI objects as json as defined in the current draft of the OCCI 1.2 standard. Partially-Implements: blueprint json-rendering Change-Id: I472e452a7cb1fda70ebd991df6cf64e2e1d5cc7b
This commit is contained in:
parent
967f1a2cab
commit
3ee034c683
|
@ -0,0 +1,261 @@
|
|||
# Copyright 2015 Spanish National Research Council
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import abc
|
||||
import json
|
||||
|
||||
import six
|
||||
import webob.exc
|
||||
|
||||
from ooi.occi.core import action
|
||||
from ooi.occi.core import attribute
|
||||
from ooi.occi.core import collection
|
||||
from ooi.occi.core import kind
|
||||
from ooi.occi.core import link
|
||||
from ooi.occi.core import mixin
|
||||
from ooi.occi.core import resource
|
||||
from ooi import utils
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class JsonRenderer(object):
|
||||
def __init__(self, obj):
|
||||
self.obj = obj
|
||||
|
||||
def _actions(self, env={}):
|
||||
if self.obj.actions:
|
||||
actions = []
|
||||
for a in self.obj.actions:
|
||||
actions.append(a.type_id)
|
||||
if actions:
|
||||
return {"actions": actions}
|
||||
return {}
|
||||
|
||||
@abc.abstractmethod
|
||||
def render_dict(self, env={}):
|
||||
raise NotImplementedError("%s for %s object not implemented" %
|
||||
(type(self), type(self.obj)))
|
||||
|
||||
def render(self, env={}):
|
||||
return json.dumps(self.render_dict(env))
|
||||
|
||||
|
||||
class AttributeRenderer(JsonRenderer):
|
||||
attr_type_names = {
|
||||
attribute.AttributeType.string_type: "string",
|
||||
attribute.AttributeType.number_type: "number",
|
||||
attribute.AttributeType.boolean_type: "boolean",
|
||||
attribute.AttributeType.list_type: "array",
|
||||
attribute.AttributeType.hash_type: "object",
|
||||
# objects are represented as strings
|
||||
attribute.AttributeType.object_type: "string",
|
||||
}
|
||||
|
||||
def render_dict(self, env={}):
|
||||
r = {
|
||||
"mutable": isinstance(self.obj, attribute.MutableAttribute),
|
||||
"required": self.obj.required,
|
||||
"type": self.attr_type_names[self.obj.attr_type],
|
||||
}
|
||||
if self.obj.description:
|
||||
r["description"] = self.obj.description
|
||||
if self.obj.default:
|
||||
r["default"] = self.obj.default
|
||||
# TODO(enolfc): missing pattern
|
||||
return {self.obj.name: r}
|
||||
|
||||
|
||||
class CategoryRenderer(JsonRenderer):
|
||||
def _location(self, env={}):
|
||||
if getattr(self.obj, "location"):
|
||||
url = env.get("application_url", "")
|
||||
return {"location": utils.join_url(url, self.obj.location)}
|
||||
return {}
|
||||
|
||||
def _attributes(self, env={}):
|
||||
attrs = {}
|
||||
for a in self.obj.attributes or []:
|
||||
r = AttributeRenderer(self.obj.attributes[a])
|
||||
attrs.update(r.render_dict(env))
|
||||
if attrs:
|
||||
return {"attributes": attrs}
|
||||
return {}
|
||||
|
||||
def render_dict(self, env={}):
|
||||
r = {
|
||||
"term": self.obj.term,
|
||||
"scheme": self.obj.scheme,
|
||||
}
|
||||
if self.obj.title is not None:
|
||||
r["title"] = self.obj.title
|
||||
r.update(self._attributes(env))
|
||||
r.update(self._location(env))
|
||||
r.update(self._actions(env))
|
||||
return r
|
||||
|
||||
|
||||
class KindRenderer(CategoryRenderer):
|
||||
def render_dict(self, env={}):
|
||||
r = super(KindRenderer, self).render_dict(env)
|
||||
if self.obj.parent:
|
||||
r["parent"] = self.obj.parent.type_id
|
||||
return r
|
||||
|
||||
|
||||
class MixinRenderer(CategoryRenderer):
|
||||
def render_dict(self, env={}):
|
||||
r = super(MixinRenderer, self).render_dict(env)
|
||||
for rel_name in ("depends", "applies"):
|
||||
rel = getattr(self.obj, rel_name, [])
|
||||
if rel:
|
||||
r[rel_name] = [o.type_id for o in rel]
|
||||
return r
|
||||
|
||||
|
||||
class EntityRenderer(JsonRenderer):
|
||||
def _mixins(self, env={}):
|
||||
mixins = []
|
||||
for m in self.obj.mixins:
|
||||
mixins.append(m.type_id)
|
||||
if mixins:
|
||||
return {"mixins": mixins}
|
||||
return {}
|
||||
|
||||
def _attributes(self, env={}):
|
||||
attrs = {}
|
||||
skipped_attributes = [
|
||||
"occi.core.id",
|
||||
"occi.core.title",
|
||||
"occi.core.summary",
|
||||
"occi.core.source",
|
||||
"occi.core.target",
|
||||
]
|
||||
for attr_name in self.obj.attributes or {}:
|
||||
if attr_name in skipped_attributes:
|
||||
continue
|
||||
if self.obj.attributes[attr_name].value is None:
|
||||
continue
|
||||
attrs[attr_name] = self.obj.attributes[attr_name].value
|
||||
if attrs:
|
||||
return {"attributes": attrs}
|
||||
return {}
|
||||
|
||||
def render_dict(self, env={}):
|
||||
r = {
|
||||
"kind": self.obj.kind.type_id,
|
||||
"id": self.obj.id,
|
||||
}
|
||||
if self.obj.mixins:
|
||||
r["mixins"] = [m for m in self.obj.mixins]
|
||||
if self.obj.title is not None:
|
||||
r["title"] = self.obj.title
|
||||
r.update(self._mixins(env))
|
||||
r.update(self._attributes(env))
|
||||
r.update(self._actions(env))
|
||||
return r
|
||||
|
||||
|
||||
class ResourceRenderer(EntityRenderer):
|
||||
def _links(self, env={}):
|
||||
links = []
|
||||
for l in self.obj.links:
|
||||
r = LinkRenderer(l)
|
||||
links.append(r.render_dict(env))
|
||||
if links:
|
||||
return {"links": links}
|
||||
else:
|
||||
return {}
|
||||
|
||||
def render_dict(self, env={}):
|
||||
r = super(ResourceRenderer, self).render_dict(env)
|
||||
r.update(self._links(env))
|
||||
if self.obj.summary is not None:
|
||||
r["summary"] = self.obj.summary
|
||||
return r
|
||||
|
||||
|
||||
class LinkRenderer(EntityRenderer):
|
||||
def render_dict(self, env={}):
|
||||
r = super(LinkRenderer, self).render_dict(env)
|
||||
url = env.get("application_url", "")
|
||||
r["source"] = {
|
||||
"kind": self.obj.source.kind.type_id,
|
||||
"location": utils.join_url(url, self.obj.source.location),
|
||||
}
|
||||
r["target"] = {
|
||||
"kind": self.obj.target.kind.type_id,
|
||||
"location": utils.join_url(url, self.obj.target.location),
|
||||
}
|
||||
return r
|
||||
|
||||
|
||||
class ActionRenderer(CategoryRenderer):
|
||||
def _location(self, env={}):
|
||||
return {}
|
||||
|
||||
def _actions(self, env={}):
|
||||
return {}
|
||||
|
||||
|
||||
class CollectionRenderer(JsonRenderer):
|
||||
def render_dict(self, env={}):
|
||||
r = {}
|
||||
for what in ["kinds", "mixins", "actions", "resources", "links"]:
|
||||
coll = getattr(self.obj, what)
|
||||
if coll:
|
||||
r[what] = [get_renderer(obj).render_dict(env) for obj in coll]
|
||||
return r
|
||||
|
||||
|
||||
class ExceptionRenderer(JsonRenderer):
|
||||
def render_dict(self, env={}):
|
||||
return {
|
||||
"code": self.obj.status_code,
|
||||
"message": self.obj.explanation,
|
||||
}
|
||||
|
||||
|
||||
_MAP = {
|
||||
"action": ActionRenderer,
|
||||
"attribute": AttributeRenderer,
|
||||
"kind": KindRenderer,
|
||||
"mixin": MixinRenderer,
|
||||
"collection": CollectionRenderer,
|
||||
"resource": ResourceRenderer,
|
||||
"link": LinkRenderer,
|
||||
"exception": ExceptionRenderer,
|
||||
None: JsonRenderer,
|
||||
}
|
||||
|
||||
|
||||
def get_renderer(obj):
|
||||
if isinstance(obj, action.Action):
|
||||
type_ = "action"
|
||||
elif isinstance(obj, attribute.Attribute):
|
||||
type_ = "attribute"
|
||||
elif isinstance(obj, collection.Collection):
|
||||
type_ = "collection"
|
||||
elif isinstance(obj, mixin.Mixin):
|
||||
type_ = "mixin"
|
||||
elif isinstance(obj, kind.Kind):
|
||||
type_ = "kind"
|
||||
elif isinstance(obj, resource.Resource):
|
||||
type_ = "resource"
|
||||
elif isinstance(obj, link.Link):
|
||||
type_ = "link"
|
||||
elif isinstance(obj, webob.exc.HTTPException):
|
||||
type_ = "exception"
|
||||
else:
|
||||
type_ = None
|
||||
return _MAP.get(type_)(obj)
|
|
@ -65,6 +65,18 @@ class BaseRendererTest(ooi.tests.base.TestCase):
|
|||
knd = kind.Kind("scheme", "term", "title")
|
||||
self.get_render_and_assert(knd)
|
||||
|
||||
def test_kind_attributes(self):
|
||||
attr = attribute.MutableAttribute("org.example", "foo",
|
||||
description="bar",
|
||||
default="baz")
|
||||
knd = kind.Kind("scheme", "term", "title",
|
||||
attributes=attribute.AttributeCollection({
|
||||
"org.example": attr}
|
||||
))
|
||||
r = self.renderer.get_renderer(knd)
|
||||
observed = r.render()
|
||||
self.assertKindAttr(knd, attr, observed)
|
||||
|
||||
def test_mixin(self):
|
||||
mxn = mixin.Mixin("scheme", "term", "title")
|
||||
self.get_render_and_assert(mxn)
|
||||
|
|
|
@ -59,6 +59,9 @@ class TestOCCIHeaderRendering(base.BaseRendererTest):
|
|||
expected = self.get_category("kind", obj)
|
||||
self.assertEqual(expected, observed)
|
||||
|
||||
def assertKindAttr(self, obj, attr, observed):
|
||||
self.skipTest("Kind attribute rendering missing for headers")
|
||||
|
||||
def assertLink(self, obj, observed):
|
||||
category = self.get_category("kind", obj.kind,
|
||||
location=obj.kind.location)
|
||||
|
|
|
@ -0,0 +1,206 @@
|
|||
# Copyright 2016 Spanish National Research Council
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import json
|
||||
|
||||
from ooi.occi.core import attribute
|
||||
from ooi.occi.rendering import occi_json
|
||||
from ooi.tests.unit.occi.renderings import base
|
||||
|
||||
|
||||
class TestOCCIJsonRendering(base.BaseRendererTest):
|
||||
def setUp(self):
|
||||
super(TestOCCIJsonRendering, self).setUp()
|
||||
self.renderer = occi_json
|
||||
|
||||
def assertAction(self, obj, observed):
|
||||
expected = {
|
||||
"term": obj.term,
|
||||
"scheme": obj.scheme,
|
||||
"title": obj.title
|
||||
}
|
||||
self.assertEqual(expected, json.loads(observed))
|
||||
|
||||
def assertCollection(self, obj, observed):
|
||||
observed_json = json.loads(observed)
|
||||
for what, fn in [("kinds", self.assertKind),
|
||||
("mixins", self.assertMixin),
|
||||
("actions", self.assertAction),
|
||||
("links", self.assertLink),
|
||||
("resources", self.assertResource)]:
|
||||
objs = getattr(obj, what)
|
||||
if objs:
|
||||
dumped_objs = [json.dumps(o) for o in observed_json[what]]
|
||||
map(fn, objs, dumped_objs)
|
||||
|
||||
def assertException(self, obj, observed):
|
||||
expected = {
|
||||
"code": obj.status_code,
|
||||
"message": obj.explanation,
|
||||
}
|
||||
self.assertEqual(expected, json.loads(observed))
|
||||
|
||||
def assertKind(self, obj, observed):
|
||||
expected = {
|
||||
"term": obj.term,
|
||||
"scheme": obj.scheme,
|
||||
"title": obj.title,
|
||||
}
|
||||
self.assertEqual(expected, json.loads(observed))
|
||||
|
||||
def assertKindAttr(self, obj, attr, observed):
|
||||
expected = {
|
||||
"mutable": isinstance(attr, attribute.MutableAttribute),
|
||||
"required": attr.required,
|
||||
"type": "string",
|
||||
}
|
||||
if attr.default:
|
||||
expected["default"] = attr.default
|
||||
if attr.description:
|
||||
expected["description"] = attr.description
|
||||
k, v = json.loads(observed)["attributes"].popitem()
|
||||
self.assertEqual(k, attr.name)
|
||||
self.assertEqual(expected, v)
|
||||
|
||||
def assertLink(self, obj, observed):
|
||||
link = {
|
||||
"kind": obj.kind.type_id,
|
||||
"id": obj.id,
|
||||
"source": {
|
||||
"location": obj.source.location,
|
||||
"kind": obj.source.kind.type_id
|
||||
},
|
||||
"target": {
|
||||
"location": obj.target.location,
|
||||
"kind": obj.target.kind.type_id,
|
||||
},
|
||||
"title": obj.title,
|
||||
}
|
||||
self.assertEqual(link, json.loads(observed))
|
||||
|
||||
def assertMixin(self, obj, observed):
|
||||
expected = {
|
||||
"term": obj.term,
|
||||
"scheme": obj.scheme,
|
||||
"title": obj.title
|
||||
}
|
||||
self.assertEqual(expected, json.loads(observed))
|
||||
|
||||
def assertResource(self, obj, observed):
|
||||
expected = {}
|
||||
for attr in ["summary", "title", "id"]:
|
||||
v = getattr(obj, attr, None)
|
||||
if v is not None:
|
||||
expected[attr] = v
|
||||
expected["kind"] = obj.kind.type_id
|
||||
if obj.mixins:
|
||||
expected["mixins"] = [m.type_id for m in obj.mixins]
|
||||
if obj.actions:
|
||||
expected["actions"] = [a.type_id for a in obj.actions]
|
||||
self.assertEqual(expected, json.loads(observed))
|
||||
|
||||
def assertResourceActions(self, obj, actions, observed):
|
||||
self.assertResource(obj, observed)
|
||||
|
||||
def assertResourceMixins(self, obj, mixins, observed):
|
||||
self.assertResource(obj, observed)
|
||||
|
||||
def assertResourceAttr(self, obj, attr, observed):
|
||||
observed_json = json.loads(observed)
|
||||
expected = {attr[0]: attr[1]}
|
||||
self.assertEqual(expected, observed_json['attributes'])
|
||||
|
||||
def assertResourceStringAttr(self, obj, attr, observed):
|
||||
self.assertResourceAttr(obj, attr, observed)
|
||||
|
||||
def assertResourceIntAttr(self, obj, attr, observed):
|
||||
self.assertResourceAttr(obj, attr, observed)
|
||||
|
||||
def assertResourceBoolAttr(self, obj, attr, observed):
|
||||
self.assertResourceAttr(obj, attr, observed)
|
||||
|
||||
def assertResourceLink(self, obj1, obj2, observed):
|
||||
self.assertLink(obj1.links[0],
|
||||
json.dumps(json.loads(observed)["links"][0]))
|
||||
|
||||
def test_object_attr(self):
|
||||
attr = attribute.MutableAttribute("org.example")
|
||||
r = self.renderer.get_renderer(attr)
|
||||
observed = r.render()
|
||||
expected = {
|
||||
"org.example": {
|
||||
"type": "string", "required": False, "mutable": True,
|
||||
}
|
||||
}
|
||||
self.assertEqual(expected, json.loads(observed))
|
||||
|
||||
def test_list_attr(self):
|
||||
attr = attribute.MutableAttribute(
|
||||
"org.example", attr_type=attribute.AttributeType.list_type)
|
||||
r = self.renderer.get_renderer(attr)
|
||||
observed = r.render()
|
||||
expected = {
|
||||
"org.example": {
|
||||
"type": "array", "required": False, "mutable": True,
|
||||
}
|
||||
}
|
||||
self.assertEqual(expected, json.loads(observed))
|
||||
|
||||
def test_hash_attr(self):
|
||||
attr = attribute.MutableAttribute(
|
||||
"org.example", attr_type=attribute.AttributeType.hash_type)
|
||||
r = self.renderer.get_renderer(attr)
|
||||
observed = r.render()
|
||||
expected = {
|
||||
"org.example": {
|
||||
"type": "object", "required": False, "mutable": True,
|
||||
}
|
||||
}
|
||||
self.assertEqual(expected, json.loads(observed))
|
||||
|
||||
def test_string_attr(self):
|
||||
attr = attribute.MutableAttribute(
|
||||
"org.example", attr_type=attribute.AttributeType.string_type)
|
||||
r = self.renderer.get_renderer(attr)
|
||||
observed = r.render()
|
||||
expected = {
|
||||
"org.example": {
|
||||
"type": "string", "required": False, "mutable": True,
|
||||
}
|
||||
}
|
||||
self.assertEqual(expected, json.loads(observed))
|
||||
|
||||
def test_number_attr(self):
|
||||
attr = attribute.MutableAttribute(
|
||||
"org.example", attr_type=attribute.AttributeType.number_type)
|
||||
r = self.renderer.get_renderer(attr)
|
||||
observed = r.render()
|
||||
expected = {
|
||||
"org.example": {
|
||||
"type": "number", "required": False, "mutable": True,
|
||||
}
|
||||
}
|
||||
self.assertEqual(expected, json.loads(observed))
|
||||
|
||||
def test_boolean_attr(self):
|
||||
attr = attribute.MutableAttribute(
|
||||
"org.example", attr_type=attribute.AttributeType.boolean_type)
|
||||
r = self.renderer.get_renderer(attr)
|
||||
observed = r.render()
|
||||
expected = {
|
||||
"org.example": {
|
||||
"type": "boolean", "required": False, "mutable": True,
|
||||
}
|
||||
}
|
||||
self.assertEqual(expected, json.loads(observed))
|
Loading…
Reference in New Issue