PY3 = sys.version_info > (3,)
def min_smc_version(version): class NestedDict(collections.MutableMapping): 
    """ 
    Generic dict structure that can be used to objectify 
    complex json. This dict allows attribute access for data
    stored in the data dict by overridding getattr.
    """ 
    def __init__(self, data=None, **kwargs):
        self.data = data if data else {}
        self.update(self.data, **kwargs)

    def __setitem__(self, key, value):
        self.data[key] = value
    def __getitem__(self, key):
        return self.data[key]
    def __delitem__(self, key):
        del self.data[key]
    def __iter__(self):
        return iter(self.data)
    def __len__(self):
        return len(self.data)
    def __getattr__(self, key):
        if key in self:
            return self[key]
        raise AttributeError("%r object has no attribute %r" 
            % (self.__class__, key))  class cached_property(object):
    """
    Use for caching a property value on the instance. If the
    attribute is deleted, it will be recreated when called.
    """

    def __init__(self, func):
        self.func = func
        #self.__doc__ = func.__doc__

    def __get__(self, obj, cls):
        if obj is None:
            return self
        value = obj.__dict__[self.func.__name__] = self.func(obj)
        return value class classproperty(object):
    """
    Used for collection manager so objects can be accessed as a
    class property and also from the instance
    """

    def __init__(self, fget):
        self.fget = fget

    def __get__(self, instance, owner_cls):
        return self.fget(owner_cls) def exception(function):
    """
    If exception was specified for prepared_request,
    inject this into SMCRequest so it can be used for
    return if needed.
    """
    @functools.wraps(function)
    def wrapper(*exception, **kwargs):
        result = function(**kwargs)
        if exception:
            result.exception = exception[0]
        return result
    return wrapper def create_hook(function):
    """
    Provide a pre-filter to the create function that provides the ability
    to modify the element json before submitting to the SMC. To register
    a create hook, set on the class or top level Element class to enable
    this on any descendent of Element::
    
        Element._create_hook = classmethod(myhook)
    
    The hook should be a callable and take two arguments, cls, json and 
    return the json after modification. For example::
    
        def myhook(cls, json):
            print("Called with class: %s" % cls)
            if 'address' in json:
                json['address'] = '2.2.2.2'
            return json
    """
    @functools.wraps(function)
    def run(cls, json, **kwargs):
        if hasattr(cls, '_create_hook'):
            json = cls._create_hook(json)
        return function(cls, json, **kwargs)
    return run def with_metaclass(mcls):
    """
    Metaclass class decorator for py2 and py3
    """
    def decorator(cls):
        body = vars(cls).copy()
        # clean out class body
        body.pop('__dict__', None)
        body.pop('__weakref__', None)
        return mcls(cls.__name__, cls.__bases__, body)
    return decorator class SMCRequest(object):
    """
    SMCRequest represents the data structure that will be submitted to the web
    layer for submission to the SMC API.

    :param str href: href for request, required by all methods
    :param dict json: json to submit, required by create, update
    :param dict params: query string parameters
    :param str filename: name of file for download, optional for create
    :param str etag: etag of element, required for update
    """
    _session_manager = None
    
    def __init__(self, href=None, json=None, params=None, filename=None,
                 etag=None, user_session=None, **kwargs):
        
        #: Filename if a file download is requested
        self.filename = filename
        #: dictionary of query parameters
        self.params = params
        #: href for this request
        self.href = href
        #: ETag for PUT or DELETE request modifications
        self.etag = etag
        #: JSON data to send in request
        self.json = {} if json is None else json
        
        # Only used in the case of streaming file download/upload
        self.files = None
        #: Default headers
        self.headers = {'Content-Type': 'application/json'}
        
        # Optional user session for this request
        #self.user_session = user_session # smc.api.session.Session
        
        for k, v in kwargs.items():
            setattr(self, k, v)
    
    def __call__(self, session):
        pass
    
    def create(self):
        return self._make_request(method='POST')

    def delete(self):
        return self._make_request(method='DELETE')

    def update(self):
        return self._make_request(method='PUT')

    def read(self):
        return self._make_request(method='GET')
    
    def _make_request(self, method):
        err = None
        result = None
        try:
            # Obtain the session
            session = _get_session(getattr(self, '_session_manager', None))
            
            if method == 'GET':
                if not self.href:
                    self.href = session.entry_points.get('elements')
            
            result = send_request(session, method, self)
            
        except SMCOperationFailure as e:
            result = e.smcresult
            try:
                err = self.exception(result.msg)  # Exception set
            except AttributeError:
                pass
        except (SessionManagerNotFound, SMCConnectionError,
                IOError, TypeError) as e:
            err = e
        finally:
            if err:
                raise err
            return result

    def __str__(self):
        sb = []
        for key in self.__dict__:
            sb.append(
                "{key}='{value}'".format(
                    key=key,
                    value=self.__dict__[key]))
        return 'SMCRequest({})'.format(','.join(sb))     def fetch_meta_by_name(name, filter_context=None, exact_match=True):
    """
    Find the element based on name and optional filters. By default, the
    name provided uses the standard filter query. Additional filters can
    be used based on supported collections in the SMC API.

    :method: GET
    :param str name: element name, can use * as wildcard
    :param str filter_context: further filter request, i.e. 'host', 'group',
        'single_fw', 'network_elements', 'services',
        'services_and_applications'
    :param bool exact_match: Do an exact match by name, note this still can
        return multiple entries
    :rtype: SMCResult
    """
    result = SMCRequest(
        params={'filter': name,
                'filter_context': filter_context,
                'exact_match': exact_match}).read()
    
    if not result.json:
        result.json = []
    return result def fetch_entry_point(name):
    """
    Get the entry point href based on the input name. Entry points are
    cached during the connection and can be accessed through the session
    by calling session.cache.get_all_entry_points()

    :method: GET
    :param str name: valid element entry point, i.e. 'host', 'iprange', etc
    :raises UnsupportedEntryPoint: entry point not available in this API version
    :return: href pulled from API cache
    :rtype: str
    """
    return entry_point().get(name) # from entry point cache class ElementNotFound(SMCException):
    """
    Generic exception when an attempt is made to load an element
    that is not found.
    """ class CreateElementFailed(SMCException):
    """
    Generic exception when there was a failure calling a
    create method
    """ class ModificationFailed(SMCException):
    """
    Used when making generic modifications to elements.
    """ class ResourceNotFound(SMCException):
    """
    Used to indicate a resource link is not found on the
    queried node. For example, the :py:class:`smc.core.engine.Engine` class
    will expose available resources but some engines may not have those
    links.
    """ class DeleteElementFailed(SMCException):
    """
    Used when deletion fails, typically due to dependencies
    for the target element
    """ class FetchElementFailed(SMCException):
    """
    Failure when fetching results
    """ class UpdateElementFailed(SMCException):
    """
    Failure when updating element. When failure is due to ETag
    being invalid, target was modified before change was
    submitted. A resubmit would be required.
    """ class UnsupportedEntryPoint(SMCException):
    """
    An entry point was specified that was not found in this API
    version. This is likely due to using an older version of the
    SMC API that does not support that feature. The exception is
    thrown specifying the entry point specified.
    """ def bytes_to_unicode(s, encoding='utf-8', errors='replace'):
    """
    Helper to convert byte string to unicode string for user based input

    :param str s: string to decode
    :param str encoding: utf-8 by default
    :param str errors: what to do when decoding fails
    :return: unicode utf-8 string
    """
    if compat.PY3:
        return str(s, 'utf-8') if isinstance(s, bytes) else s
    return s if isinstance(s, unicode) else s.decode(encoding, errors) def unicode_to_bytes(s, encoding='utf-8', errors='replace'):
    """
    Helper to convert unicode strings to bytes for data that needs to be
    written to on output stream (i.e. terminal)
    For Python 3 this should be called str_to_bytes

    :param str s: string to encode
    :param str encoding: utf-8 by default
    :param str errors: what to do when encoding fails
    :return: byte string utf-8 encoded
    """
    return s if isinstance(s, str) else s.encode(encoding, errors) def merge_dicts(dict1, dict2, append_lists=False):
    """
    Merge the second dict into the first
    Not intended to merge list of dicts.

    :param append_lists: If true, instead of clobbering a list with the
        new value, append all of the new values onto the original list.
    """
    for key in dict2:
        if isinstance(dict2[key], dict):
            if key in dict1 and key in dict2:
                merge_dicts(dict1[key], dict2[key], append_lists)
            else:
                dict1[key] = dict2[key]
        # If the value is a list and the ``append_lists`` flag is set,
        # append the new values onto the original list
        elif isinstance(dict2[key], list) and append_lists:
            # The value in dict1 must be a list in order to append new
            # values onto it. Don't add duplicates.
            if key in dict1 and isinstance(dict1[key], list):
                dict1[key].extend(
                    [k for k in dict2[key] if k not in dict1[key]])
            else:
                dict1[key] = dict2[key]
        else:
            dict1[key] = dict2[key]  # Overwrite list or scalar class RequestAction(object):
    """
    A Generic request action provides a simple interface to
    building and executing an SMCRequest. This will raise
    the exception specified if the resource is not found.
    
    Valid parameters that can be provided in kwargs are:
    
    :param str method: method for which to call. Can be
        'read', 'create', 'update' or 'delete' (default: 'read')
    :param str resource: The element resource to act on. If the
        href is already known, this can be provided as href
    :param bool raw_result: Return the raw SMCResult
    """
    def make_request(self, *exception, **kwargs):
        raw_result = kwargs.pop('raw_result', False)
        method = kwargs.pop('method', 'read')
        ex = exception[0] if exception else ActionCommandFailed
        if 'resource' in kwargs:
            try:
                kwargs.update(href=self.data.get_link(
                    kwargs.pop('resource')))
            except ResourceNotFound as e:
                raise ex(e)
        
        request = SMCRequest(**kwargs)
        request.exception = ex
        result = getattr(request, method)()
        if raw_result:
            return result
        return result.json class UnicodeMixin(object):
    """
    Mixin used to stage for supporting python 3. After py2 support is dropped
    then this can be removed. Py2 requires that __str__ returns bytes whereas
    py3 should return unicode. Each py2 supported class will have a __unicode__
    method.

    Consider migrating to future module and use their class decorator
    from future.utils import python_2_unicode_compatible
    \@python_2_unicode_compatible
    From: http://python-future.org/what_else.html
    """
    import sys
    if sys.version_info > (3, 0):
        def __str__(x): return x.__unicode__()
    else:
        def __str__(x): return unicode(x).encode('utf-8') def element_resolver(elements, do_raise=True):
    """
    Element resolver takes either a single class instance
    or a list of elements to resolve the href. It does
    not assume a specific interface, instead if it's
    a class, it just needs an 'href' attribute that should
    hold the http url for the resource. If a list is
    provided, a list is returned. If you want to suppress
    raising an exception and just return None or [] instead,
    set do_raise=False.

    :raises ElementNotFound: if this is of type Element,
        ElementLocator will attempt to retrieve meta if it
        doesn't already exist but the element was not found.
    """
    if isinstance(elements, list):
        e = []
        for element in elements:
            try:
                e.append(element.href)
            except AttributeError:
                e.append(element)
            except smc.api.exceptions.ElementNotFound:
                if do_raise:
                    raise
        return e
    try:
        return elements.href
    except AttributeError:
        return elements
    except smc.api.exceptions.ElementNotFound:
        if do_raise:
            raise
