1209fb97c7
Resolve the following deprecation warnings on Python 3.x: DeprecationWarning: Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated since Python 3.3,and in 3.9 it will stop working" Note that even though we're in Ussuri, I've kept this Python 2 compatible since we haven't done all the other work to mark this package as Python 3-only. Change-Id: Iff4cf1871a6a91d91da03d9b79ef61e715a979cf Signed-off-by: Stephen Finucane <stephenfin@redhat.com>
167 lines
5.7 KiB
Python
167 lines
5.7 KiB
Python
# Copyright 2013 Red Hat, Inc.
|
|
#
|
|
# 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.
|
|
|
|
"""Provides the base report model
|
|
|
|
This module defines a class representing the basic report
|
|
data model from which all data models should inherit (or
|
|
at least implement similar functionality). Data models
|
|
store unserialized data generated by generators during
|
|
the report serialization process.
|
|
"""
|
|
|
|
import copy
|
|
|
|
try: # python 3
|
|
from collections import abc
|
|
except ImportError: # python 2
|
|
import collections as abc
|
|
|
|
import six
|
|
|
|
|
|
class ReportModel(abc.MutableMapping):
|
|
"""A Report Data Model
|
|
|
|
A report data model contains data generated by some
|
|
generator method or class. Data may be read or written
|
|
using dictionary-style access, and may be read (but not
|
|
written) using object-member-style access. Additionally,
|
|
a data model may have an associated view. This view is
|
|
used to serialize the model when str() is called on the
|
|
model. An appropriate object for a view is callable with
|
|
a single parameter: the model to be serialized.
|
|
|
|
If present, the object passed in as data will be transformed
|
|
into a standard python dict. For mappings, this is fairly
|
|
straightforward. For sequences, the indices become keys
|
|
and the items become values.
|
|
|
|
:param data: a sequence or mapping of data to associate with the model
|
|
:param attached_view: a view object to attach to this model
|
|
"""
|
|
|
|
def __init__(self, data=None, attached_view=None):
|
|
self.attached_view = attached_view
|
|
|
|
if data is not None:
|
|
if isinstance(data, abc.Mapping):
|
|
self.data = dict(data)
|
|
elif isinstance(data, abc.Sequence):
|
|
# convert a list [a, b, c] to a dict {0: a, 1: b, 2: c}
|
|
self.data = dict(enumerate(data))
|
|
else:
|
|
raise TypeError('Data for the model must be a sequence '
|
|
'or mapping.')
|
|
else:
|
|
self.data = {}
|
|
|
|
def __str__(self):
|
|
self_cpy = copy.deepcopy(self)
|
|
for key in self_cpy:
|
|
if getattr(self_cpy[key], 'attached_view', None) is not None:
|
|
self_cpy[key] = six.text_type(self_cpy[key])
|
|
|
|
if self.attached_view is not None:
|
|
return self.attached_view(self_cpy)
|
|
else:
|
|
raise Exception("Cannot stringify model: no attached view")
|
|
|
|
def __repr__(self):
|
|
if self.attached_view is not None:
|
|
return ("<Model {cl.__module__}.{cl.__name__} {dt}"
|
|
" with view {vw.__module__}."
|
|
"{vw.__name__}>").format(cl=type(self),
|
|
dt=self.data,
|
|
vw=type(self.attached_view))
|
|
else:
|
|
return ("<Model {cl.__module__}.{cl.__name__} {dt}"
|
|
" with no view>").format(cl=type(self),
|
|
dt=self.data)
|
|
|
|
def __getitem__(self, attrname):
|
|
return self.data[attrname]
|
|
|
|
def __setitem__(self, attrname, attrval):
|
|
self.data[attrname] = attrval
|
|
|
|
def __delitem__(self, attrname):
|
|
del self.data[attrname]
|
|
|
|
def __contains__(self, key):
|
|
return self.data.__contains__(key)
|
|
|
|
def __getattr__(self, attrname):
|
|
# Needed for deepcopy in Python3. That will avoid an infinite loop
|
|
# in __getattr__ .
|
|
if 'data' not in self.__dict__:
|
|
self.data = {}
|
|
|
|
try:
|
|
return self.data[attrname]
|
|
except KeyError:
|
|
# we don't have that key in data, and the
|
|
# model class doesn't have that attribute
|
|
raise AttributeError(
|
|
"'{cl}' object has no attribute '{an}'".format(
|
|
cl=type(self).__name__, an=attrname
|
|
)
|
|
)
|
|
|
|
def __len__(self):
|
|
return len(self.data)
|
|
|
|
def __iter__(self):
|
|
return self.data.__iter__()
|
|
|
|
def set_current_view_type(self, tp, visited=None):
|
|
"""Set the current view type
|
|
|
|
This method attempts to set the current view
|
|
type for this model and all submodels by calling
|
|
itself recursively on all values, traversing
|
|
intervening sequences and mappings when possible,
|
|
and ignoring all other objects.
|
|
|
|
:param tp: the type of the view ('text', 'json', 'xml', etc)
|
|
:param visited: a set of object ids for which the corresponding objects
|
|
have already had their view type set
|
|
"""
|
|
|
|
if visited is None:
|
|
visited = set()
|
|
|
|
def traverse_obj(obj):
|
|
oid = id(obj)
|
|
|
|
# don't die on recursive structures,
|
|
# and don't treat strings like sequences
|
|
if oid in visited or isinstance(obj, six.string_types):
|
|
return
|
|
|
|
visited.add(oid)
|
|
|
|
if hasattr(obj, 'set_current_view_type'):
|
|
obj.set_current_view_type(tp, visited=visited)
|
|
|
|
if isinstance(obj, abc.Sequence):
|
|
for item in obj:
|
|
traverse_obj(item)
|
|
|
|
elif isinstance(obj, abc.Mapping):
|
|
for val in six.itervalues(obj):
|
|
traverse_obj(val)
|
|
|
|
traverse_obj(self)
|