text: # -*- coding: utf-8 -*-
  """Python client for InfluxDB v0.8."""
  
  import warnings
  from sys import version_info
  
  import json
  import socket
  import requests
  import requests.exceptions
  
  from influxdb import chunked_json
  
  try:
      xrange
  except NameError:
      xrange = range
  
  if version_info[0] == 3:
      from urllib.parse import urlparse
  else:
      from urlparse import urlparse
  
  session = requests.Session()
  
  
  class InfluxDBClientError(Exception):
      """Raised when an error occurs in the request."""
  
      def __init__(self, content, code=-1):
          """Initialize an InfluxDBClientError handler."""
          super(InfluxDBClientError, self).__init__(
              "{0}: {1}".format(code, content))
          self.content = content
          self.code = code
  
  
  class InfluxDBClient(object):
      """Define the standard InfluxDBClient for influxdb v0.8.
  
      The ``InfluxDBClient`` object holds information necessary to connect
      to InfluxDB. Requests can be made to InfluxDB directly through the client.
  
      :param host: hostname to connect to InfluxDB, defaults to 'localhost'
      :type host: string
      :param port: port to connect to InfluxDB, defaults to 'localhost'
      :type port: int
      :param username: user to connect, defaults to 'root'
      :type username: string
      :param password: password of the user, defaults to 'root'
      :type password: string
      :param database: database name to connect to, defaults is None
      :type database: string
      :param ssl: use https instead of http to connect to InfluxDB, defaults is
          False
      :type ssl: boolean
      :param verify_ssl: verify SSL certificates for HTTPS requests, defaults is
          False
      :type verify_ssl: boolean
      :param retries: number of retries your client will try before aborting,
          defaults to 3. 0 indicates try until success
      :type retries: int
      :param timeout: number of seconds Requests will wait for your client to
          establish a connection, defaults to None
      :type timeout: int
      :param use_udp: use UDP to connect to InfluxDB, defaults is False
      :type use_udp: int
      :param udp_port: UDP port to connect to InfluxDB, defaults is 4444
      :type udp_port: int
      """
  
      def __init__(self,
                   host='localhost',
                   port=8086,
                   username='root',
                   password='root',
                   database=None,
                   ssl=False,
                   verify_ssl=False,
                   timeout=None,
                   retries=3,
                   use_udp=False,
                   udp_port=4444):
          """Construct a new InfluxDBClient object."""
          self._host = host
          self._port = port
          self._username = username
          self._password = password
          self._database = database
          self._timeout = timeout
          self._retries = retries
  
          self._verify_ssl = verify_ssl
  
          self._use_udp = use_udp
          self._udp_port = udp_port
          if use_udp:
              self.udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
  
          self._scheme = "http"
  
          if ssl is True:
              self._scheme = "https"
  
          self._baseurl = "{0}://{1}:{2}".format(
              self._scheme,
              self._host,
              self._port)
  
          self._headers = {
              'Content-type': 'application/json',
              'Accept': 'text/plain'}
  
      @staticmethod
      def from_dsn(dsn, **kwargs):
          r"""Return an instaance of InfluxDBClient from given data source name.
  
          Returns an instance of InfluxDBClient from the provided data source
          name. Supported schemes are "influxdb", "https+influxdb",
          "udp+influxdb". Parameters for the InfluxDBClient constructor may be
          also be passed to this function.
  
          Examples:
              >> cli = InfluxDBClient.from_dsn('influxdb://username:password@\
              ... localhost:8086/databasename', timeout=5)
              >> type(cli)
              <class 'influxdb.client.InfluxDBClient'>
              >> cli = InfluxDBClient.from_dsn('udp+influxdb://username:pass@\
              ... localhost:8086/databasename', timeout=5, udp_port=159)
              >> print('{0._baseurl} - {0.use_udp} {0.udp_port}'.format(cli))
              http://localhost:8086 - True 159
  
          :param dsn: data source name
          :type dsn: string
          :param **kwargs: additional parameters for InfluxDBClient.
          :type **kwargs: dict
          :note: parameters provided in **kwargs may override dsn parameters.
          :note: when using "udp+influxdb" the specified port (if any) will be
          used for the TCP connection; specify the udp port with the additional
          udp_port parameter (cf. examples).
          :raise ValueError: if the provided DSN has any unexpected value.
  
          """
          init_args = {}
          conn_params = urlparse(dsn)
          scheme_info = conn_params.scheme.split('+')
  
          if len(scheme_info) == 1:
              scheme = scheme_info[0]
              modifier = None
          else:
              modifier, scheme = scheme_info
  
          if scheme != 'influxdb':
              raise ValueError('Unknown scheme "{0}".'.format(scheme))
  
          if modifier:
              if modifier == 'udp':
                  init_args['use_udp'] = True
              elif modifier == 'https':
                  init_args['ssl'] = True
              else:
                  raise ValueError('Unknown modifier "{0}".'.format(modifier))
  
          if conn_params.hostname:
              init_args['host'] = conn_params.hostname
          if conn_params.port:
              init_args['port'] = conn_params.port
          if conn_params.username:
              init_args['username'] = conn_params.username
          if conn_params.password:
              init_args['password'] = conn_params.password
          if conn_params.path and len(conn_params.path) > 1:
              init_args['database'] = conn_params.path[1:]
  
          init_args.update(kwargs)
  
          return InfluxDBClient(**init_args)
  
      # Change member variables
  
      def switch_database(self, database):
          """Change client database.
  
          :param database: the new database name to switch to
          :type database: string
          """
          self._database = database
  
      def switch_db(self, database):
          """Change client database.
  
          DEPRECATED.
          """
          warnings.warn(
              "switch_db is deprecated, and will be removed "
              "in future versions. Please use "
              "``InfluxDBClient.switch_database(database)`` instead.",
              FutureWarning)
          return self.switch_database(database)
  
      def switch_user(self, username, password):
          """Change client username.
  
          :param username: the new username to switch to
          :type username: string
          :param password: the new password to switch to
          :type password: string
          """
          self._username = username
          self._password = password
  
      def request(self, url, method='GET', params=None, data=None,
                  expected_response_code=200):
          """Make a http request to API."""
          url = "{0}/{1}".format(self._baseurl, url)
  
          if params is None:
              params = {}
  
          auth = {
              'u': self._username,
              'p': self._password
          }
  
          params.update(auth)
  
          if data is not None and not isinstance(data, str):
              data = json.dumps(data)
  
          retry = True
          _try = 0
          # Try to send the request more than once by default (see #103)
          while retry:
              try:
                  response = session.request(
                      method=method,
                      url=url,
                      params=params,
                      data=data,
                      headers=self._headers,
                      verify=self._verify_ssl,
                      timeout=self._timeout
                  )
                  break
              except (requests.exceptions.ConnectionError,
                      requests.exceptions.Timeout):
                  _try += 1
                  if self._retries != 0:
                      retry = _try < self._retries
          else:
              raise requests.exceptions.ConnectionError
  
          if response.status_code == expected_response_code:
              return response
          else:
              raise InfluxDBClientError(response.content, response.status_code)
  
      def write(self, data):
          """Provide as convenience for influxdb v0.9.0, this may change."""
          self.request(
              url="write",
              method='POST',
              params=None,
              data=data,
              expected_response_code=200
          )
          return True
  
      # Writing Data
      #
      # Assuming you have a database named foo_production you can write data
      # by doing a POST to /db/foo_production/series?u=some_user&p=some_password
      # with a JSON body of points.
  
      def write_points(self, data, time_precision='s', *args, **kwargs):
          """Write to multiple time series names.
  
          An example data blob is:
  
          data = [
              {
                  "points": [
                      [
                          12
                      ]
                  ],
                  "name": "cpu_load_short",
                  "columns": [
                      "value"
                  ]
              }
          ]
  
          :param data: A list of dicts in InfluxDB 0.8.x data format.
          :param time_precision: [Optional, default 's'] Either 's', 'm', 'ms'
              or 'u'.
          :param batch_size: [Optional] Value to write the points in batches
              instead of all at one time. Useful for when doing data dumps from
              one database to another or when doing a massive write operation
          :type batch_size: int
  
          """
          def list_chunks(l, n):
              """Yield successive n-sized chunks from l."""
              for i in xrange(0, len(l), n):
                  yield l[i:i + n]
  
          batch_size = kwargs.get('batch_size')
          if batch_size and batch_size > 0:
              for item in data:
                  name = item.get('name')
                  columns = item.get('columns')
                  point_list = item.get('points', [])
  
                  for batch in list_chunks(point_list, batch_size):
                      item = [{
                          "points": batch,
                          "name": name,
                          "columns": columns
                      }]
                      self._write_points(
                          data=item,
                          time_precision=time_precision)
              return True
  
          return self._write_points(data=data,
                                    time_precision=time_precision)
  
      def write_points_with_precision(self, data, time_precision='s'):
          """Write to multiple time series names.
  
          DEPRECATED.
          """
          warnings.warn(
              "write_points_with_precision is deprecated, and will be removed "
              "in future versions. Please use "
              "``InfluxDBClient.write_points(time_precision='..')`` instead.",
              FutureWarning)
          return self._write_points(data=data, time_precision=time_precision)
  
      def _write_points(self, data, time_precision):
          if time_precision not in ['s', 'm', 'ms', 'u']:
              raise Exception(
                  "Invalid time precision is given. (use 's', 'm', 'ms' or 'u')")
  
          if self._use_udp and time_precision != 's':
              raise Exception(
                  "InfluxDB only supports seconds precision for udp writes"
              )
  
          url = "db/{0}/series".format(self._database)
  
          params = {
              'time_precision': time_precision
          }
  
          if self._use_udp:
              self.send_packet(data)
          else:
              self.request(
                  url=url,
                  method='POST',
                  params=params,
                  data=data,
                  expected_response_code=200
              )
  
          return True
  
      # One Time Deletes
  
      def delete_points(self, name):
          """Delete an entire series."""
          url = "db/{0}/series/{1}".format(self._database, name)
  
          self.request(
              url=url,
              method='DELETE',
              expected_response_code=204
          )
  
          return True
  
      # Regularly Scheduled Deletes
  
      def create_scheduled_delete(self, json_body):
          """Create schedule delete from database.
  
          2013-11-08: This endpoint has not been implemented yet in ver0.0.8,
          but it is documented in http://influxdb.org/docs/api/http.html.
          See also: src/api/http/api.go:l57
  
          """
          raise NotImplementedError()
  
      # get list of deletes
      # curl http://localhost:8086/db/site_dev/scheduled_deletes
      #
      # remove a regularly scheduled delete
      # curl -X DELETE http://localhost:8086/db/site_dev/scheduled_deletes/:id
  
      def get_list_scheduled_delete(self):
          """Get list of scheduled deletes.
  
          2013-11-08: This endpoint has not been implemented yet in ver0.0.8,
          but it is documented in http://influxdb.org/docs/api/http.html.
          See also: src/api/http/api.go:l57
  
          """
          raise NotImplementedError()
  
      def remove_scheduled_delete(self, delete_id):
          """Remove scheduled delete.
  
          2013-11-08: This endpoint has not been implemented yet in ver0.0.8,
          but it is documented in http://influxdb.org/docs/api/http.html.
          See also: src/api/http/api.go:l57
  
          """
          raise NotImplementedError()
  
      def query(self, query, time_precision='s', chunked=False):
          """Query data from the influxdb v0.8 database.
  
          :param time_precision: [Optional, default 's'] Either 's', 'm', 'ms'
              or 'u'.
          :param chunked: [Optional, default=False] True if the data shall be
              retrieved in chunks, False otherwise.
          """
          return self._query(query, time_precision=time_precision,
                             chunked=chunked)
  
      # Querying Data
      #
      # GET db/:name/series. It takes five parameters
      def _query(self, query, time_precision='s', chunked=False):
          if time_precision not in ['s', 'm', 'ms', 'u']:
              raise Exception(
                  "Invalid time precision is given. (use 's', 'm', 'ms' or 'u')")
  
          if chunked is True:
              chunked_param = 'true'
          else:
              chunked_param = 'false'
  
          # Build the URL of the serie to query
          url = "db/{0}/series".format(self._database)
  
          params = {
              'q': query,
              'time_precision': time_precision,
              'chunked': chunked_param
          }
  
          response = self.request(
              url=url,
              method='GET',
              params=params,
              expected_response_code=200
          )
  
          if chunked:
              try:
                  decoded = chunked_json.loads(response.content.decode())
              except UnicodeDecodeError:
                  decoded = chunked_json.loads(response.content.decode('utf-8'))
  
              return list(decoded)
  
          return response.json()
  
      # Creating and Dropping Databases
      #
      # ### create a database
      # curl -X POST http://localhost:8086/db -d '{"name": "site_development"}'
      #
      # ### drop a database
      # curl -X DELETE http://localhost:8086/db/site_development
  
      def create_database(self, database):
          """Create a database on the InfluxDB server.
  
          :param database: the name of the database to create
          :type database: string
          :rtype: boolean
          """
          url = "db"
  
          data = {'name': database}
  
          self.request(
              url=url,
              method='POST',
              data=data,
              expected_response_code=201
          )
  
          return True
  
      def delete_database(self, database):
          """Drop a database on the InfluxDB server.
  
          :param database: the name of the database to delete
          :type database: string
          :rtype: boolean
          """
          url = "db/{0}".format(database)
  
          self.request(
              url=url,
              method='DELETE',
              expected_response_code=204
          )
  
          return True
  
      # ### get list of databases
      # curl -X GET http://localhost:8086/db
  
      def get_list_database(self):
          """Get the list of databases."""
          url = "db"
  
          response = self.request(
              url=url,
              method='GET',
              expected_response_code=200
          )
  
          return response.json()
  
      def get_database_list(self):
          """Get the list of databases.
  
          DEPRECATED.
          """
          warnings.warn(
              "get_database_list is deprecated, and will be removed "
              "in future versions. Please use "
              "``InfluxDBClient.get_list_database`` instead.",
              FutureWarning)
          return self.get_list_database()
  
      def delete_series(self, series):
          """Drop a series on the InfluxDB server.
  
          :param series: the name of the series to delete
          :type series: string
          :rtype: boolean
          """
          url = "db/{0}/series/{1}".format(
              self._database,
              series
          )
  
          self.request(
              url=url,
              method='DELETE',
              expected_response_code=204
          )
  
          return True
  
      def get_list_series(self):
          """Get a list of all time series in a database."""
          response = self._query('list series')
          return [series[1] for series in response[0]['points']]
  
      def get_list_continuous_queries(self):
          """Get a list of continuous queries."""
          response = self._query('list continuous queries')
          return [query[2] for query in response[0]['points']]
  
      # Security
      # get list of cluster admins
      # curl http://localhost:8086/cluster_admins?u=root&p=root
  
      # add cluster admin
      # curl -X POST http://localhost:8086/cluster_admins?u=root&p=root \
      #      -d '{"name": "paul", "password": "i write teh docz"}'
  
      # update cluster admin password
      # curl -X POST http://localhost:8086/cluster_admins/paul?u=root&p=root \
      #      -d '{"password": "new pass"}'
  
      # delete cluster admin
      # curl -X DELETE http://localhost:8086/cluster_admins/paul?u=root&p=root
  
      # Database admins, with a database name of site_dev
      # get list of database admins
      # curl http://localhost:8086/db/site_dev/admins?u=root&p=root
  
      # add database admin
      # curl -X POST http://localhost:8086/db/site_dev/admins?u=root&p=root \
      #      -d '{"name": "paul", "password": "i write teh docz"}'
  
      # update database admin password
      # curl -X POST http://localhost:8086/db/site_dev/admins/paul?u=root&p=root\
      #      -d '{"password": "new pass"}'
  
      # delete database admin
      # curl -X DELETE \
      #        http://localhost:8086/db/site_dev/admins/paul?u=root&p=root
  
      def get_list_cluster_admins(self):
          """Get list of cluster admins."""
          response = self.request(
              url="cluster_admins",
              method='GET',
              expected_response_code=200
          )
  
          return response.json()
  
      def add_cluster_admin(self, new_username, new_password):
          """Add cluster admin."""
          data = {
              'name': new_username,
              'password': new_password
          }
  
          self.request(
              url="cluster_admins",
              method='POST',
              data=data,
              expected_response_code=200
          )
  
          return True
  
      def update_cluster_admin_password(self, username, new_password):
          """Update cluster admin password."""
          url = "cluster_admins/{0}".format(username)
  
          data = {
              'password': new_password
          }
  
          self.request(
              url=url,
              method='POST',
              data=data,
              expected_response_code=200
          )
  
          return True
  
      def delete_cluster_admin(self, username):
          """Delete cluster admin."""
          url = "cluster_admins/{0}".format(username)
  
          self.request(
              url=url,
              method='DELETE',
              expected_response_code=200
          )
  
          return True
  
      def set_database_admin(self, username):
          """Set user as database admin."""
          return self.alter_database_admin(username, True)
  
      def unset_database_admin(self, username):
          """Unset user as database admin."""
          return self.alter_database_admin(username, False)
  
      def alter_database_admin(self, username, is_admin):
          """Alter the database admin."""
          url = "db/{0}/users/{1}".format(self._database, username)
  
          data = {'admin': is_admin}
  
          self.request(
              url=url,
              method='POST',
              data=data,
              expected_response_code=200
          )
  
          return True
  
      def get_list_database_admins(self):
          """Get list of database admins.
  
          2013-11-08: This endpoint has not been implemented yet in ver0.0.8,
          but it is documented in http://influxdb.org/docs/api/http.html.
          See also: src/api/http/api.go:l57
  
          """
          raise NotImplementedError()
  
      def add_database_admin(self, new_username, new_password):
          """Add cluster admin.
  
          2013-11-08: This endpoint has not been implemented yet in ver0.0.8,
          but it is documented in http://influxdb.org/docs/api/http.html.
          See also: src/api/http/api.go:l57
  
          """
          raise NotImplementedError()
  
      def update_database_admin_password(self, username, new_password):
          """Update database admin password.
  
          2013-11-08: This endpoint has not been implemented yet in ver0.0.8,
          but it is documented in http://influxdb.org/docs/api/http.html.
          See also: src/api/http/api.go:l57
  
          """
          raise NotImplementedError()
  
      def delete_database_admin(self, username):
          """Delete database admin.
  
          2013-11-08: This endpoint has not been implemented yet in ver0.0.8,
          but it is documented in http://influxdb.org/docs/api/http.html.
          See also: src/api/http/api.go:l57
  
          """
          raise NotImplementedError()
  
      ###
      # Limiting User Access
  
      # Database users
      # get list of database users
      # curl http://localhost:8086/db/site_dev/users?u=root&p=root
  
      # add database user
      # curl -X POST http://localhost:8086/db/site_dev/users?u=root&p=root \
      #       -d '{"name": "paul", "password": "i write teh docz"}'
  
      # update database user password
      # curl -X POST http://localhost:8086/db/site_dev/users/paul?u=root&p=root \
      #       -d '{"password": "new pass"}'
  
      # delete database user
      # curl -X DELETE http://localhost:8086/db/site_dev/users/paul?u=root&p=root
  
      def get_database_users(self):
          """Get list of database users."""
          url = "db/{0}/users".format(self._database)
  
          response = self.request(
              url=url,
              method='GET',
              expected_response_code=200
          )
  
          return response.json()
  
      def add_database_user(self, new_username, new_password, permissions=None):
          """Add database user.
  
          :param permissions: A ``(readFrom, writeTo)`` tuple
          """
          url = "db/{0}/users".format(self._database)
  
          data = {
              'name': new_username,
              'password': new_password
          }
  
          if permissions:
              try:
                  data['readFrom'], data['writeTo'] = permissions
              except (ValueError, TypeError):
                  raise TypeError(
                      "'permissions' must be (readFrom, writeTo) tuple"
                  )
  
          self.request(
              url=url,
              method='POST',
              data=data,
              expected_response_code=200
          )
  
          return True
  
      def update_database_user_password(self, username, new_password):
          """Update password."""
          return self.alter_database_user(username, new_password)
  
      def alter_database_user(self, username, password=None, permissions=None):
          """Alter a database user and/or their permissions.
  
          :param permissions: A ``(readFrom, writeTo)`` tuple
          :raise TypeError: if permissions cannot be read.
          :raise ValueError: if neither password nor permissions provided.
          """
          url = "db/{0}/users/{1}".format(self._database, username)
  
          if not password and not permissions:
              raise ValueError("Nothing to alter for user {0}.".format(username))
  
          data = {}
  
          if password:
              data['password'] = password
  
          if permissions:
              try:
                  data['readFrom'], data['writeTo'] = permissions
              except (ValueError, TypeError):
                  raise TypeError(
                      "'permissions' must be (readFrom, writeTo) tuple"
                  )
  
          self.request(
              url=url,
              method='POST',
              data=data,
              expected_response_code=200
          )
  
          if username == self._username:
              self._password = password
  
          return True
  
      def delete_database_user(self, username):
          """Delete database user."""
          url = "db/{0}/users/{1}".format(self._database, username)
  
          self.request(
              url=url,
              method='DELETE',
              expected_response_code=200
          )
  
          return True
  
      # update the user by POSTing to db/site_dev/users/paul
  
      def update_permission(self, username, json_body):
          """Update read/write permission.
  
          2013-11-08: This endpoint has not been implemented yet in ver0.0.8,
          but it is documented in http://influxdb.org/docs/api/http.html.
          See also: src/api/http/api.go:l57
  
          """
          raise NotImplementedError()
  
      def send_packet(self, packet):
          """Send a UDP packet along the wire."""
          data = json.dumps(packet)
          byte = data.encode('utf-8')
          self.udp_socket.sendto(byte, (self._host, self._udp_port))
  