import collections
import smc.base.collection
from smc.compat import string_types
from smc.base.structs import NestedDict
from smc.base.decorators import cached_property, classproperty, exception,\
    create_hook, with_metaclass
from smc.api.common import SMCRequest, fetch_meta_by_name, fetch_entry_point
from smc.api.exceptions import ElementNotFound, \
    CreateElementFailed, ModificationFailed, ResourceNotFound,\
    DeleteElementFailed, FetchElementFailed, UpdateElementFailed,\
    UnsupportedEntryPoint
from .util import bytes_to_unicode, unicode_to_bytes, merge_dicts
from smc.base.mixins import RequestAction, UnicodeMixin
from smc.base.util import element_resolver
        from smc.elements.other import Category
        from smc.administration.tasks import Task
        from smc.core.resource import History
"""
Classes representing basic models for data obtained or retrieved from the SMC

ElementBase is the top level parent class that provides the instance level
cache, meta data and basic methods to model the retrieved elements.

Element is the common interface that exposes methods and a retrieval
descriptor for elements that have a direct entry point in the SMC API.
The href descriptor provides a transparent way to retrieve the object using
entry point filters based on providing only the ``name`` attribute on an
instance. All instance data is lazy loaded. When an element is first retrieved,
a single query is made to find the element and it's meta data. Subsequent access
to methods or properties that access the elements ``data`` attribute will cause
a second query to obtain the elements full json.

The SubElement interface defines elements without a direct entry point and instead
are references of Element classes, for example, obtaining a reference to an engine
interface and it's sub-interfaces. SubElements are therefore not directly searchable
through entry points like :class:`Element` instances.

A class attribute 'typeof' is used by a filter when an object needs to
be loaded. This value should be the elements entry point. SubElement
classes may also have the typeof attribute which will then register
the class with the registry metaclass for use with factory functions.

Element class relationship (abbreviated)::

                             ElementBase (object)
                                   |
                                data = ElementCache()
                                meta = Meta(href,name,type)
                                update()
                                delete()
                                modify_attribute()
                                 .....
                                   |
                         ----------------------
                         |                    |
    Element (ElementBase)                     SubElement (ElementBase)
        |------------------------------------------------------|
      href = ElementLocator()                                href
      name                                                   name
      objects                                                ....
      export()
      rename
      category_tags
      referenced_by
      ...

Classes that do not require state on retrieved json or provide basic
container functionality may inherit from object.
"""


@exception
def prepared_request(*exception, **kwargs):  # @UnusedVariable
    """
    Prepared request is a wrapper to allow an exception to
    be thrown to wrap the SMCResult. Exception is optional.
    If not provided, the SMCResult object is returned,
    otherwise it is only thrown if SMC reports an error.
    """
    return SMCRequest(**kwargs)


def LoadElement(href, only_etag=False):
    """
    Return an instance of a element as a ElementCache dict
    used as a cache.
    
    :rtype ElementCache
    """
    request = SMCRequest(href=href)
    request.exception = FetchElementFailed
    result = request.read()
    if only_etag:
        return result.etag
    return ElementCache(
        result.json, etag=result.etag)



@create_hook