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:
Enol Fernandez 2016-03-21 11:39:34 +01:00 committed by Enol Fernandez
parent 967f1a2cab
commit 3ee034c683
4 changed files with 482 additions and 0 deletions

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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))