# -*- coding: utf-8 -*-
"""Models for Python objects in Tanium's API."""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import copy
import operator
import re
import six
import warnings
from . import exceptions
float_types = (float,)
""":obj:`tuple` of :obj:`type`: Float types."""
integer_types = six.integer_types
""":obj:`tuple` of :obj:`type`: Integer types."""
string_types = six.string_types
""":obj:`tuple` of :obj:`type`: String types."""
simple_types = tuple(list(integer_types) + list(string_types) + list(float_types))
""":obj:`tuple` of :obj:`type`: All types that should be considered simple types."""
[docs]class ApiModel(object):
"""Base class for all models in the API."""
API_NAME = None
""":obj:`str`: Name of object used in API calls."""
API_NAME_SRC = None
""":obj:`str`: Name of object in source file."""
API_SIMPLE = None
""":obj:`dict`: Map of simple attributes to their types."""
API_COMPLEX = None
""":obj:`dict`: Map of complex attributes to their types."""
API_CONSTANTS = None
""":obj:`dict`: Map of constants to their values."""
API_STR_ADD = None
""":obj:`list` or :obj:`str`: Added to :attr:`API_STR` in str."""
T_ATTR_REPR = "{attr}={value!r}"
""":obj:`str`: Template for simple attrs in str and all attr in repr."""
T_ATTR_STR = "{attr}={value}"
""":obj:`str`: Template for complex attrs in str."""
T_ATTR_JOIN = ", "
""":obj:`str`: String used to join attr vals in str and repr."""
T_CLS_STR = "{obj.__class__.__name__}({attrs})"
""":obj:`str`: Template for class name and attrs in str."""
T_CLS_REPR = "{obj.__class__.__name__}({attrs})"
""":obj:`str`: Template for class name and attrs in repr."""
T_ATTR_DESC = " - {api_type} attribute {attr!r} of type {attr_type!r}"
""":obj:`str`: Template for attr line in :meth:`api_attrs_desc`."""
T_DESC = "Defined attributes:"
""":obj:`str`: Template for first line in :meth:`api_attrs_desc`."""
API_STR = [
"id",
"name",
"display_name",
"value",
"type",
"public_flag",
"hidden_flag",
"question",
"query_text",
"question_text",
"expiration",
"saved_question",
]
""":obj:`list` of :obj:`str`: Attrs to display in str or to put first in repr."""
[docs] def __copy__(self):
"""Support shallow copy for self.
Returns:
:obj:`ApiItem`
"""
return self.__class__(**self.__dict__)
[docs] def __deepcopy__(self, memo):
"""Support deep copy for self.
Returns:
:obj:`ApiItem`
"""
self_dict = self.serialize(wrap_name=False)
new_dict = copy.deepcopy(self_dict, memo)
return self.__class__(**new_dict)
[docs] def __eq__(self, value):
"""Support for self == value.
Args:
value (:obj:`dict` or :obj:`list` or :obj:`ApiModel` or :obj:`object`):
Value for comparison.
Notes:
* If value is a dict, it will be turned into an ApiModel.
* If this object is an ApiList and value is a list, it will be turned
into an ApiList.
* If value is turned into or already is an ApiModel, it will be serialized
into a dict/list.
* Finally, actual comparison of value is done against this object
serialized into a dict/list.
Returns:
:obj:`bool`
"""
sargs = {"wrap_name": False, "wrap_item_attr": False}
this = self.serialize(**sargs)
if isinstance(value, dict):
value = self.__class__(**value)
elif isinstance(value, list) and isinstance(self, ApiList):
value = self.__class__(*value)
if isinstance(value, ApiModel):
value = value.serialize(**sargs)
return this == value
[docs] def __ne__(self, value):
"""Support for self != value.
Args:
value (:obj:`dict` or :obj:`list` or :obj:`ApiModel` or :obj:`object`):
Value for comparison.
Returns:
:obj:`bool`
"""
return not self == value
[docs] @classmethod
def api_attrs(cls):
"""Get simple and complex attributes combined.
Returns:
:obj:`dict`
"""
return dict(list(cls.API_SIMPLE.items()) + list(cls.API_COMPLEX.items()))
[docs] @classmethod
def api_attrs_repr(cls):
"""Get attributes for repr formatting.
Notes:
Will return attrs without dupes in order of priority to show on
format string from :attr:`API_STR`, :attr:`API_STR_ADD`,
and :meth:`api_attrs`.
Returns:
:obj:`list` of :obj:`str`
"""
api_attrs = cls.api_attrs()
show_attrs = cls.API_STR + cls.API_STR_ADD + list(api_attrs)
attrs = []
for attr in show_attrs:
if attr not in attrs and (attr in api_attrs or hasattr(cls, attr)):
attrs.append(attr)
return attrs or list(api_attrs)
[docs] @classmethod
def api_attrs_str(cls):
"""Get attributes for str formatting.
Notes:
Will return attrs without dupes in order of priority to show on
format string from :attr:`API_STR` and :attr:`API_STR_ADD`.
If no attrs from :attr:`API_STR` and :attr:`API_STR_ADD` exist
in :meth:`api_attrs`, return all attrs from :meth:`api_attrs`.
Returns:
:obj:`list` of :obj:`str`
"""
api_attrs = cls.api_attrs()
show_attrs = cls.API_STR + cls.API_STR_ADD
attrs = []
for attr in show_attrs:
if attr not in attrs and (attr in api_attrs or hasattr(cls, attr)):
attrs.append(attr)
return attrs or list(api_attrs)
[docs] @classmethod
def api_attrs_desc(cls):
"""Get description of attrs from :attr:`API_SIMPLE` and :attr:`API_COMPLEX`.
Notes:
Used by exceptions to add defined attributes to error messages.
Returns:
:obj:`list` of :obj:`str`
"""
tmpl = cls.T_ATTR_DESC.format
lines = []
lines.append(cls.T_DESC)
for attr, attr_type in cls.API_SIMPLE.items():
desc = tmpl(api_type="simple", attr=attr, attr_type=attr_type)
lines.append(desc)
for attr, attr_type in cls.API_COMPLEX.items():
desc = tmpl(api_type="complex", attr=attr, attr_type=attr_type)
lines.append(desc)
return lines
[docs] @classmethod
def api_coerce_int(cls, value):
"""Try to coerce value into :obj:`int` if possible.
Args:
value (:obj:`object`):
Object to coerce into int.
Returns:
:obj:`int` or :obj:`object` if unchanged.
"""
try:
return int(value)
except Exception:
return None if value == "" else value
# REST GetResultData can return "max_available_age" of ""
[docs] @classmethod
def api_coerce_float(cls, value):
"""Try to coerce value into :obj:`float` if possible.
Args:
value (:obj:`object`):
Object to coerce into float.
Returns:
:obj:`float` or :obj:`object` if unchanged.
"""
try:
return float(value)
except Exception:
return value
[docs] @classmethod
def api_coerce_list(cls, value):
"""Coerce value into :obj:`list` if it is not already.
Args:
value (:obj:`object`):
Object to coerce into list. If value is None, will return empty list.
Returns:
:obj:`list` or :obj:`object` if unchanged.
"""
if value is None:
return []
if not isinstance(value, (list, tuple)):
return [value]
return value
[docs] @classmethod
def api_coerce_simple(cls, value, be_type):
"""Try to coerce a value into a simple type.
Args:
value (:obj:`object`):
Value that should be coerced.
be_type (:class:`object`):
Type that value should be coerced to.
Returns:
:obj:`float` or :obj:`int` or :obj:`str` or :obj:`object` if unchanged.
"""
if be_type == float_types and not isinstance(value, float_types):
return cls.api_coerce_float(value=value)
elif be_type == integer_types:
if isinstance(value, bool) or not isinstance(value, integer_types):
return cls.api_coerce_int(value=value)
elif be_type == string_types and isinstance(value, simple_types):
return format(value)
return value
[docs] @classmethod
def api_coerce_complex(cls, value, be_type):
"""Try to coerce a value into a complex type.
Args:
value (:obj:`object`):
Value that should be coerced.
be_type (:class:`object`):
Type that value should be coerced to.
Returns:
:obj:`ApiItem` or :obj:`ApiList` or :obj:`object` if unchanged.
"""
if not isinstance(value, be_type):
if isinstance(value, dict):
return be_type(**value)
elif isinstance(value, (list, tuple)):
return be_type(*value)
return value
[docs] def api_coerce_value(self, attr, value):
"""Perform type checking of value.
Will coerce value into expected type if needed and possible.
Args:
attr (:obj:`str`):
Attribute that is being set with value on obj.
value (:obj:`object`):
Value of attr being checked.
Raises:
:exc:`pytan3.api_models.exceptions.AttrTypeError`:
If value is not the expected type after all of this magic.
:exc:`pytan3.api_models.exceptions.AttrUndefinedError`:
If value type is not :data:`simple_types` and attr is not in
:attr:`api_attrs`.
:exc:`pytan3.api_models.exceptions.AttrUndefinedWarning`:
If value type is :data:`simple_types` and attr not in
:attr:`api_attrs`.
Notes:
No checking or coercion is done if:
* value is None
* attr starts with "_"
* attr is all upper case
Returns:
:obj:`object`
"""
if value is None or attr.startswith("_") or attr.isupper():
return value
if attr not in self.api_attrs():
if isinstance(value, bool) or isinstance(value, integer_types):
self.API_SIMPLE[attr] = integer_types
elif isinstance(value, float_types):
self.API_SIMPLE[attr] = float_types
elif isinstance(value, string_types):
self.API_SIMPLE[attr] = string_types
else:
raise exceptions.AttrUndefinedError(obj=self, attr=attr, value=value)
warnings.warn(
exceptions.AttrUndefinedWarning(obj=self, attr=attr, value=value)
)
be_type = self.api_attrs()[attr]
if attr in self.API_SIMPLE:
value = self.api_coerce_simple(value=value, be_type=be_type)
if attr in self.API_COMPLEX:
value = self.api_coerce_complex(value=value, be_type=be_type)
if isinstance(value, be_type) or value is None:
return value
raise exceptions.AttrTypeError(
obj=self, value=value, attr=attr, be_type=be_type
)
[docs]class ApiItem(ApiModel):
"""Model for a complex item in the API."""
API_LIST_API_NAME = None
""":obj:`str`: Name of :attr:`API_LIST_CLS` used in API calls."""
API_LIST_CLS = None
""":class:`ApiList`: List class that holds this item class."""
[docs] def __init__(self, **kwargs):
"""Constructor.
Args:
**kwargs:
Set and checked using :meth:`ApiModel.api_attrs`.
"""
for attr in self.api_attrs():
setattr(self, attr, kwargs.pop(attr, None))
for attr, value in kwargs.items():
setattr(self, attr, value)
[docs] def __len__(self):
"""Return number of :meth:`ApiModel.api_attrs` that are not None.
Returns:
:obj:`int`
"""
api_attrs = self.api_attrs()
set_attrs = [k for k in api_attrs if getattr(self, k, None) is not None]
return len(set_attrs)
[docs] def __str__(self):
"""Show object info using :meth:`ApiModel.api_attrs_str`.
Returns:
:obj:`str`
"""
show_attrs = self.api_attrs_str()
attrs = []
for attr in show_attrs:
value = getattr(self, attr, None)
tmpl = self.T_ATTR_STR if attr in self.API_COMPLEX else self.T_ATTR_REPR
attrs.append(tmpl.format(attr=attr, value=value))
attrs = self.T_ATTR_JOIN.join(attrs)
return self.T_CLS_STR.format(obj=self, attrs=attrs)
[docs] def __repr__(self):
"""Show object info using :meth:`ApiModel.api_attrs_repr`.
Returns:
:obj:`str`
"""
show_attrs = self.api_attrs_repr()
attrs = []
for attr in show_attrs:
value = getattr(self, attr, None)
tmpl = self.T_ATTR_REPR
attrs.append(tmpl.format(attr=attr, value=value))
attrs = self.T_ATTR_JOIN.join(attrs)
return self.T_CLS_REPR.format(obj=self, attrs=attrs)
[docs] def __setattr__(self, attr, value):
"""Enforce type using :meth:`ApiModel.api_coerce_value`."""
value = self.api_coerce_value(attr=attr, value=value)
super(ApiItem, self).__setattr__(attr, value)
[docs] def serialize(
self,
empty=False,
list_attrs=False,
exclude_attrs=None,
only_attrs=None,
wrap_name=True,
wrap_item_attr=True,
):
"""Serialize this object into a dict.
Args:
empty (:obj:`bool`, optional):
Include attrs that have a value of None when serializing.
Defaults to: False.
list_attrs (:obj:`bool`, optional):
Include simple attrs of :obj:`ApiList` when serializing.
Defaults to: False.
exclude_attrs (:obj:`list` of :obj:`str`, optional):
Exclude these attrs when serializing.
Defaults to: None.
only_attrs (:obj:`list` of :obj:`str`, optional):
Include only these attrs when serializing.
Defaults to: None.
wrap_name (:obj:`bool`, optional):
Wrap return in a dict with key of :attr:`ApiModel.API_NAME` and
value of return.
Defaults to: True.
wrap_item_attr (:obj:`bool`, optional):
Wrap return in a dict with key of :attr:`ApiList.API_ITEM_ATTR`
and value of return.
Only used when serializing items of type :obj:`ApiList`.
Defaults to: True.
Notes:
All child objects will get wrap_name=False.
Returns:
:obj:`dict`
"""
exclude_attrs = self.api_coerce_list(value=exclude_attrs)
only_attrs = self.api_coerce_list(value=only_attrs)
ret = {}
sargs = {
"empty": empty,
"exclude_attrs": exclude_attrs,
"only_attrs": only_attrs,
"list_attrs": list_attrs,
"wrap_name": False,
"wrap_item_attr": wrap_item_attr,
}
for attr in self.api_attrs():
if attr in exclude_attrs or (only_attrs and attr not in only_attrs):
continue
value = getattr(self, attr, None)
is_model = isinstance(value, ApiModel)
value = value.serialize(**sargs) if is_model else value
include = (value is None and empty) or value is not None
ret.update({attr: value} if include else {})
if wrap_name:
ret = {self.API_NAME: ret}
return ret
[docs]class ApiList(ApiModel):
"""Model for an array in the API."""
API_ITEM_ATTR = None
""":obj:`str`: Name of :attr:`API_ITEM_CLS` used in API calls."""
API_ITEM_CLS = None
""":class:`ApiItem`: Item class this list class holds."""
LIST = None
""":obj:`list` of :attr:`API_ITEM_CLS`: List container for this class."""
T_CLS_STR = "{obj.__class__.__name__}({attrs}) with {count} {item_cls} objects"
""":obj:`str`: Template for class name and attrs in str."""
[docs] def __init__(self, *args, **kwargs):
"""Constructor.
Args:
*args:
Items of type :attr:`API_ITEM_CLS`:
Will be added to :attr:`LIST`.
**kwargs:
attr from :attr:`API_ITEM_ATTR` (:obj:`list`):
List of :attr:`API_ITEM_CLS` to add to :attr:`LIST`.
rest of kwargs:
Set on this object with type checking using.
"""
super(ApiList, self).__setattr__("LIST", [])
# support obj(item1, item2, **kwargs)
items = list(args)
# support obj(item_attr=value)
if self.API_ITEM_ATTR in kwargs:
value = kwargs.pop(self.API_ITEM_ATTR)
# support obj(self.API_ITEM_ATTR=(item1, item2))
if isinstance(value, tuple):
value = list(value)
# support obj(self.API_ITEM_ATTR=None)
if value is None:
value = []
# support obj(self.API_ITEM_ATTR=item1)
if not isinstance(value, list):
value = [value]
# finally, support obj(self.API_ITEM_ATTR=[item1, item2])
items += value
self.LIST = items
for attr in self.api_attrs():
setattr(self, attr, kwargs.pop(attr, None))
for attr, value in kwargs.items():
setattr(self, attr, value)
[docs] def __str__(self):
"""Show object info using :meth:`ApiModel.api_attrs_str`.
Returns:
:obj:`str`
"""
show_attrs = self.api_attrs_str()
attrs = []
for attr in show_attrs:
value = getattr(self, attr, None)
tmpl = self.T_ATTR_STR if attr in self.API_COMPLEX else self.T_ATTR_REPR
attrs.append(tmpl.format(attr=attr, value=value))
attrs = self.T_ATTR_JOIN.join(attrs)
return self.T_CLS_STR.format(
obj=self, attrs=attrs, count=len(self), item_cls=self.api_item_cls_str()
)
[docs] def __repr__(self):
"""Show object info using :meth:`ApiModel.api_attrs_repr`.
Returns:
:obj:`str`
"""
show_attrs = self.api_attrs_repr()
attrs = []
tmpl = self.T_ATTR_REPR
for attr in show_attrs:
value = getattr(self, attr, None)
attrs.append(tmpl.format(attr=attr, value=value))
value = [format(i) for i in self]
attrs.append(tmpl.format(attr=self.API_ITEM_ATTR, value=value))
attrs = self.T_ATTR_JOIN.join(attrs)
return self.T_CLS_REPR.format(obj=self, attrs=attrs)
[docs] def __len__(self):
"""Return length of list container :attr:`LIST`.
Returns:
:obj:`int`
"""
return len(self.LIST)
[docs] def __contains__(self, value):
"""Support in operand.
Args:
value (:obj:`object`):
Check if this value in :attr:`LIST`.
Notes:
Will call :meth:`api_coerce_item` to check value is of type defined in
:attr:`API_ITEM_CLS`, but will return False if value is of wrong type
and can not be coerced.
Returns:
:obj:`bool`:
"""
try:
value = self.api_coerce_item(item=value, items=None, attr=None, op="in")
except Exception:
return False
return value in self.LIST
[docs] def __add__(self, value):
"""Support + operand.
Args:
value (:obj:`ApiList` or :obj:`list` or :obj:`tuple`):
ApiList of this same type or a python list type to combine
with this object.
Notes:
This will use :meth:`api_coerce_items` to perform type checking
on each item in value.
Returns:
:obj:`ApiList`:
A new instance with list items from this object combined with
list items from value.
"""
api_attrs = self.api_attrs()
val_items = self.api_coerce_items(attr=None, value=value, op="+=")
mod_items = val_items + self.LIST
attrs = {k: getattr(self, k, None) for k, v in api_attrs.items()}
return self.__class__(*mod_items, **attrs)
[docs] def __iadd__(self, value):
"""Support += operand.
Args:
value (:obj:`ApiList` or :obj:`list` or :obj:`tuple`):
ApiList of this same type or a python list type to combine
with this object.
Notes:
This will use :meth:`api_coerce_items` to perform type checking
on each item in value.
Returns:
:obj:`ApiList`:
This object with list items from value appended.
"""
mod_items = self.api_coerce_items(attr=None, value=value, op="+=")
self.LIST += mod_items
return self
[docs] def __getitem__(self, value):
"""Support indexing of list container.
Args:
value (:obj:`int`):
Index of item to retrieve from :attr:`LIST`.
Returns:
:attr:`API_ITEM_CLS`:
The type of object this list contains.
"""
return self.LIST[value]
[docs] def __setattr__(self, attr, value):
"""Enforce type checking for attr and value.
Args:
attr (:obj:`str`):
Attribute to set on this object.
value (:obj:`object`):
Value to perform type checking on using
:func:`ApiModel.api_coerce_value`.
Notes:
If attr is either "LIST" or :attr:`API_ITEM_ATTR`, will use
:meth:`api_coerce_items` to perform type checking on each item in value.
If attr is same as :attr:`API_ITEM_ATTR`, the attr will
be changed to "LIST".
"""
if attr in [self.API_ITEM_ATTR, "LIST"]:
value = self.api_coerce_items(attr=attr, value=value, op="=")
attr = "LIST"
else:
value = self.api_coerce_value(attr=attr, value=value)
super(ApiList, self).__setattr__(attr, value)
[docs] def api_coerce_items(self, attr, value, op):
"""Check that value is a list type and that all items value are the proper type.
Args:
attr (:obj:`str`):
Attribute being set on this object.
value (:obj:`ApiList` or :obj:`list` or :obj:`tuple`):
The list object holding items that should have type checking.
op (:obj:`str`):
Operation being performed that called this method.
Used in exception messages.
Raises:
:exc:`pytan3.api_models.exceptions.ListTypeError`:
If value is not a :obj:`list` or :obj:`tuple` or an :obj:`ApiList`
that is the same type as this objects class.
Notes:
Will call :meth:`api_coerce_items_hook` before checking each item
in value, which can be overridden by subclasses to modify value.
Will call :meth:`api_coerce_item` to check each item in value
is of type defined in :attr:`API_ITEM_CLS`.
Returns:
:obj:`list`:
The list of items with their values checked and coerced.
"""
if value is None:
return []
is_list = isinstance(value, (list, tuple))
is_same = isinstance(value, self.__class__)
if not any([is_list, is_same]):
raise exceptions.ListTypeError(obj=self, attr=attr, value=value, op=op)
value = self.api_coerce_items_hook(attr=attr, value=value, op=op)
return [
self.api_coerce_item(item=i, items=value, attr=attr, op=op) for i in value
]
[docs] def api_coerce_items_hook(self, attr, value, op):
"""Check hook that allows subclasses to modify list items.
Args:
attr (:obj:`str`):
Attribute being set on this object.
value (:obj:`ApiList` or :obj:`list` or :obj:`tuple`):
The list object holding items.
op (:obj:`str`):
Operation being performed that called this method.
Notes:
By default, this does nothing but return value unchanged.
Returns:
:obj:`list`:
The list of items modified or as is.
"""
return value
[docs] def api_coerce_item(self, item, items, attr, op):
"""Perform type checking of a list item.
Args:
item (:obj:`object`):
Item that is being checked.
items (:obj:`ApiList` or :obj:`list` or :obj:`tuple`):
The list object that item came from.
op (:obj:`str`):
Operation being performed that called this method.
Used in exception messages.
Notes:
Will coerce item into expected type if needed and possible.
Raises:
:exc:`pytan3.api_models.exceptions.ListItemTypeError`:
If item does not match the type from :obj:`API_ITEM_CLS`
and it can not be coerced into the expected type.
Returns:
:obj:`object`
"""
be_type = self.API_ITEM_CLS
be_map = {
float_types: self.api_coerce_float,
integer_types: self.api_coerce_int,
}
if be_type in be_map and not isinstance(item, be_type):
coercer = be_map[be_type]
item = coercer(value=item)
elif self.api_item_cls_is_complex():
if isinstance(item, dict):
item = be_type(**item)
elif isinstance(item, (list, tuple)):
item = be_type(*item)
if be_type is not None and not isinstance(item, be_type):
raise exceptions.ListItemTypeError(
obj=self, item=item, items=items, op=op, attr=attr, be_type=be_type
)
return item
[docs] @classmethod
def api_attrs_desc(cls):
"""Get simple and complex attributes of an ApiModel object in string format.
Notes:
Used by exceptions to add defined attributes to error messages.
Returns:
:obj:`list` of :obj:`str`
"""
lines = super(ApiList, cls).api_attrs_desc()
tmpl = cls.T_ATTR_DESC.format
desc = tmpl(api_type="List", attr=cls.API_ITEM_ATTR, attr_type=cls.API_ITEM_CLS)
lines.append(desc)
return lines
[docs] @classmethod
def api_item_cls_str(cls):
"""Get str of :attr:`API_ITEM_CLS`.
Returns:
:obj:`str`
"""
classes = cls.api_coerce_list(cls.API_ITEM_CLS)
names = [x.__name__ for x in classes if hasattr(x, "__name__")]
names = names or ["ANY TYPE"]
return ", ".join(names)
[docs] @classmethod
def api_item_cls_is_complex(cls):
"""Check if :attr:`API_ITEM_CLS` is an ApiModel sub class.
Returns:
:obj:`bool`
"""
check = cls.api_coerce_list(cls.API_ITEM_CLS)
return any(issubclass(x, (ApiItem, ApiList)) for x in check)
[docs] def append(self, value):
"""Support appending an item to list container.
Args:
value (:obj:`object`):
Item to append to :attr:`LIST`.
Notes:
Will call :meth:`api_coerce_item` to check value
is of type defined in :attr:`API_ITEM_CLS`.
"""
value = self.api_coerce_item(item=value, items=None, attr=None, op="append()")
self.LIST.append(value)
[docs] def remove(self, value):
"""Support removing an item from list container.
Args:
value (:obj:`object`):
Item to remove from :attr:`LIST`.
"""
value = self.api_coerce_item(item=value, items=None, attr=None, op="remove()")
return self.LIST.remove(value)
[docs] def pop(self, value=-1):
"""Support pop of an item from list container.
Args:
value (:obj:`object`, optional):
Item to pop from :attr:`LIST`.
Defaults to: -1.
"""
return self.LIST.pop(value)
[docs] def reverse(self):
"""Support reverse on list container."""
self.LIST.reverse()
[docs] def get_item_by_attr(self, value, attr="name", regex_value=False):
"""Support getting an item from list container by attr value.
Args:
value (:obj:`object`):
Value to check against the attr value of each item in :attr:`LIST`.
attr (:obj:`str`, optional):
Attribute to check if value matches.
Defaults to: "name".
regex_value (:obj:`bool`, optional):
Treat value as a regex pattern instead of comparing equality of value
to attr value.
Defaults to: False.
Raises:
:exc:`pytan3.api_models.exceptions.GetSingleItemError`:
If number of items in :attr:`LIST` whose attr value
matches value is either 0 or more than 1.
Returns:
:obj:`object`:
The object from :attr:`LIST` whose attr value matches value.
"""
items = []
for i in self.LIST:
ival = getattr(i, attr, None)
if regex_value and re.search(format(value), format(ival)):
items.append(i)
elif ival == value:
items.append(i)
if len(items) == 1:
return items[0]
raise exceptions.GetSingleItemError(
obj=self, value=value, attr=attr, regex_value=regex_value, items=items
)
[docs] def get_items_by_attr(self, value, attr="name", regex_value=False, new_list=False):
"""Support getting items from list container by attr value.
Args:
value (:obj:`object`):
Value to check against the attr value of each item in :attr:`LIST`.
attr (:obj:`str`, optional):
Attribute to check if value matches.
Defaults to: "name".
regex_value (:obj:`bool`, optional):
Treat value as a regex pattern instead of comparing equality of value
to attr value.
Defaults to: False.
new_list (:obj:`bool`, optional):
Return a new class of this objects type with matching items instead
of a regular python list type.
Defaults to: False.
Returns:
:obj:`list` of :obj:`object`:
All objects from :attr:`LIST` whose attr value matches value.
"""
items = []
for i in self.LIST:
ival = getattr(i, attr, None)
if regex_value and re.search(format(value), format(ival)):
items.append(i)
elif ival == value:
items.append(i)
return self.__class__(*items) if new_list else items
[docs] def pop_item_by_attr(self, value, attr="name", regex_value=False):
"""Support popping an item from list container by attr value.
Args:
value (:obj:`object`):
Value to check against the attr value of each item in :attr:`LIST`.
attr (:obj:`str`, optional):
Attribute to check if value matches.
Defaults to: "name".
regex_value (:obj:`bool`, optional):
Treat value as a regex pattern instead of comparing equality of value
to attr value.
Defaults to: False.
Raises:
:exc:`pytan3.api_models.exceptions.GetSingleItemError`:
If number of items in :attr:`LIST` whose attr value
matches value is either 0 or more than 1.
Returns:
:obj:`object`:
The object popped from :attr:`LIST` whose attr value matches value.
"""
item = self.get_item_by_attr(value, attr, regex_value)
return self.LIST.pop(self.LIST.index(item))
[docs] def pop_items_by_attr(self, value, attr="name", regex_value=False, new_list=False):
"""Support popping items from list container by attr value.
Args:
value (:obj:`object`):
Value to check against the attr value of each item in :attr:`LIST`.
attr (:obj:`str`, optional):
Attribute to check if value matches.
Defaults to: "name".
regex_value (:obj:`bool`, optional):
Treat value as a regex pattern instead of comparing equality of value
to attr value.
Defaults to: False.
new_list (:obj:`bool`, optional):
Return a new class of this objects type with matching items instead
of a regular python list type.
Defaults to: False.
Returns:
:obj:`list` of :obj:`object`:
All objects popped from :attr:`LIST` whose attr value matches value.
"""
items = self.get_items_by_attr(value, attr, regex_value, new_list)
for i in items:
self.LIST.remove(i)
return items
[docs] def sort(self, key=operator.attrgetter("id"), reverse=False):
"""Support sorting items in list container in place.
Args:
key (:obj:`object`, optional):
Key to sort items in :attr:`LIST` on.
Defaults to: operator.attrgetter("id").
reverse (:obj:`bool`, optional):
Reverse the sort mechanism.
Defaults to: False.
"""
self.LIST.sort(key=key, reverse=reverse)
[docs] def serialize(
self,
empty=False,
list_attrs=False,
exclude_attrs=None,
only_attrs=None,
wrap_name=True,
wrap_item_attr=True,
):
"""Serialize this object into a dict.
Args:
empty (:obj:`bool`, optional):
Include attrs that have a value of None when serializing.
Defaults to: False.
list_attrs (:obj:`bool`, optional):
Include simple attrs of :obj:`ApiList` when serializing.
Defaults to: False.
exclude_attrs (:obj:`list` of :obj:`str`, optional):
Exclude these attrs when serializing.
Defaults to: None.
only_attrs (:obj:`list` of :obj:`str`, optional):
Include only these attrs when serializing.
Defaults to: None.
wrap_name (:obj:`bool`, optional):
Wrap return in a dict with key of :attr:`ApiModel.API_NAME` and
value of return.
Defaults to: True.
wrap_item_attr (:obj:`bool`, optional):
Wrap return in a dict with key of :attr:`ApiList.API_ITEM_ATTR`
and value of return.
Only used when serializing items of type :obj:`ApiList`.
Defaults to: True.
Notes:
All child objects will get wrap_name=False.
If wrap_name and wrap_item_attr are both False,
return will be :obj:`list` of :obj:`dict`, otherwise return will be a
:obj:`dict`.
Returns:
:obj:`list` of :obj:`object` or :obj:`dict`
"""
exclude_attrs = self.api_coerce_list(value=exclude_attrs)
only_attrs = self.api_coerce_list(value=only_attrs)
sargs = {
"empty": empty,
"exclude_attrs": exclude_attrs,
"only_attrs": only_attrs,
"list_attrs": list_attrs,
"wrap_name": False,
"wrap_item_attr": wrap_item_attr,
}
simples = {}
if list_attrs:
for attr in self.api_attrs():
if attr in exclude_attrs or (only_attrs and attr not in only_attrs):
continue
value = getattr(self, attr, None)
is_model = isinstance(value, ApiModel)
value = value.serialize(**sargs) if is_model else value
include = (value is None and empty) or value is not None
simples.update({attr: value} if include else {})
items = [i.serialize(**sargs) if isinstance(i, ApiModel) else i for i in self]
if wrap_item_attr:
ret = {}
ret.update(simples)
sub_ret = {self.API_ITEM_ATTR: items}
if wrap_name:
sub_ret = {self.API_NAME: sub_ret}
ret.update(sub_ret)
return ret
if wrap_name:
ret = {}
ret.update(simples)
ret.update({self.API_NAME: items})
return ret
return items