text: import glob
  import json
  import logging
  import math
  import os
  
  from os.path import basename
  from collections import namedtuple
  
  import requests
  
  API_VERSION = 'v2'
  __version__ = '1.0.0'
  DEBUG = False
  
  Return = namedtuple('Return', ('status_code', 'result'))
  
  
  def set_debug():
      global DEBUG
      logger = logging.getLogger()
      logger.level = logging.DEBUG
      DEBUG = True
  
  
  def show_debug_hints(req_type, endpoint, data, headers, resp):
      if DEBUG:
          logging.debug('http status code: %s' % resp.status_code)
          logging.debug('response body: %s' % resp.text)
          headers_s = ''.join(
              ' -H ' +
              '"%s: %s"' %
              (k,
               v) for (
                  k,
                  v) in list(
                  headers.items()))
          curl_cmd = 'curl -X %s %s -d "%s" %s' % (
              req_type, endpoint, json.dumps(data), headers_s)
          logging.debug(curl_cmd)
  
  
  class APIObject(object):
  
      def __init__(self, api_key, name, path=None, **kw):
          self.api_key = api_key
          self.name = name
          path = path or name
          self.path = path.format(**kw)
          for (k, v) in list(kw.items()):
              setattr(self, k, v)
  
      def send_req(self, req_type, path, data={}, params={}):
          req_calls = {
              'GET': requests.get,
              'POST': requests.post,
              'DELETE': requests.delete,
              'PUT': requests.put}
          headers = {
              'authorization': 'Bearer %s' % self.api_key,
              'Content-Type': 'application/json'}
          endpoint = 'https://api.digitalocean.com/%s/%s' % (API_VERSION, path)
          req_call = req_calls[req_type]
          resp = req_call(
              endpoint,
              params=params,
              data=json.dumps(data),
              headers=headers)
          status_code = resp.status_code
  
          # default status for request and returned values
          failed = False
          ret = None
  
          # requests is failes if statos is not in this list
          if status_code not in (200, 201, 202, 204):
              failed = True
  
          # If there's no response (No content), as for a DELETE method,
          # I can't get a json object
          if resp.text and resp.text != '':
              ret = resp.json()
  
          if failed or DEBUG:
              show_debug_hints(req_type, endpoint, data, headers, resp)
  
          if failed:
              raise Exception(resp.text)
  
          return Return(status_code, ret)
  
  
  class Resource(APIObject):
  
      def info(self):
          return self.send_req('GET', self.path)
  
      def update(self, **data):
          return self.send_req('PUT', self.path, data)
  
  
  class Collection(APIObject):
  
      def list(self, **params):
          """
          @params: per_page=10, page=4
              per_page: number of objects to include in result
              page: page number
          """
  
          # it returns a Return nametuple object
          return self.send_req('GET', self.path, params=params)
  
      def all(self):
          items = []
          resp = self.list()
          items.extend(resp.result[self.name])
          total = resp.result['meta']['total']
  
          # if total == len(items), math.ceil will be == 1
          more_no_reqs = math.ceil(total / len(items))
  
          # This seems easier to understand to me
          for i in range(1, more_no_reqs):
              # now I starts from 1. Getting next page
              resp = self.list(page=(i + 1))
              items.extend(resp.result[self.name])
  
          return items
  
      def create(self, **data):
          return self.send_req('POST', self.path, data)
  
      def delete(self, id):
          path = self.path + '/' + str(id)
          return self.send_req('DELETE', path)
  
  
  class Droplet(Resource):
  
      def ip_addresses(self):
          networks_v4 = self.info().result['droplet']['networks']['v4']
          return [net['ip_address'] for net in networks_v4]
  
      def status(self):
          return self.info().result['droplet']['status']
  
  
  class Droplets(Collection):
  
      def create(self, name, region, size, image, ssh_keys=None,
                 backups=False, ipv6=False, private_networking=False):
          valid_sizes = self.sizes.list().result['sizes']
          valid_size_slugs = [
              droplet_config['slug'] for droplet_config in valid_sizes]
          assert size in valid_size_slugs, 'Invalid droplet size: %s' % size
          data = dict(
              name=name,
              region=region,
              size=size,
              image=image,
              ssh_keys=ssh_keys,
              backups=backups,
              private_networking=private_networking)
          return self.send_req('POST', self.path, data)
  
  
  class Images(Collection):
  
      def search(self, word, region=None, show_op=False):
          """
          @region: <string> eg sgp1, nyc1
          @show_op: prints output
          """
          region = region and region.lower()
  
          def filter_image(image):
              distribution = image['distribution'].lower()
              slug = image['slug'] and image['slug'].lower() or ''
              if (word in distribution) or (word in slug):
                  if not region:
                      return True
                  else:
                      return region in image['regions']
              return False
  
          images = list(filter(filter_image, self.all()))
  
          if show_op:
              for image in images:
                  print(image['slug'], image['id'], image['distribution'])
  
          return images
  
  
  class DomainRecords(Collection):
  
      def Record(self, record_id):
          return Resource(self.api_key, self.path +
                          '/{record_id}', record_id=record_id)
  
  
  class Firewall(Resource):
      def add_droplet(self, droplet_id):
          """Add droplet to firewall"""
  
          # first get info from firewall to determine droplets assiged to it
          droplet_ids = self.info().result['firewall']['droplet_ids']
  
          if droplet_id in droplet_ids:
              logging.warning(
                  "Droplet {d} has already firewall {f}".format(
                      d=droplet_id, f=self.id))
              return
  
          else:
              # determine path
              path = "firewalls/{id}/droplets".format(id=self.id)
  
              # determine data
              data = {"droplet_ids": [droplet_id]}
  
              return self.send_req('POST', path, data)
  
      def remove_droplet(self, droplet_id):
          """Remove droplet from firewall"""
  
          # first get info from firewall to determine droplets assiged to it
          droplet_ids = self.info().result['firewall']['droplet_ids']
  
          if droplet_id not in droplet_ids:
              logging.warning(
                  "Droplet {d} hasn't firewall {f}".format(
                      d=droplet_id, f=self.id))
              return
  
          else:
              # determine path
              path = "firewalls/{id}/droplets".format(id=self.id)
  
              # determine data
              data = {"droplet_ids": [droplet_id]}
  
              return self.send_req('DELETE', path, data)
  
  
  class Firewalls(Collection):
      # override APIObject.create and return a Firewall object
      def create(self, **data):
          # call base method and create a firewall
          status, result = super().create(**data)
  
          # get firewall_id
          firewall_id = result['firewall']['id']
  
          # now get a Firewall instance
          return Firewall(self.api_key, 'firewalls/{id}', id=firewall_id)
  
      def get_by_name(self, name):
          """Return a Firewall object from a name"""
  
          # search for a firewall rule by name:
          # https://stackoverflow.com/a/29051598/4385116
          data = list(filter(lambda d: d['name'] in [name], self.all()))
  
          # get firewall id
          firewall_id = data[0]['id']
  
          # now get a Firewall instance
          return Firewall(self.api_key, 'firewalls/{id}', id=firewall_id)
  
  
  class Client(object):
  
      def __init__(self, api_key):
          self.api_key = api_key
          sizes = Collection(self.api_key, 'sizes')
          self.droplets = Droplets(self.api_key, 'droplets', sizes=sizes)
          self.images = Images(self.api_key, 'images')
          self.keys = Collection(self.api_key, 'ssh_keys', 'account/keys')
          self.domains = Collection(self.api_key, 'domains')
          self.firewalls = Firewalls(self.api_key, 'firewalls')
          self.sizes = sizes
  
      def Domain(self, domain):
          return Resource(self.api_key, 'domains/{domain}', domain=domain)
  
      def DomainRecords(self, domain, record_id=None):
          return DomainRecords(
              self.api_key, 'domains/{domain}/records', domain=domain)
  
      def Droplet(self, id):
          return Droplet(self.api_key, 'droplets/{id}', id=id)
  
      def sync_ssh_keys(self, keysdir):
          """
          sync local ssh keys directory containing all key files
          - uploads all keys in keysdir to digitalocean
          - removes any extra keys found at digitalocean
          """
          local_keys = dict((basename(path), open(path).read())
                            for path in glob.glob(os.path.join(keysdir, '*')))
          registered_keys = dict((key['name'], key)
                                 for key in self.keys.list().result['ssh_keys'])
          local_key_names = set(local_keys.keys())
          registered_key_names = set(registered_keys.keys())
          new_key_names = local_key_names.difference(registered_key_names)
          keynames_to_discard = registered_key_names.difference(local_keys)
          for name in new_key_names:
              self.keys.create(name=name, public_key=local_keys[name])
          for name in keynames_to_discard:
              self.keys.delete(registered_keys[name]['id'])
          final_keys = self.keys.list().result['ssh_keys']
          return {'new': new_key_names, 'deleted': keynames_to_discard,
                  'all_ids': [key['id'] for key in final_keys]}
  