class BaseAPI(object):
    """
        Basic api class for
    """
    tokens = []
    _last_used = 0
    end_point = "https://api.digitalocean.com/v2/"

    def __init__(self, *args, **kwargs):
        self.token = os.getenv("DIGITALOCEAN_ACCESS_TOKEN", "")
        self.end_point = os.getenv("DIGITALOCEAN_END_POINT", "https://api.digitalocean.com/v2/")
        self._log = logging.getLogger(__name__)

        self._session = requests.Session()

        for attr in kwargs.keys():
            setattr(self, attr, kwargs[attr])

        parsed_url = urlparse.urlparse(self.end_point)
        if not parsed_url.scheme or not parsed_url.netloc:
            raise EndPointError("Provided end point is not a valid URL. Please use a valid URL")

        if not parsed_url.path:
            self.end_point += '/'

    def __getstate__(self):
        state = self.__dict__.copy()
        # The logger is not pickleable due to using thread.lock
        del state['_log']
        return state

    def __setstate__(self, state):
        self.__dict__ = state
        self._log = logging.getLogger(__name__)

    def __perform_request(self, url, type=GET, params=None):
        """
            This method will perform the real request,
            in this way we can customize only the "output" of the API call by
            using self.__call_api method.
            This method will return the request object.
        """
        if params is None:
            params = {}

        if not self.token:
            raise TokenError("No token provided. Please use a valid token")

        url = urlparse.urljoin(self.end_point, url)

        # lookup table to find out the appropriate requests method,
        # headers and payload type (json or query parameters)
        identity = lambda x: x
        json_dumps = lambda x: json.dumps(x)
        lookup = {
            GET: (self._session.get, {'Content-type': 'application/json'}, 'params', identity),
            PATCH: (requests.patch, {'Content-type': 'application/json'},
                    'data', json_dumps),
            POST: (requests.post, {'Content-type': 'application/json'}, 'data',
                   json_dumps),
            PUT: (self._session.put, {'Content-type': 'application/json'}, 'data',
                  json_dumps),
            DELETE: (self._session.delete,
                     {'content-type': 'application/json'},
                     'data', json_dumps),
        }

        requests_method, headers, payload, transform = lookup[type]
        agent = "{0}/{1} {2}/{3}".format('python-digitalocean',
                                         __version__,
                                         requests.__name__,
                                         requests.__version__)
        headers.update({'Authorization': 'Bearer ' + self.token,
                        'User-Agent': agent})
        kwargs = {'headers': headers, payload: transform(params)}

        timeout = self.get_timeout()
        if timeout:
            kwargs['timeout'] = timeout

        # remove token from log
        headers_str = str(headers)
        for i, token in enumerate(self.tokens):
            headers_str = headers_str.replace(token.strip(), 'TOKEN%s' % i)
        self._log.debug('%s %s %s:%s %s %s' %
                        (type, url, payload, params, headers_str, timeout))

        return requests_method(url, **kwargs)

    def __deal_with_pagination(self, url, method, params, data):
        """
            Perform multiple calls in order to have a full list of elements
            when the API are "paginated". (content list is divided in more
            than one page)
        """
        all_data = data
        while data.get("links", {}).get("pages", {}).get("next"):
            url, query = data["links"]["pages"]["next"].split("?", 1)

            # Merge the query parameters
            for key, value in urlparse.parse_qs(query).items():
                params[key] = value

            data = self.__perform_request(url, method, params).json()

            # Merge the dictionaries
            for key, value in data.items():
                if isinstance(value, list) and key in all_data:
                    all_data[key] += value
                else:
                    all_data[key] = value

        return all_data

    def __init_ratelimit(self, headers):
        # Add the account requests/hour limit
        self.ratelimit_limit = headers.get('Ratelimit-Limit', None)
        # Add the account requests remaining
        self.ratelimit_remaining = headers.get('Ratelimit-Remaining', None)
        # Add the account requests limit reset time
        self.ratelimit_reset = headers.get('Ratelimit-Reset', None)

    @property
    def token(self):
        # use all the tokens round-robin style
        if self.tokens:
            self._last_used = (self._last_used + 1) % len(self.tokens)
            return self.tokens[self._last_used]
        return ""

    @token.setter
    def token(self, token):
        self._last_used = 0
        if isinstance(token, list):
            self.tokens = token
        else:
            # for backward compatibility
            self.tokens = [token]

    def get_timeout(self):
        """
            Checks if any timeout for the requests to DigitalOcean is required.
            To set a timeout, use the REQUEST_TIMEOUT_ENV_VAR environment
            variable.
        """
        timeout_str = os.environ.get(REQUEST_TIMEOUT_ENV_VAR)
        if timeout_str:
            try:
                return float(timeout_str)
            except:
                self._log.error('Failed parsing the request read timeout of '
                                '"%s". Please use a valid float number!' %
                                        timeout_str)
        return None

    def get_data(self, url, type=GET, params=None):
        """
            This method is a basic implementation of __call_api that checks
            errors too. In case of success the method will return True or the
            content of the response to the request.

            Pagination is automatically detected and handled accordingly
        """
        if params is None:
            params = dict()

        # If per_page is not set, make sure it has a sane default
        if type is GET:
            params.setdefault("per_page", 200)

        req = self.__perform_request(url, type, params)
        if req.status_code == 204:
            return True

        if req.status_code == 404:
            raise NotFoundError()

        try:
            data = req.json()

        except ValueError as e:
            raise JSONReadError(
                'Read failed from DigitalOcean: %s' % str(e)
            )

        if not req.ok:
            msg = [data[m] for m in ("id", "message") if m in data][1]
            raise DataReadError(msg)

        # init request limits
        self.__init_ratelimit(req.headers)

        # If there are more elements available (total) than the elements per
        # page, try to deal with pagination. Note: Breaking the logic on
        # multiple pages,
        pages = data.get("links", {}).get("pages", {})
        if pages.get("next") and "page" not in params:
            return self.__deal_with_pagination(url, type, params, data)
        else:
            return data

    def __str__(self):
        return "<%s>" % self.__class__.__name__

    def __unicode__(self):
        return u"%s" % self.__str__()

    def __repr__(self):
        return str(self) class Action(BaseAPI):
    def __init__(self, *args, **kwargs):
        self.id = None
        self.status = None
        self.type = None
        self.started_at = None
        self.completed_at = None
        self.resource_id = None
        self.resource_type = None
        self.region = None
        self.region_slug = None
        # Custom, not provided by the json object.
        self.droplet_id = None

        super(Action, self).__init__(*args, **kwargs)

    @classmethod
    def get_object(cls, api_token, action_id):
        """
            Class method that will return a Action object by ID.
        """
        action = cls(token=api_token, id=action_id)
        action.load_directly()
        return action

    def load_directly(self):
        action = self.get_data("actions/%s" % self.id)
        if action:
            action = action[u'action']
            # Loading attributes
            for attr in action.keys():
                setattr(self, attr, action[attr])

    def load(self):
        if not self.droplet_id:
            action = self.load_directly()
        else:
            action = self.get_data(
                "droplets/%s/actions/%s" % (
                    self.droplet_id,
                    self.id
                )
            )
        if action:
            action = action[u'action']
            # Loading attributes
            for attr in action.keys():
                setattr(self, attr, action[attr])

    def wait(self, update_every_seconds=1, repeat=20):
        """
            Wait until the action is marked as completed or with an error.
            It will return True in case of success, otherwise False.

            Optional Args:
                update_every_seconds - int : number of seconds to wait before
                    checking if the action is completed.
        """
        counter = 0
        while self.status == u'in-progress':
            sleep(update_every_seconds)
            self.load()
            counter += 1
            if counter > repeat:
                break

        return self.status == u'completed'

    def __str__(self):
        return "<Action: %s %s %s>" % (self.id, self.type, self.status) class Balance(BaseAPI):
    def __init__(self, *args, **kwargs):
        self.month_to_date_balance = None
        self.account_balance = None
        self.month_to_date_usage = None
        self.generated_at = None

        super(Balance, self).__init__(*args, **kwargs)

    @classmethod
    def get_object(cls, api_token):
        """
            Class method that will return an Balance object.
        """
        acct = cls(token=api_token)
        acct.load()
        return acct

    def load(self):
        # URL https://api.digitalocean.com/customers/my/balance
        balance = self.get_data("customers/my/balance")

        for attr in balance.keys():
            setattr(self, attr, balance[attr])

    def __str__(self):
        return "<Balance: %s>" % (self.account_balance) class Certificate(BaseAPI):
    """
    An object representing an SSL Certificate stored on DigitalOcean.

    Attributes accepted at creation time:

    Args:
        name (str): A name for the Certificate
        private_key (str, optional): The contents of a PEM-formatted
            private-key corresponding to the SSL certificate. Only used
            when uploading a custom certificate.
        leaf_certificate (str, optional): The contents of a PEM-formatted
            public SSL certificate. Only used when uploading a custom
            certificate.
        certificate_chain (str, optional): The full PEM-formatted trust chain
            between the certificate authority's certificate and your domain's
            SSL certificate. Only used when uploading a custom certificate.
        dns_names (:obj:`str`): A list of fully qualified domain names (FQDNs)
            for which the certificate will be issued by Let's Encrypt
        type (str): Specifies the type of certificate to be created. The value
            should be "custom" for a user-uploaded certificate or
            "lets_encrypt" for one automatically generated with Let's Encrypt.

    Attributes returned by API:
        name (str): The name of the Certificate
        id (str): A unique identifier for the Certificate
        not_after (str): A string that represents the Certificate's expiration
            date.
        sha1_fingerprint (str): A unique identifier for the Certificate
            generated from its SHA-1 fingerprint
        created_at (str): A string that represents when the Certificate was
            created
        dns_names (:obj:`str`): A list of fully qualified domain names (FQDNs)
            for which a Let's Encrypt generated certificate is issued.
        type (str): Specifies the type of certificate. The value will be
            "custom" for a user-uploaded certificate or "lets_encrypt" for one
            automatically generated with Let's Encrypt.
        state (str): Represents the current state of the certificate. It may be
            "pending", "verified", or "errored".
    """
    def __init__(self, *args, **kwargs):
        self.id = ""
        self.name = None
        self.private_key = None
        self.leaf_certificate = None
        self.certificate_chain = None
        self.not_after = None
        self.sha1_fingerprint = None
        self.created_at = None
        self.dns_names = []
        self.type = None
        self.state = None

        super(Certificate, self).__init__(*args, **kwargs)

    @classmethod
    def get_object(cls, api_token, cert_id):
        """
            Class method that will return a Certificate object by its ID.
        """
        certificate = cls(token=api_token, id=cert_id)
        certificate.load()
        return certificate

    def load(self):
        """
            Load the Certificate object from DigitalOcean.

            Requires self.id to be set.
        """
        data = self.get_data("certificates/%s" % self.id)
        certificate = data["certificate"]

        for attr in certificate.keys():
            setattr(self, attr, certificate[attr])

        return self

    def create(self):
        """
            Create the Certificate
        """
        params = {
            "name": self.name,
            "type": self.type,
            "dns_names": self.dns_names,
            "private_key": self.private_key,
            "leaf_certificate": self.leaf_certificate,
            "certificate_chain": self.certificate_chain
        }

        data = self.get_data("certificates", type=POST, params=params)

        if data:
            self.id = data['certificate']['id']
            self.not_after = data['certificate']['not_after']
            self.sha1_fingerprint = data['certificate']['sha1_fingerprint']
            self.created_at = data['certificate']['created_at']
            self.type = data['certificate']['type']
            self.dns_names = data['certificate']['dns_names']
            self.state = data['certificate']['state']

        return self

    def destroy(self):
        """
            Delete the Certificate
        """
        return self.get_data("certificates/%s" % self.id, type=DELETE)

    def __str__(self):
        return "<Certificate: %s %s>" % (self.id, self.name) class Domain(BaseAPI):
    def __init__(self, *args, **kwargs):
        self.name = None
        self.ttl = None
        self.zone_file = None
        self.ip_address = None

        super(Domain, self).__init__(*args, **kwargs)

    @classmethod
    def get_object(cls, api_token, domain_name):
        """
            Class method that will return a Domain object by ID.
        """
        domain = cls(token=api_token, name=domain_name)
        domain.load()
        return domain

    def load(self):
        # URL https://api.digitalocean.com/v2/domains
        domains = self.get_data("domains/%s" % self.name)
        domain = domains['domain']

        for attr in domain.keys():
            setattr(self, attr, domain[attr])

    def destroy(self):
        """
            Destroy the domain by name
        """
        # URL https://api.digitalocean.com/v2/domains/[NAME]
        return self.get_data("domains/%s" % self.name, type=DELETE)

    def create_new_domain_record(self, *args, **kwargs):
        """
            Create new domain record.
            https://developers.digitalocean.com/#create-a-new-domain-record

            Args:
                type: The record type (A, MX, CNAME, etc).
                name: The host name, alias, or service being defined by the record
                data: Variable data depending on record type.

            Optional Args:
                priority: The priority of the host
                port: The port that the service is accessible on
                weight: The weight of records with the same priority
                ttl: This value is the time to live for the record, in seconds.
                flags: An unsigned integer between 0-255 used for CAA records.
                tag: The parameter tag for CAA records. Valid values are "issue",
                    "issuewild", or "iodef".
        """
        data = {
            "type": kwargs.get("type", None),
            "name": kwargs.get("name", None),
            "data": kwargs.get("data", None)
        }

        #  Optional Args
        if kwargs.get("priority", None) != None:
            data['priority'] = kwargs.get("priority", None)

        if kwargs.get("port", None):
            data['port'] = kwargs.get("port", None)

        if kwargs.get("weight", None) != None:
            data['weight'] = kwargs.get("weight", None)

        if kwargs.get("ttl", None):
            data['ttl'] = kwargs.get("ttl", 1800)

        if kwargs.get("flags", None) != None:
            data['flags'] = kwargs.get("flags", None)

        if kwargs.get("tag", None):
            data['tag'] = kwargs.get("tag", "issue")

        if self.ttl:
            data['ttl'] = self.ttl

        return self.get_data(
            "domains/%s/records" % self.name,
            type=POST,
            params=data
        )

    def update_domain_record(self, *args, **kwargs):
        """
            Args:
                type: The record type (A, MX, CNAME, etc).
                name: The host name, alias, or service being defined by the record
                data: Variable data depending on record type.
                priority: The priority of the host
                port: The port that the service is accessible on
                weight: The weight of records with the same priority
        """
        data = {
            'id': kwargs.get("id", None),
            'domain': kwargs.get("domain", None)
        }

        if kwargs.get("data", None):
            data['data'] = kwargs.get("data", None)

        if kwargs.get("type", None):
            data['type'] = kwargs.get("type", None)

        if kwargs.get("name", None):
            data['name'] = kwargs.get("name", None)

        if kwargs.get("port", None):
            data['port'] = kwargs.get("port", None)

        if kwargs.get("weight", None):
            data['weight'] = kwargs.get("weight", None)

        return self.get_data(
            "domains/%s/records/%s" % (data['domain'], data['id']),
            type=PUT,
            params=data
        )

    def delete_domain_record(self, *args, **kwargs):

        data = {
            'id': kwargs.get("id", None)
        }

        return self.get_data(
            "domains/%s/records/%s" % (self.name, data['id']),
            type=DELETE
        )

    def create(self):
        """
            Create new domain
        """
        # URL https://api.digitalocean.com/v2/domains
        data = {
            "name": self.name,
            "ip_address": self.ip_address,
        }

        domain = self.get_data("domains", type=POST, params=data)
        return domain

    def get_records(self, params=None):
        """
            Returns a list of Record objects
        """
        if params is None:
            params = {}
        
        # URL https://api.digitalocean.com/v2/domains/[NAME]/records/
        records = []
        data = self.get_data("domains/%s/records/" % self.name, type=GET, params=params)

        for record_data in data['domain_records']:

            record = Record(domain_name=self.name, **record_data)
            record.token = self.token
            records.append(record)

        return records

    def __str__(self):
        return "%s" % (self.name) class Droplet(BaseAPI):
    """Droplet management

    Attributes accepted at creation time:

    Args:
        name (str): name
        size_slug (str): droplet size
        image (str): image name to use to create droplet
        region (str): region
        ssh_keys (:obj:`str`, optional): list of ssh keys
        backups (bool): True if backups enabled
        ipv6 (bool): True if ipv6 enabled
        private_networking (bool): True if private networking enabled
        user_data (str): arbitrary data to pass to droplet
        volumes (:obj:`str`, optional): list of blockstorage volumes
        monitoring (bool): True if installing the DigitalOcean monitoring agent
        vpc_uuid (str, optional): ID of a VPC in which the Droplet will be created

    Attributes returned by API:
        * id (int): droplet id
        * memory (str): memory size
        * vcpus (int): number of vcpus
        * disk (int): disk size in GB
        * locked (bool): True if locked
        * created_at (str): creation date in format u'2014-11-06T10:42:09Z'
        * status (str): status, e.g. 'new', 'active', etc
        * networks (dict): details of connected networks
        * kernel (dict): details of kernel
        * backup_ids (:obj:`int`, optional): list of ids of backups of this droplet
        * snapshot_ids (:obj:`int`, optional): list of ids of snapshots of this droplet
        * action_ids (:obj:`int`, optional): list of ids of actions
        * features (:obj:`str`, optional): list of enabled features. e.g.
              [u'private_networking', u'virtio']
        * image (dict): details of image used to create this droplet
        * ip_address (str): public ip addresses
        * private_ip_address (str): private ip address
        * ip_v6_address (:obj:`str`, optional): list of ipv6 addresses assigned
        * end_point (str): url of api endpoint used
        * volume_ids (:obj:`str`, optional): list of blockstorage volumes
        * vpc_uuid (str, optional): ID of the VPC that the Droplet is assigned to
    """

    def __init__(self, *args, **kwargs):
        # Defining default values
        self.id = None
        self.name = None
        self.memory = None
        self.vcpus = None
        self.disk = None
        self.region = []
        self.image = None
        self.size_slug = None
        self.locked = None
        self.created_at = None
        self.status = None
        self.networks = []
        self.kernel = None
        self.backup_ids = []
        self.snapshot_ids = []
        self.action_ids = []
        self.features = []
        self.ip_address = None
        self.private_ip_address = None
        self.ip_v6_address = None
        self.ssh_keys = []
        self.backups = None
        self.ipv6 = None
        self.private_networking = None
        self.user_data = None
        self.volumes = []
        self.tags = []
        self.monitoring = None
        self.vpc_uuid = None

        # This will load also the values passed
        super(Droplet, self).__init__(*args, **kwargs)

    @classmethod
    def get_object(cls, api_token, droplet_id):
        """Class method that will return a Droplet object by ID.

        Args:
            api_token (str): token
            droplet_id (int): droplet id
        """
        droplet = cls(token=api_token, id=droplet_id)
        droplet.load()
        return droplet

    @classmethod
    def create_multiple(*args, **kwargs):
        api = BaseAPI(token=kwargs.get("token"))

        data = {
            "names": kwargs.get("names"),
            "size": kwargs.get("size_slug") or kwargs.get("size"),
            "image": kwargs.get("image"),
            "region": kwargs.get("region"),
            "backups": bool(kwargs.get("backups")),
            "ipv6": bool(kwargs.get("ipv6")),
            "private_networking": bool(kwargs.get("private_networking")),
            "tags": kwargs.get("tags"),
            "monitoring": bool(kwargs.get("monitoring")),
            "vpc_uuid": kwargs.get("vpc_uuid"),
        }

        if kwargs.get("ssh_keys"):
            data["ssh_keys"] = Droplet.__get_ssh_keys_id_or_fingerprint(
                    kwargs["ssh_keys"], kwargs.get("token"),
                    kwargs["names"][0])

        if kwargs.get("user_data"):
            data["user_data"] = kwargs["user_data"]

        droplets = []

        data = api.get_data("droplets/", type=POST, params=data)

        if data:
            action_ids = [data["links"]["actions"][0]["id"]]
            for droplet_json in data["droplets"]:
                droplet_json["token"] = kwargs["token"]
                droplet = Droplet(**droplet_json)
                droplet.action_ids = action_ids
                droplets.append(droplet)

        return droplets

    def __check_actions_in_data(self, data):
        # reloading actions if actions is provided.
        if u"actions" in data:
            self.action_ids = []
            for action in data[u'actions']:
                self.action_ids.append(action[u'id'])

    def get_data(self, *args, **kwargs):
        """
            Customized version of get_data to perform __check_actions_in_data
        """
        data = super(Droplet, self).get_data(*args, **kwargs)
        if "type" in kwargs:
            if kwargs["type"] == POST:
                self.__check_actions_in_data(data)
        return data

    def load(self):
        """
           Fetch data about droplet - use this instead of get_data()
        """
        droplets = self.get_data("droplets/%s" % self.id)
        droplet = droplets['droplet']

        for attr in droplet.keys():
            setattr(self, attr, droplet[attr])

        for net in self.networks['v4']:
            if net['type'] == 'private':
                self.private_ip_address = net['ip_address']
            if net['type'] == 'public':
                self.ip_address = net['ip_address']
        if self.networks['v6']:
            self.ip_v6_address = self.networks['v6'][0]['ip_address']

            if "backups" in self.features:
                self.backups = True
            else:
                self.backups = False
            if "ipv6" in self.features:
                self.ipv6 = True
            else:
                self.ipv6 = False
            if "private_networking" in self.features:
                self.private_networking = True
            else:
                self.private_networking = False

        if "tags" in droplets:
            self.tags = droplets["tags"]

        return self

    def _perform_action(self, params, return_dict=True):
        """
            Perform a droplet action.

            Args:
                params (dict): parameters of the action

            Optional Args:
                return_dict (bool): Return a dict when True (default),
                    otherwise return an Action.

            Returns dict or Action
        """
        action = self.get_data(
            "droplets/%s/actions/" % self.id,
            type=POST,
            params=params
        )
        if return_dict:
            return action
        else:
            action = action[u'action']
            return_action = Action(token=self.tokens)
            # Loading attributes
            for attr in action.keys():
                setattr(return_action, attr, action[attr])
            return return_action

    def power_on(self, return_dict=True):
        """
            Boot up the droplet

            Optional Args:
                return_dict (bool): Return a dict when True (default),
                    otherwise return an Action.

            Returns dict or Action
        """
        return self._perform_action({'type': 'power_on'}, return_dict)

    def shutdown(self, return_dict=True):
        """
            shutdown the droplet

            Optional Args:
                return_dict (bool): Return a dict when True (default),
                    otherwise return an Action.

            Returns dict or Action
        """
        return self._perform_action({'type': 'shutdown'}, return_dict)

    def reboot(self, return_dict=True):
        """
            restart the droplet

            Optional Args:
                return_dict (bool): Return a dict when True (default),
                    otherwise return an Action.

            Returns dict or Action
        """
        return self._perform_action({'type': 'reboot'}, return_dict)

    def power_cycle(self, return_dict=True):
        """
            restart the droplet

            Optional Args:
                return_dict (bool): Return a dict when True (default),
                    otherwise return an Action.

            Returns dict or Action
        """
        return self._perform_action({'type': 'power_cycle'}, return_dict)

    def power_off(self, return_dict=True):
        """
            restart the droplet

            Optional Args:
                return_dict (bool): Return a dict when True (default),
                    otherwise return an Action.

            Returns dict or Action
        """
        return self._perform_action({'type': 'power_off'}, return_dict)

    def reset_root_password(self, return_dict=True):
        """
            reset the root password

            Optional Args:
                return_dict (bool): Return a dict when True (default),
                    otherwise return an Action.

            Returns dict or Action
        """
        return self._perform_action({'type': 'password_reset'}, return_dict)

    def resize(self, new_size_slug, return_dict=True, disk=True):
        """Resize the droplet to a new size slug.
        https://developers.digitalocean.com/documentation/v2/#resize-a-droplet

        Args:
            new_size_slug (str): name of new size

        Optional Args:
            return_dict (bool): Return a dict when True (default), \
                otherwise return an Action.
            disk (bool): If a permanent resize, with disk changes included.

        Returns dict or Action
        """
        options = {"type": "resize", "size": new_size_slug}
        if disk: options["disk"] = "true"

        return self._perform_action(options, return_dict)

    def take_snapshot(self, snapshot_name, return_dict=True, power_off=False):
        """Take a snapshot!

        Args:
            snapshot_name (str): name of snapshot

        Optional Args:
            return_dict (bool): Return a dict when True (default),
                otherwise return an Action.
            power_off (bool): Before taking the snapshot the droplet will be
                turned off with another API call. It will wait until the
                droplet will be powered off.

        Returns dict or Action
        """
        if power_off is True and self.status != "off":
            action = self.power_off(return_dict=False)
            action.wait()
            self.load()

        return self._perform_action(
            {"type": "snapshot", "name": snapshot_name},
            return_dict
        )

    def restore(self, image_id, return_dict=True):
        """Restore the droplet to an image ( snapshot or backup )

        Args:
            image_id (int): id of image

        Optional Args:
            return_dict (bool): Return a dict when True (default),
                otherwise return an Action.

        Returns dict or Action
        """
        return self._perform_action(
            {"type": "restore", "image": image_id},
            return_dict
        )

    def rebuild(self, image_id=None, return_dict=True):
        """Restore the droplet to an image ( snapshot or backup )

        Args:
            image_id (int): id of image

        Optional Args:
            return_dict (bool): Return a dict when True (default),
                otherwise return an Action.

        Returns dict or Action
        """
        if not image_id:
            image_id = self.image['id']

        return self._perform_action(
            {"type": "rebuild", "image": image_id},
            return_dict
        )

    def enable_backups(self, return_dict=True):
        """
            Enable automatic backups

            Optional Args:
                return_dict (bool): Return a dict when True (default),
                    otherwise return an Action.

            Returns dict or Action
        """
        return self._perform_action({'type': 'enable_backups'}, return_dict)

    def disable_backups(self, return_dict=True):
        """
            Disable automatic backups

            Optional Args:
                return_dict (bool): Return a dict when True (default),
                    otherwise return an Action.

            Returns dict or Action
        """
        return self._perform_action({'type': 'disable_backups'}, return_dict)

    def destroy(self):
        """
            Destroy the droplet

            Returns dict
        """
        return self.get_data("droplets/%s" % self.id, type=DELETE)

    def rename(self, name, return_dict=True):
        """Rename the droplet

        Args:
            name (str): new name

        Optional Args:
            return_dict (bool): Return a dict when True (default),
                otherwise return an Action.

        Returns dict or Action
        """
        return self._perform_action(
            {'type': 'rename', 'name': name},
            return_dict
        )

    def enable_private_networking(self, return_dict=True):
        """
           Enable private networking on an existing Droplet where available.

           Optional Args:
               return_dict (bool): Return a dict when True (default),
                   otherwise return an Action.

           Returns dict or Action
        """
        return self._perform_action(
            {'type': 'enable_private_networking'},
            return_dict
        )

    def enable_ipv6(self, return_dict=True):
        """
            Enable IPv6 on an existing Droplet where available.

            Optional Args:
                return_dict (bool): Return a dict when True (default),
                    otherwise return an Action.

            Returns dict or Action
        """
        return self._perform_action({'type': 'enable_ipv6'}, return_dict)

    def change_kernel(self, kernel, return_dict=True):
        """Change the kernel to a new one

        Args:
            kernel : instance of digitalocean.Kernel.Kernel

        Optional Args:
            return_dict (bool): Return a dict when True (default),
                otherwise return an Action.

        Returns dict or Action
        """
        if type(kernel) != Kernel:
            raise BadKernelObject("Use Kernel object")

        return self._perform_action(
            {'type': 'change_kernel', 'kernel': kernel.id},
            return_dict
        )

    @staticmethod
    def __get_ssh_keys_id_or_fingerprint(ssh_keys, token, name):
        """
            Check and return a list of SSH key IDs or fingerprints according
            to DigitalOcean's API. This method is used to check and create a
            droplet with the correct SSH keys.
        """
        ssh_keys_id = list()
        for ssh_key in ssh_keys:
            if type(ssh_key) in [int, type(2 ** 64)]:
                ssh_keys_id.append(int(ssh_key))

            elif type(ssh_key) == SSHKey:
                ssh_keys_id.append(ssh_key.id)

            elif type(ssh_key) in [type(u''), type('')]:
                # ssh_key could either be a fingerprint or a public key
                #
                # type(u'') and type('') is the same in python 3 but
                # different in 2. See:
                # https://github.com/koalalorenzo/python-digitalocean/issues/80
                regexp_of_fingerprint = '([0-9a-fA-F]{2}:){15}[0-9a-fA-F]'
                match = re.match(regexp_of_fingerprint, ssh_key)

                if match is not None and match.end() == len(ssh_key) - 1:
                    ssh_keys_id.append(ssh_key)

                else:
                    key = SSHKey()
                    key.token = token
                    results = key.load_by_pub_key(ssh_key)

                    if results is None:
                        key.public_key = ssh_key
                        key.name = "SSH Key %s" % name
                        key.create()
                    else:
                        key = results

                    ssh_keys_id.append(key.id)
            else:
                raise BadSSHKeyFormat(
                    "Droplet.ssh_keys should be a list of IDs, public keys"
                    + " or fingerprints."
                )

        return ssh_keys_id

    def create(self, *args, **kwargs):
        """
            Create the droplet with object properties.

            Note: Every argument and parameter given to this method will be
            assigned to the object.
        """
        for attr in kwargs.keys():
            setattr(self, attr, kwargs[attr])

        # Provide backwards compatibility
        if not self.size_slug and self.size:
            self.size_slug = self.size

        ssh_keys_id = Droplet.__get_ssh_keys_id_or_fingerprint(self.ssh_keys,
                                                               self.token,
                                                               self.name)

        data = {
            "name": self.name,
            "size": self.size_slug,
            "image": self.image,
            "region": self.region,
            "ssh_keys": ssh_keys_id,
            "backups": bool(self.backups),
            "ipv6": bool(self.ipv6),
            "private_networking": bool(self.private_networking),
            "volumes": self.volumes,
            "tags": self.tags,
            "monitoring": bool(self.monitoring),
            "vpc_uuid": self.vpc_uuid,
        }

        if self.user_data:
            data["user_data"] = self.user_data

        data = self.get_data("droplets/", type=POST, params=data)

        if data:
            self.id = data['droplet']['id']
            action_id = data['links']['actions'][0]['id']
            self.action_ids = []
            self.action_ids.append(action_id)

    def get_events(self):
        """
            A helper function for backwards compatibility.
            Calls get_actions()
        """
        return self.get_actions()

    def get_actions(self):
        """
            Returns a list of Action objects
            This actions can be used to check the droplet's status
        """
        answer = self.get_data("droplets/%s/actions/" % self.id, type=GET)

        actions = []
        for action_dict in answer['actions']:
            action = Action(**action_dict)
            action.token = self.tokens
            action.droplet_id = self.id
            action.load()
            actions.append(action)
        return actions

    def get_action(self, action_id):
        """Returns a specific Action by its ID.

        Args:
            action_id (int): id of action
        """
        return Action.get_object(
            api_token=self.tokens,
            action_id=action_id
        )

    def get_snapshots(self):
        """
            This method will return the snapshots/images connected to that
            specific droplet.
        """
        snapshots = list()
        for id in self.snapshot_ids:
            snapshot = Image()
            snapshot.id = id
            snapshot.token = self.tokens
            snapshots.append(snapshot)
        return snapshots

    def get_kernel_available(self):
        """
            Get a list of kernels available
        """

        kernels = list()
        data = self.get_data("droplets/%s/kernels/" % self.id)
        while True:
            for jsond in data[u'kernels']:
                kernel = Kernel(**jsond)
                kernel.token = self.tokens
                kernels.append(kernel)
            try:
                url = data[u'links'][u'pages'].get(u'next')
                if not url:
                        break
                data = self.get_data(url)
            except KeyError:  # No links.
                break

        return kernels

    def update_volumes_data(self):
        """
           Trigger volume objects list refresh.
           When called on a droplet instance, it will take
           all volumes ids(gathered in initial droplet details
           collection) and will create list of object of Volume
           types. Each volume is a separate api call.
        """
        self.volumes = list()

        for volume_id in self.volume_ids:
            volume = Volume().get_object(self.tokens, volume_id)
            self.volumes.append(volume)


    def __str__(self):
        return "<Droplet: %s %s>" % (self.id, self.name) class Firewall(BaseAPI):
    """
    An object representing an DigitalOcean Firewall.

    Attributes accepted at creation time:

    Args:
        name (str): The Firewall's name.
        droplet_ids (obj:`list` of `int`): A list of Droplet IDs to be assigned
            to the Firewall.
        tags (obj:`list` of `str`):  A list Tag names to be assigned to the
            Firewall.
        inbound_rules (obj:`list`): A list of `InboundRules` objects
        outbound_rules (obj:`list`): A list of `OutboundRules` objects

    Attributes returned by API:
        id (str): A UUID to identify and reference a Firewall.
        status (str): A status string indicating the current state of the
            Firewall. This can be "waiting", "succeeded", or "failed".
        created_at (str): The time at which the Firewall was created.
        name (str): The Firewall's name.
        pending_changes (obj:`list`): Details exactly which Droplets are having
            their security policies updated.
        droplet_ids (obj:`list` of `int`): A list of Droplet IDs to be assigned
            to the Firewall.
        tags (obj:`list` of `str`):  A list Tag names to be assigned to the
            Firewall.
        inbound_rules (obj:`list`): A list of `InboundRules` objects
        outbound_rules (obj:`list`): A list of `OutboundRules` objects
    """
    def __init__(self, *args, **kwargs):
        self.id = None
        self.status = None
        self.created_at = None
        self.pending_changes = []
        self.name = None
        self.inbound_rules = []
        self.outbound_rules = []
        self.droplet_ids = None
        self.tags = None

        super(Firewall, self).__init__(*args, **kwargs)

    @classmethod
    def get_object(cls, api_token, firewall_id):
        """
            Class method that will return a Firewall object by ID.
        """
        firewall = cls(token=api_token, id=firewall_id)
        firewall.load()
        return firewall

    def _set_firewall_attributes(self, data):
        self.id = data['firewall']['id']
        self.name = data['firewall']['name']
        self.status = data['firewall']['status']
        self.created_at = data['firewall']['created_at']
        self.pending_changes = data['firewall']['pending_changes']
        self.droplet_ids = data['firewall']['droplet_ids']
        self.tags = data['firewall']['tags']

        in_rules = list()
        for rule in data['firewall']['inbound_rules']:
            in_rules.append(InboundRule(**rule))
        self.inbound_rules = in_rules

        out_rules = list()
        for rule in data['firewall']['outbound_rules']:
            out_rules.append(OutboundRule(**rule))
        self.outbound_rules = out_rules

    def load(self):
        data = self.get_data("firewalls/%s" % self.id)
        if data:
            self._set_firewall_attributes(data)

        return self

    def create(self, *args, **kwargs):
        inbound = jsonpickle.encode(self.inbound_rules, unpicklable=False)
        outbound = jsonpickle.encode(self.outbound_rules, unpicklable=False)
        params = {'name': self.name,
                  'droplet_ids': self.droplet_ids,
                  'inbound_rules': jsonpickle.decode(inbound),
                  'outbound_rules': jsonpickle.decode(outbound),
                  'tags': self.tags}

        data = self.get_data('firewalls/', type=POST, params=params)

        if data:
            self._set_firewall_attributes(data)

        return self

    def add_droplets(self, droplet_ids):
        """
            Add droplets to this Firewall.
        """
        return self.get_data(
            "firewalls/%s/droplets" % self.id,
            type=POST,
            params={"droplet_ids": droplet_ids}
        )

    def remove_droplets(self, droplet_ids):
        """
            Remove droplets from this Firewall.
        """
        return self.get_data(
            "firewalls/%s/droplets" % self.id,
            type=DELETE,
            params={"droplet_ids": droplet_ids}
        )

    def add_tags(self, tags):
        """
            Add tags to this Firewall.
        """
        return self.get_data(
            "firewalls/%s/tags" % self.id,
            type=POST,
            params={"tags": tags}
        )

    def remove_tags(self, tags):
        """
            Remove tags from this Firewall.
        """
        return self.get_data(
            "firewalls/%s/tags" % self.id,
            type=DELETE,
            params={"tags": tags}
        )

    # TODO: Other Firewall calls (Add/Remove rules, Create / Delete etc)

    def destroy(self):
        """
            Destroy the Firewall
        """
        return self.get_data("firewalls/%s/" % self.id, type=DELETE)

    def __str__(self):
        return "<Firewall: %s %s>" % (self.id, self.name) class InboundRule(object):
    """
    An object holding information about a Firewall's inbound rule.

    Args:
        protocol (str): The type of traffic to be allowed. This may be one
            of "tcp", "udp", or "icmp".
        ports (str): The ports on which traffic will be allowed specified as a
            string containing a single port, a range (e.g. "8000-9000"), or
            "all" to open all ports for a protocol.
        sources (obj): A `Sources` object.
    """
    def __init__(self, protocol="", ports="", sources=""):
        self.protocol = protocol
        self.ports = ports

        if isinstance(sources, Sources):
            self.sources = sources
        else:
            for source in sources:
                self.sources = Sources(**sources) class OutboundRule(object):
    """
    An object holding information about a Firewall's outbound rule.

    Args:
        protocol (str): The type of traffic to be allowed. This may be one
            of "tcp", "udp", or "icmp".
        ports (str): The ports on which traffic will be allowed specified as a
            string containing a single port, a range (e.g. "8000-9000"), or
            "all" to open all ports for a protocol.
        destinations (obj): A `Destinations` object.
    """
    def __init__(self, protocol="", ports="", destinations=""):
        self.protocol = protocol
        self.ports = ports

        if isinstance(destinations, Destinations):
            self.destinations = destinations
        else:
            for destination in destinations:
                self.destinations = Destinations(**destinations) class Image(BaseAPI):
    """
    An object representing an DigitalOcean Image.

    Attributes accepted at creation time:

    Args:
        name (str): The name to be given to an image.
        url (str): A URL from which the virtual machine image may be retrieved.
        region (str): The slug of the region where the image will be available.
        distribution (str, optional): The name of the image's distribution.
        description (str, optional): Free-form text field to describe an image.
        tags (obj:`list` of `str`, optional): List of tag names to apply to \
            the image.

    Attributes returned by API:
        * id (int): A unique number to identify and reference a image.
        * name (str): The display name given to an image.
        * type (str): The kind of image. This will be either "snapshot",
              "backup", or "custom".
        * distribution (str): The name of the image's distribution.
        * slug (str): A uniquely identifying string that is associated with each \
              of the DigitalOcean-provided public images.
        * public (bool): Indicates whether the image is public or not.
        * regions (obj:`list` of `str`): A list of the slugs of the regions where \
              the image is available for use.
        * created_at (str): A time value given in ISO8601 combined date and time \
              format that represents when the image was created.
        * min_disk_size (int): The minimum disk size in GB required for a Droplet \
              to use this image.
        * size_gigabytes (int): The size of the image in gigabytes.
        * description (str): Free-form text field to describing an image.
        * tags (obj:`list` of `str`): List of tag names to applied to the image.
        * status (str): Indicates the state of a custom image. This may be "NEW", \
              "available", "pending", or "deleted".
        * error_message (str): Information about errors that may occur when \
              importing a custom image.
    """

    def __init__(self, *args, **kwargs):
        self.id = None
        self.name = None
        self.distribution = None
        self.slug = None
        self.min_disk_size = None
        self.public = None
        self.regions = []
        self.created_at = None
        self.size_gigabytes = None
        self.description = None
        self.status = None
        self.tags = []
        self.error_message = None
        self.url = None
        self.region = None

        super(Image, self).__init__(*args, **kwargs)

    @classmethod
    def get_object(cls, api_token, image_id_or_slug):
        """
            Class method that will return an Image object by ID or slug.

            This method is used to validate the type of the image. If it is a
            number, it will be considered as an Image ID, instead if it is a
            string, it will considered as slug.
        """
        if cls._is_string(image_id_or_slug):
            image = cls(token=api_token, slug=image_id_or_slug)
            image.load(use_slug=True)
        else:
            image = cls(token=api_token, id=image_id_or_slug)
            image.load()
        return image

    @staticmethod
    def _is_string(value):
        """
            Checks if the value provided is a string (True) or not integer
            (False) or something else (None).
        """
        if type(value) in [type(u''), type('')]:
            return True
        elif type(value) in [int, type(2 ** 64)]:
            return False
        else:
            return None

    def create(self):
        """
        Creates a new custom DigitalOcean Image from the Linux virtual machine
        image located at the provided `url`.
        """
        params = {'name': self.name,
                  'region': self.region,
                  'url': self.url,
                  'distribution': self.distribution,
                  'description': self.description,
                  'tags': self.tags}

        data = self.get_data('images', type=POST, params=params)

        if data:
            for attr in data['image'].keys():
                setattr(self, attr, data['image'][attr])

        return self

    def load(self, use_slug=False):
        """
            Load slug.

            Loads by id, or by slug if id is not present or use slug is True.
        """
        identifier = None
        if use_slug or not self.id:
            identifier = self.slug
        else:
            identifier = self.id
        if not identifier:
            raise NotFoundError("One of self.id or self.slug must be set.")
        data = self.get_data("images/%s" % identifier)
        image_dict = data['image']

        # Setting the attribute values
        for attr in image_dict.keys():
            setattr(self, attr, image_dict[attr])

        return self

    def destroy(self):
        """
            Destroy the image
        """
        return self.get_data("images/%s/" % self.id, type=DELETE)

    def transfer(self, new_region_slug):
        """
            Transfer the image
        """
        return self.get_data(
            "images/%s/actions/" % self.id,
            type=POST,
            params={"type": "transfer", "region": new_region_slug}
        )

    def rename(self, new_name):
        """
            Rename an image
        """
        return self.get_data(
            "images/%s" % self.id,
            type=PUT,
            params={"name": new_name}
        )

    def __str__(self):
        return "<Image: %s %s %s>" % (self.id, self.distribution, self.name) class LoadBalancer(BaseAPI):
    """
    An object representing an DigitalOcean Load Balancer.

    Attributes accepted at creation time:

    Args:
        name (str): The Load Balancer's name
        region (str): The slug identifier for a DigitalOcean region
        size (str): The size of the load balancer. The available sizes \
            are "lb-small", "lb-medium", or "lb-large". Once you have \
            created a load balancer, you can't change its size
        algorithm (str, optional): The load balancing algorithm to be \
            used. Currently, it must be either "round_robin" or \
            "least_connections"
        forwarding_rules (obj:`list`): A list of `ForwrdingRules` objects
        health_check (obj, optional): A `HealthCheck` object
        sticky_sessions (obj, optional): A `StickySessions` object
        redirect_http_to_https (bool, optional): A boolean indicating \
            whether HTTP requests to the Load Balancer should be \
            redirected to HTTPS
        enable_proxy_protocol (bool, optional): A boolean value indicating \
            whether PROXY Protocol is in use
        enable_backend_keepalive (bool, optional): A boolean value \
            indicating whether HTTP keepalive connections are maintained \
            to target Droplets.
        droplet_ids (obj:`list` of `int`): A list of IDs representing \
            Droplets to be added to the Load Balancer (mutually \
            exclusive with 'tag')
        tag (str): A string representing a DigitalOcean Droplet tag \
            (mutually exclusive with 'droplet_ids')
        vpc_uuid (str): ID of a VPC in which the Load Balancer will be created

    Attributes returned by API:
        * name (str): The Load Balancer's name
        * id (str): An unique identifier for a LoadBalancer
        * ip (str): Public IP address for a LoadBalancer
        * region (str): The slug identifier for a DigitalOcean region
        * size (str): The size of the load balancer
        * algorithm (str, optional): The load balancing algorithm to be \
              used. Currently, it must be either "round_robin" or \
              "least_connections"
        * forwarding_rules (obj:`list`): A list of `ForwrdingRules` objects
        * health_check (obj, optional): A `HealthCheck` object
        * sticky_sessions (obj, optional): A `StickySessions` object
        * redirect_http_to_https (bool, optional): A boolean indicating \
              whether HTTP requests to the Load Balancer should be \
              redirected to HTTPS
        * enable_proxy_protocol (bool, optional): A boolean value indicating \
              whether PROXY Protocol is in use
        * enable_backend_keepalive (bool, optional): A boolean value \
              indicating whether HTTP keepalive connections are maintained \
              to target Droplets.
        * droplet_ids (obj:`list` of `int`): A list of IDs representing \
              Droplets to be added to the Load Balancer
        * tag (str): A string representing a DigitalOcean Droplet tag
        * status (string): An indication the current state of the LoadBalancer
        * created_at (str): The date and time when the LoadBalancer was created
        * vpc_uuid (str): ID of a VPC which the Load Balancer is assigned to
    """
    def __init__(self, *args, **kwargs):
        self.id = None
        self.name = None
        self.region = None
        self.size = None
        self.algorithm = None
        self.forwarding_rules = []
        self.health_check = None
        self.sticky_sessions = None
        self.redirect_http_to_https = False
        self.enable_proxy_protocol = False
        self.enable_backend_keepalive = False
        self.droplet_ids = []
        self.tag = None
        self.status = None
        self.created_at = None
        self.vpc_uuid = None

        super(LoadBalancer, self).__init__(*args, **kwargs)

    @classmethod
    def get_object(cls, api_token, id):
        """
        Class method that will return a LoadBalancer object by its ID.

        Args:
            api_token (str): DigitalOcean API token
            id (str): Load Balancer ID
        """
        load_balancer = cls(token=api_token, id=id)
        load_balancer.load()
        return load_balancer

    def load(self):
        """
        Loads updated attributes for a LoadBalancer object.

        Requires self.id to be set.
        """
        data = self.get_data('load_balancers/%s' % self.id, type=GET)
        load_balancer = data['load_balancer']

        # Setting the attribute values
        for attr in load_balancer.keys():
            if attr == 'health_check':
                health_check = HealthCheck(**load_balancer['health_check'])
                setattr(self, attr, health_check)
            elif attr == 'sticky_sessions':
                sticky_ses = StickySessions(**load_balancer['sticky_sessions'])
                setattr(self, attr, sticky_ses)
            elif attr == 'forwarding_rules':
                rules = list()
                for rule in load_balancer['forwarding_rules']:
                    rules.append(ForwardingRule(**rule))
                setattr(self, attr, rules)
            else:
                setattr(self, attr, load_balancer[attr])

        return self

    def create(self, *args, **kwargs):
        """
        Creates a new LoadBalancer.

        Note: Every argument and parameter given to this method will be
        assigned to the object.

        Args:
            name (str): The Load Balancer's name
            region (str): The slug identifier for a DigitalOcean region
            size (str): The size of the load balancer. The available sizes
                are "lb-small", "lb-medium", or "lb-large". Once you have
                created a load balancer, you can't change its size
            algorithm (str, optional): The load balancing algorithm to be
                used. Currently, it must be either "round_robin" or
                "least_connections"
            forwarding_rules (obj:`list`): A list of `ForwrdingRules` objects
            health_check (obj, optional): A `HealthCheck` object
            sticky_sessions (obj, optional): A `StickySessions` object
            redirect_http_to_https (bool, optional): A boolean indicating
                whether HTTP requests to the Load Balancer should be
                redirected to HTTPS
            enable_proxy_protocol (bool, optional): A boolean value indicating
                whether PROXY Protocol is in use
            enable_backend_keepalive (bool, optional): A boolean value
                indicating whether HTTP keepalive connections are maintained
                to target Droplets.
            droplet_ids (obj:`list` of `int`): A list of IDs representing
                Droplets to be added to the Load Balancer (mutually
                exclusive with 'tag')
            tag (str): A string representing a DigitalOcean Droplet tag
                (mutually exclusive with 'droplet_ids')
            vpc_uuid (str): ID of a Load Balancer in which the Droplet will be
                created
        """
        rules_dict = [rule.__dict__ for rule in self.forwarding_rules]

        params = {'name': self.name, 'region': self.region,
                  'size': self.size,
                  'forwarding_rules': rules_dict,
                  'redirect_http_to_https': self.redirect_http_to_https,
                  'enable_proxy_protocol': self.enable_proxy_protocol,
                  'enable_backend_keepalive': self.enable_backend_keepalive,
                  'vpc_uuid': self.vpc_uuid}

        if self.droplet_ids and self.tag:
            raise ValueError('droplet_ids and tag are mutually exclusive args')
        elif self.tag:
            params['tag'] = self.tag
        else:
            params['droplet_ids'] = self.droplet_ids

        if self.algorithm:
            params['algorithm'] = self.algorithm
        if self.health_check:
            params['health_check'] = self.health_check.__dict__
        if self.sticky_sessions:
            params['sticky_sessions'] = self.sticky_sessions.__dict__

        data = self.get_data('load_balancers', type=POST, params=params)

        if data:
            self.id = data['load_balancer']['id']
            self.ip = data['load_balancer']['ip']
            self.algorithm = data['load_balancer']['algorithm']
            self.size = data['load_balancer']['size']
            self.health_check = HealthCheck(
                **data['load_balancer']['health_check'])
            self.sticky_sessions = StickySessions(
                **data['load_balancer']['sticky_sessions'])
            self.droplet_ids = data['load_balancer']['droplet_ids']
            self.status = data['load_balancer']['status']
            self.created_at = data['load_balancer']['created_at']
            self.redirect_http_to_https = data['load_balancer']['redirect_http_to_https']
            self.enable_proxy_protocol = data['load_balancer']['enable_proxy_protocol']
            self.enable_backend_keepalive = data['load_balancer']['enable_backend_keepalive']
            self.vpc_uuid = data['load_balancer']['vpc_uuid']

        return self

    def save(self):
        """
        Save the LoadBalancer
        """
        forwarding_rules = [rule.__dict__ for rule in self.forwarding_rules]

        data = {
            'name': self.name,
            'region': self.region['slug'],
            'forwarding_rules': forwarding_rules,
            'redirect_http_to_https': self.redirect_http_to_https,
            'enable_proxy_protocol': self.enable_proxy_protocol,
            'enable_backend_keepalive': self.enable_backend_keepalive,
            'vpc_uuid': self.vpc_uuid
        }

        if self.tag:
            data['tag'] = self.tag
        else:
            data['droplet_ids'] = self.droplet_ids

        if self.algorithm:
            data["algorithm"] = self.algorithm
        if self.health_check:
            data['health_check'] = self.health_check.__dict__
        if self.sticky_sessions:
            data['sticky_sessions'] = self.sticky_sessions.__dict__

        return self.get_data("load_balancers/%s" % self.id,
                             type=PUT,
                             params=data)

    def destroy(self):
        """
        Destroy the LoadBalancer
        """
        return self.get_data('load_balancers/%s' % self.id, type=DELETE)

    def add_droplets(self, droplet_ids):
        """
        Assign a LoadBalancer to a Droplet.

        Args:
            droplet_ids (obj:`list` of `int`): A list of Droplet IDs
        """
        return self.get_data(
            "load_balancers/%s/droplets" % self.id,
            type=POST,
            params={"droplet_ids": droplet_ids}
        )

    def remove_droplets(self, droplet_ids):
        """
        Unassign a LoadBalancer.

        Args:
            droplet_ids (obj:`list` of `int`): A list of Droplet IDs
        """
        return self.get_data(
            "load_balancers/%s/droplets" % self.id,
            type=DELETE,
            params={"droplet_ids": droplet_ids}
        )

    def add_forwarding_rules(self, forwarding_rules):
        """
        Adds new forwarding rules to a LoadBalancer.

        Args:
            forwarding_rules (obj:`list`): A list of `ForwrdingRules` objects
        """
        rules_dict = [rule.__dict__ for rule in forwarding_rules]

        return self.get_data(
            "load_balancers/%s/forwarding_rules" % self.id,
            type=POST,
            params={"forwarding_rules": rules_dict}
        )

    def remove_forwarding_rules(self, forwarding_rules):
        """
        Removes existing forwarding rules from a LoadBalancer.

        Args:
            forwarding_rules (obj:`list`): A list of `ForwrdingRules` objects
        """
        rules_dict = [rule.__dict__ for rule in forwarding_rules]

        return self.get_data(
            "load_balancers/%s/forwarding_rules" % self.id,
            type=DELETE,
            params={"forwarding_rules": rules_dict}
        )

    def __str__(self):
        return "%s" % (self.id) class StickySessions(object):
    """
    An object holding information on a LoadBalancer's sticky sessions settings.

    Args:
        type (str): The type of sticky sessions used. Can be "cookies" or
            "none"
        cookie_name (str, optional): The name used for the client cookie when
            using cookies for sticky session
        cookie_ttl_seconds (int, optional): The number of seconds until the
            cookie expires
    """
    def __init__(self, type='none', cookie_name='', cookie_ttl_seconds=None, **kwargs):
        self.type = type
        if type == 'cookies':
            self.cookie_name = 'DO-LB'
            self.cookie_ttl_seconds = 300
        self.cookie_name = cookie_name
        self.cookie_ttl_seconds = cookie_ttl_seconds class HealthCheck(object):
    """
    An object holding information about a LoadBalancer health check settings.

    Args:
        protocol (str): The protocol used for health checks. The possible
            values are "http" or "tcp".
        port (int): The port on the backend Droplets for heath checks
        path (str): The path to send a health check request to
        check_interval_seconds (int): The number of seconds between between two
            consecutive health checks
        response_timeout_seconds (int): The number of seconds the Load Balancer
            instance will wait for a response until marking a check as failed
        healthy_threshold (int): The number of times a health check must fail
            for a backend Droplet to be removed from the pool
        unhealthy_threshold (int): The number of times a health check must pass
            for a backend Droplet to be re-added to the pool
    """
    def __init__(self, protocol='http', port=80, path='/',
                 check_interval_seconds=10, response_timeout_seconds=5,
                 healthy_threshold=5, unhealthy_threshold=3):
        self.protocol = protocol
        self.port = port
        self.path = path
        self.check_interval_seconds = check_interval_seconds
        self.response_timeout_seconds = response_timeout_seconds
        self.healthy_threshold = healthy_threshold
        self.unhealthy_threshold = unhealthy_threshold class ForwardingRule(object):
    """
    An object holding information about a LoadBalancer forwarding rule setting.

    Args:
        entry_protocol (str): The protocol used for traffic to a LoadBalancer.
            The possible values are: "http", "https", or "tcp"
        entry_port (int): The port the LoadBalancer instance will listen on
        target_protocol (str): The protocol used for traffic from a
            LoadBalancer to the backend Droplets. The possible values are:
            "http", "https", or "tcp"
        target_port (int): The port on the backend Droplets on which the
            LoadBalancer will send traffic
        certificate_id (str, optional): The ID of the TLS certificate used for
            SSL termination if enabled
        tls_passthrough (bool, optional): A boolean indicating if SSL encrypted
            traffic will be passed through to the backend Droplets
    """
    def __init__(self, entry_protocol=None, entry_port=None,
                 target_protocol=None, target_port=None, certificate_id="",
                 tls_passthrough=False):
        self.entry_protocol = entry_protocol
        self.entry_port = entry_port
        self.target_protocol = target_protocol
        self.target_port = target_port
        self.certificate_id = certificate_id
        self.tls_passthrough = tls_passthrough class SSHKey(BaseAPI):
    def __init__(self, *args, **kwargs):
        self.id = ""
        self.name = None
        self.public_key = None
        self.fingerprint = None

        super(SSHKey, self).__init__(*args, **kwargs)

    @classmethod
    def get_object(cls, api_token, ssh_key_id):
        """
            Class method that will return a SSHKey object by ID.
        """
        ssh_key = cls(token=api_token, id=ssh_key_id)
        ssh_key.load()
        return ssh_key

    def load(self):
        """
            Load the SSHKey object from DigitalOcean.

            Requires either self.id or self.fingerprint to be set.
        """
        identifier = None
        if self.id:
            identifier = self.id
        elif self.fingerprint is not None:
            identifier = self.fingerprint

        data = self.get_data("account/keys/%s" % identifier, type=GET)

        ssh_key = data['ssh_key']

        # Setting the attribute values
        for attr in ssh_key.keys():
            setattr(self, attr, ssh_key[attr])
        self.id = ssh_key['id']

    def load_by_pub_key(self, public_key):
        """
            This method will load a SSHKey object from DigitalOcean
            from a public_key. This method will avoid problems like
            uploading the same public_key twice.
        """

        data = self.get_data("account/keys/")
        for jsoned in data['ssh_keys']:
            if jsoned.get('public_key', "") == public_key:
                self.id = jsoned['id']
                self.load()
                return self
        return None

    def create(self):
        """
            Create the SSH Key
        """
        input_params = {
            "name": self.name,
            "public_key": self.public_key,
        }

        data = self.get_data("account/keys/", type=POST, params=input_params)

        if data:
            self.id = data['ssh_key']['id']

    def edit(self):
        """
            Edit the SSH Key
        """
        input_params = {
            "name": self.name,
            "public_key": self.public_key,
        }

        data = self.get_data(
            "account/keys/%s" % self.id,
            type=PUT,
            params=input_params
        )

        if data:
            self.id = data['ssh_key']['id']

    def destroy(self):
        """
            Destroy the SSH Key
        """
        return self.get_data("account/keys/%s" % self.id, type=DELETE)

    def __str__(self):
        return "<SSHKey: %s %s>" % (self.id, self.name) class Tag(BaseAPI):
    def __init__(self, *args, **kwargs):
        self.name = ""
        self.resources = {}
        super(Tag, self).__init__(*args, **kwargs)


    @classmethod
    def get_object(cls, api_token, tag_name):
        tag = cls(token=api_token, name=tag_name)
        tag.load()
        return tag


    def load(self):
        """
           Fetch data about tag
        """
        tags = self.get_data("tags/%s" % self.name)
        tag = tags['tag']

        for attr in tag.keys():
            setattr(self, attr, tag[attr])

        return self


    def create(self, **kwargs):
        """
            Create the tag.
        """
        for attr in kwargs.keys():
            setattr(self, attr, kwargs[attr])

        params = {"name": self.name}

        output = self.get_data("tags", type="POST", params=params)
        if output:
            self.name = output['tag']['name']
            self.resources = output['tag']['resources']


    def delete(self):
        return self.get_data("tags/%s" % self.name, type="DELETE")


    def __get_resources(self, resources, method):

        """ Method used to talk directly to the API (TAGs' Resources) """
        tagged = self.get_data(
            'tags/%s/resources' % self.name, params={
                "resources": resources
            },
            type=method,
        )
        return tagged


    def __add_resources(self, resources):
        """
            Add the resources to this tag.

            Attributes accepted at creation time:
                resources: array - See API.
        """
        return self.__get_resources(resources, method='POST')


    def __remove_resources(self, resources):
        """
            Remove resources from this tag.

            Attributes accepted at creation time:
                resources: array - See API.
        """
        return self.__get_resources(resources, method='DELETE')


    def __build_resources_field(self, resources_to_tag, object_class, resource_type):
        """
            Private method to build the `resources` field used to tag/untag 
            DO resources. Returns an array of objects containing two fields: 
            resource_id and resource_type.
            It checks the type of objects in the 1st argument and build the
            right structure for the API. It accepts array of strings, array
            of ints and array of the object type defined by object_class arg.
            The 3rd argument specify the resource type as defined by DO API
            (like droplet, image, volume or volume_snapshot).
            See: https://developers.digitalocean.com/documentation/v2/#tag-a-resource
        """
        resources_field = []
        if not isinstance(resources_to_tag, list): return resources_to_tag
        for resource_to_tag in resources_to_tag:
            res = {}

            try:
                if isinstance(resource_to_tag, unicode):
                    res = {"resource_id": resource_to_tag}
            except NameError:
                pass

            if isinstance(resource_to_tag, str) or isinstance(resource_to_tag, int):
                res = {"resource_id": str(resource_to_tag)}
            elif isinstance(resource_to_tag, object_class):
                res = {"resource_id": str(resource_to_tag.id)}

            if len(res) > 0:
                res["resource_type"] = resource_type
                resources_field.append(res)

        return resources_field


    def add_droplets(self, droplet):
        """
            Add the Tag to a Droplet.

            Attributes accepted at creation time:
                droplet: array of string or array of int, or array of Droplets.
        """
        droplets = droplet
        if not isinstance(droplets, list):
            droplets = [droplet]

        # Extracting data from the Droplet object
        resources = self.__build_resources_field(droplets, Droplet, "droplet")
        if len(resources) > 0:
            return self.__add_resources(resources)

        return False


    def remove_droplets(self, droplet):
        """
            Remove the Tag from the Droplet.

            Attributes accepted at creation time:
                droplet: array of string or array of int, or array of Droplets.
        """
        droplets = droplet
        if not isinstance(droplets, list):
            droplets = [droplet]

        # Build resources field from the Droplet objects
        resources = self.__build_resources_field(droplets, Droplet, "droplet")
        if len(resources) > 0:
            return self.__remove_resources(resources)

        return False


    def add_snapshots(self, snapshots):
        """
            Add the Tag to the Snapshot.

            Attributes accepted at creation time:
                snapshots: array of string or array of int or array of Snapshot.
        """
        
        if not isinstance(snapshots, list):
            snapshots = [snapshots]

        resources = self.__build_resources_field(snapshots, Snapshot, "volume_snapshot")
        if len(resources) > 0:
            return self.__add_resources(resources)
        
        return False


    def remove_snapshots(self, snapshots):
        """
            remove the Tag from the Snapshot.

            Attributes accepted at creation time:
                snapshots: array of string or array of int or array of Snapshot.
        """
        if not isinstance(snapshots, list):
            snapshots = [snapshots]

        resources = self.__build_resources_field(snapshots, Snapshot, "volume_snapshot")
        if len(resources) > 0:
            return self.__remove_resources(resources)
        
        return False


    def __str__(self):
        return "<Tag: %s>" % self.name class Volume(BaseAPI):
    def __init__(self, *args, **kwargs):
        self.id = None
        self.name = None
        self.droplet_ids = []
        self.region = None
        self.description = None
        self.size_gigabytes = None
        self.created_at = None
        self.snapshot_id = None
        self.filesystem_type = None
        self.filesystem_label = None
        self.tags = None

        super(Volume, self).__init__(*args, **kwargs)

    @classmethod
    def get_object(cls, api_token, volume_id):
        """
        Class method that will return an Volume object by ID.
        """
        volume = cls(token=api_token, id=volume_id)
        volume.load()
        return volume

    def load(self):
        data = self.get_data("volumes/%s" % self.id)
        volume_dict = data['volume']

        # Setting the attribute values
        for attr in volume_dict.keys():
            setattr(self, attr, volume_dict[attr])

        return self

    def create(self, *args, **kwargs):
        """
        Creates a Block Storage volume

        Note: Every argument and parameter given to this method will be
        assigned to the object.

        Args:
            name: string - a name for the volume
            region: string - slug identifier for the region
            size_gigabytes: int - size of the Block Storage volume in GiB
            filesystem_type: string, optional - name of the filesystem type the
                volume will be formatted with ('ext4' or 'xfs')
            filesystem_label: string, optional - the label to be applied to the
                filesystem, only used in conjunction with filesystem_type

        Optional Args:
            description: string - text field to describe a volume
            tags: List[string], optional - the tags to be applied to the volume
        """
        data = self.get_data('volumes/',
                             type=POST,
                             params={'name': self.name,
                                     'region': self.region,
                                     'size_gigabytes': self.size_gigabytes,
                                     'description': self.description,
                                     'filesystem_type': self.filesystem_type,
                                     'filesystem_label': self.filesystem_label,
                                     'tags': self.tags,
                                     })

        if data:
            self.id = data['volume']['id']
            self.created_at = data['volume']['created_at']

        return self

    def create_from_snapshot(self, *args, **kwargs):
        """
        Creates a Block Storage volume

        Note: Every argument and parameter given to this method will be
        assigned to the object.

        Args:
            name: string - a name for the volume
            snapshot_id: string - unique identifier for the volume snapshot
            size_gigabytes: int - size of the Block Storage volume in GiB
            filesystem_type: string, optional - name of the filesystem type the
                volume will be formatted with ('ext4' or 'xfs')
            filesystem_label: string, optional - the label to be applied to the
                filesystem, only used in conjunction with filesystem_type

        Optional Args:
            description: string - text field to describe a volume
            tags: List[string], optional - the tags to be applied to the volume
        """
        data = self.get_data('volumes/',
                             type=POST,
                             params={'name': self.name,
                                     'snapshot_id': self.snapshot_id,
                                     'region': self.region,
                                     'size_gigabytes': self.size_gigabytes,
                                     'description': self.description,
                                     'filesystem_type': self.filesystem_type,
                                     'filesystem_label': self.filesystem_label,
                                     'tags': self.tags,
                                     })

        if data:
            self.id = data['volume']['id']
            self.created_at = data['volume']['created_at']

        return self

    def destroy(self):
        """
            Destroy a volume
        """
        return self.get_data("volumes/%s/" % self.id, type=DELETE)

    def attach(self, droplet_id, region):
        """
        Attach a Volume to a Droplet.

        Args:
            droplet_id: int - droplet id
            region: string - slug identifier for the region
        """
        return self.get_data(
            "volumes/%s/actions/" % self.id,
            type=POST,
            params={"type": "attach",
                    "droplet_id": droplet_id,
                    "region": region}
        )

    def detach(self, droplet_id, region):
        """
        Detach a Volume to a Droplet.

        Args:
            droplet_id: int - droplet id
            region: string - slug identifier for the region
        """
        return self.get_data(
            "volumes/%s/actions/" % self.id,
            type=POST,
            params={"type": "detach",
                    "droplet_id": droplet_id,
                    "region": region}
        )

    def resize(self, size_gigabytes, region):
        """
        Detach a Volume to a Droplet.

        Args:
            size_gigabytes: int - size of the Block Storage volume in GiB
            region: string - slug identifier for the region
        """
        return self.get_data(
            "volumes/%s/actions/" % self.id,
            type=POST,
            params={"type": "resize",
                    "size_gigabytes": size_gigabytes,
                    "region": region}
        )

    def snapshot(self, name):
        """
        Create a snapshot of the volume.

        Args:
            name: string - a human-readable name for the snapshot
        """
        return self.get_data(
            "volumes/%s/snapshots/" % self.id,
            type=POST,
            params={"name": name}
        )

    def get_snapshots(self):
        """
        Retrieve the list of snapshots that have been created from a volume.

        Args:
        """
        data = self.get_data("volumes/%s/snapshots/" % self.id)
        snapshots = list()
        for jsond in data[u'snapshots']:
            snapshot = Snapshot(**jsond)
            snapshot.token = self.tokens
            snapshots.append(snapshot)

        return snapshots


    def __str__(self):
        return "<Volume: %s %s %s>" % (self.id, self.name, self.size_gigabytes) class VPC(BaseAPI):
    """
    An object representing a DigitalOcean VPC.

    Attributes accepted at creation time:

    Args:
        name (str): A name for the VPC
        region (str): The slug for the region where the VPC will be created
        description(str): A free-form text field for describing the VPC
        ip_range (str): The requested range of IP addresses for the VPC in \
            CIDR notation


    Attributes returned by API:
        * id (str): A unique identifier for the VPC
        * name (str): The name of the VPC
        * region (str): The slug for the region where the VPC is located
        * description(str): A free-form text field for describing the VPC
        * ip_range (str): The requested range of IP addresses for the VPC in \
            CIDR notation
        * urn (str): The uniform resource name (URN) for the VPC
        * created_at (str): A string that represents when the VPC was created
        * default (bool): A boolean representing whether or not the VPC is the \
            user's default VPC for the region
    """
    def __init__(self, *args, **kwargs):
        self.id = ""
        self.name = None
        self.region = None
        self.description = None
        self.ip_range = None
        self.urn = None
        self.created_at = None
        self.default = False

        super(VPC, self).__init__(*args, **kwargs)

    @classmethod
    def get_object(cls, api_token, vpc_id):
        """
            Class method that will return a VPC object by its ID.
        """
        vpc = cls(token=api_token, id=vpc_id)
        vpc.load()
        return vpc

    def load(self):
        """
            Load the VPC object from DigitalOcean.

            Requires self.id to be set.
        """
        data = self.get_data("vpcs/%s" % self.id)
        vpc = data["vpc"]

        for attr in vpc.keys():
            setattr(self, attr, vpc[attr])

        return self

    def create(self):
        """
            Create the VPC
        """
        params = {
            "name": self.name,
            "region": self.region,
            "description": self.description,
            "ip_range": self.ip_range
        }

        data = self.get_data("vpcs", type=POST, params=params)

        if data:
            self.id = data['vpc']['id']
            self.name = data['vpc']['name']
            self.region = data['vpc']['region']
            self.description = data['vpc']['description']
            self.ip_range = data['vpc']['ip_range']
            self.urn = data['vpc']['urn']
            self.created_at = data['vpc']['created_at']
            self.default = data['vpc']['default']

        return self

    def rename(self, new_name):
        """
            Rename a VPC

            Args:
                name (str): The new name for the VPC
        """
        data = self.get_data("vpcs/%s" % self.id,
                             type=PATCH,
                             params={"name": new_name})

        vpc = data["vpc"]

        for attr in vpc.keys():
            setattr(self, attr, vpc[attr])

        return self

    def rename(self, new_name):
        """
            Rename a VPC

            Args:
                name (str): The new name for the VPC
        """
        data = self.get_data("vpcs/%s" % self.id,
                             type=PATCH,
                             params={"name": new_name})

        vpc = data["vpc"]

        for attr in vpc.keys():
            setattr(self, attr, vpc[attr])

        return self

    def destroy(self):
        """
            Delete the VPC
        """
        return self.get_data("vpcs/%s" % self.id, type=DELETE)

    def __str__(self):
        return "<VPC: %s %s>" % (self.id, self.name) class Project(BaseAPI):
    def __init__(self,*args, **kwargs):
        self.name = None
        self.description = None
        self.purpose = None
        self.environment = None
        self.id = None
        self.is_default = None
        self.owner_uuid = None
        self.owner_id = None
        self.created_at = None
        self.updated_at = None
        self.resources = None
        super(Project,self).__init__(*args, **kwargs)

    @classmethod
    def get_object(cls, api_token, project_id):
        """Class method that will return a Project object by ID.
        Args:
            api_token (str): token
            kwargs (str): project id or project name
        """

        project = cls(token=api_token, id=project_id)
        project.load()
        return project

    def load(self):
        # URL https://api.digitalocean.com/v2/projects
        project = self.get_data("projects/%s" % self.id)
        project = project['project']
        for attr in project.keys():
            setattr(self, attr, project[attr])

    def set_as_default_project(self):

        data = {
            "name": self.name,
            "description": self.description,
            "purpose": self.purpose,
            "environment": self.environment,
            "is_default": True
        }

        project = self.get_data("projects/%s" % self.id, type=PUT, params=data)
        return project

    def create_project(self):

        """Creating Project with the following arguments
        Args:
            api_token (str): token
            "name": Name of the Project - Required
            "description": Description of the Project - Optional
            "purpose": Purpose of the project - Required
            "environment": Related Environment of  Project - Optional
                         - Development
                         - Stating
                         - Production
        """

        data = {
            "name": self.name,
            "purpose": self.purpose
        }
        if self.description:
            data['description'] = self.description
        if self.environment:
            data['environment'] = self.environment

        data = self.get_data("projects", type=POST, params=data)

        if data:
            self.id = data['project']['id']
            self.owner_uuid = data['project']['owner_uuid']
            self.owner_id = data['project']['owner_id']
            self.name = data['project']['name']
            self.description = data['project']['description']
            self.purpose = data['project']['purpose']
            self.environment = data['project']['environment']
            self.is_default = data['project']['is_default']
            self.created_at = data['project']['created_at']
            self.updated_at = data['project']['updated_at']

    def delete_project(self):
        data = dict()
        return self.get_data("projects/%s" % self.id, type=DELETE, params=data)

    def update_project(self, **kwargs):
        data = dict()
        data['name'] = kwargs.get("name", self.name)
        data['description'] = kwargs.get("description", self.description)
        data['purpose'] = kwargs.get("purpose", self.purpose)
        """
        Options for Purpose by Digital Ocean
         - Just Trying out DigitalOcean
         - Class Project / Educational Purposes
         - Website or blog
         - Web Application
         - Service or API
         - Mobile Application
         - Machine Learning / AI / Data Processing
         - IoT
         - Operational / Developer tooling
         - Other
        """
        data['environment'] = kwargs.get("environment", self.environment)
        """
        Options for Environment by Digital Ocean
         - Development
         - Stating
         - Production
        """
        data['is_default'] = kwargs.get("is_default", self.is_default)
        update_response = self.get_data("projects/%s" % self.id, type=PUT, params=data)
        for attr in update_response['project'].keys():
            setattr(self, attr, update_response['project'][attr])

    def get_all_resources(self):
        project_resources_response = self.get_data("projects/%s/resources" % self.id)
        project_resources = project_resources_response['resources']
        self.resources = []
        for i in project_resources:
            self.resources.append(i['urn'])
        return self.resources

    def load_resources(self):
        project_resources_response = self.get_data("projects/%s/resources" % self.id)
        project_resources = project_resources_response['resources']
        self.resources = []
        for i in project_resources:
            self.resources.append(i['urn'])

    def assign_resource(self, resources):
        data = {
            'resources': resources
        }
        return self.get_data("projects/%s/resources" % self.id, type=POST, params=data)

    def __str__(self):
        return "<Project: " + self.name + "> " + self.id
    from urlparse import urlparse, parse_qs
    from urllib.parse import urlparse, parse_qs                 # noqa
from .baseapi import BaseAPI
from .Account import Account
from .Action import Action
from .Balance import Balance
from .Certificate import Certificate
from .Domain import Domain
from .Droplet import Droplet
from .FloatingIP import FloatingIP
from .Firewall import Firewall, InboundRule, OutboundRule
from .Image import Image
from .LoadBalancer import LoadBalancer
from .LoadBalancer import StickySessions, HealthCheck, ForwardingRule
from .Region import Region
from .SSHKey import SSHKey
from .Size import Size
from .Snapshot import Snapshot
from .Tag import Tag
from .Volume import Volume
from .VPC import VPC
from .Project import Project
        return Droplet.get_object(api_token=self.tokens, droplet_id=droplet_id)

    def get_all_sizes(self):
        """
            This function returns a list of Size object.
        """
        data = self.get_data("sizes/")
        sizes = list()
        for jsoned in data['sizes']:
            size = Size(**jsoned)
            size.token = self.tokens
            sizes.append(size)
        return sizes

    def get_images(self, private=False, type=None):
        """
            This function returns a list of Image object.
        """
        params = {}
        if private:
            params['private'] = 'true'
        if type:
            params['type'] = type
        data = self.get_data("images/", params=params)
        images = list()
        for jsoned in data['images']:
            image = Image(**jsoned)
            image.token = self.tokens
            images.append(image)
        return images

    def get_all_images(self):
        """
            This function returns a list of Image objects containing all
            available DigitalOcean images, both public and private.
        """
        images = self.get_images()
        return images

    def get_image(self, image_id_or_slug):
        """
            Return a Image by its ID/Slug.
        """
        return Image.get_object(
            api_token=self.tokens,
            image_id_or_slug=image_id_or_slug,
        )

    def get_my_images(self):
        """
            This function returns a list of Image objects representing
            private DigitalOcean images (e.g. snapshots and backups).
        """
        images = self.get_images(private=True)
        return images

    def get_global_images(self):
        """
            This function returns a list of Image objects representing
            public DigitalOcean images (e.g. base distribution images
            and 'One-Click' applications).
        """
        data = self.get_images()
        images = list()
        for i in data:
            if i.public:
                i.token = self.tokens
                images.append(i)
        return images

    def get_distro_images(self):
        """
            This function returns a list of Image objects representing
            public base distribution images.
        """
        images = self.get_images(type='distribution')
        return images

    def get_app_images(self):
        """
            This function returns a list of Image objectobjects representing
            public DigitalOcean 'One-Click' application images.
        """
        images = self.get_images(type='application')
        return images

    def get_all_domains(self):
        """
            This function returns a list of Domain object.
        """
        data = self.get_data("domains/")
        domains = list()
        for jsoned in data['domains']:
            domain = Domain(**jsoned)
            domain.token = self.tokens
            domains.append(domain)
        return domains

    def get_domain(self, domain_name):
        """
            Return a Domain by its domain_name
        """
        return Domain.get_object(api_token=self.tokens, domain_name=domain_name)

    def get_all_sshkeys(self):
        """
            This function returns a list of SSHKey object.
        """
        data = self.get_data("account/keys/")
        ssh_keys = list()
        for jsoned in data['ssh_keys']:
            ssh_key = SSHKey(**jsoned)
            ssh_key.token = self.tokens
            ssh_keys.append(ssh_key)
        return ssh_keys

    def get_ssh_key(self, ssh_key_id):
        """
            Return a SSHKey object by its ID.
        """

        return SSHKey.get_object(api_token=self.tokens, ssh_key_id=ssh_key_id)