Repository: influxdata/influxdb-client-python

Issue:
`InfluxDBClient.from_config_file()` incompatible with config files generated by `influx setup`
The method `InfluxDBClient.from_config_file()` does not seem to be compatible with the config file format generated by `influx setup` due to quotation marks around strings. Would it be better to make the client deal with the quotation marks correctly, perhaps by `.strip()`-ing the quote characters, or would it be better to change the config file format generated by `influx setup`?

__Steps to reproduce:__

1. Run the following in shell on new install of influxdb2 (added line breaks for clarity):
```
influx setup
--bucket=telem
--name=influx2  # name expected by InfluxDBClient.from_config_file()
--org=moms-sewing-room
--password=fhqwhgads
--username=edgar_jr
--force
```

This creates the file ~/.influxdbv2/configs where the first un-commented section is as below, note the quotation marks around all the strings:
```
[influx2]
  url = "http://localhost:8086"
  token = "7CkNpNw7A6rmeyc_vp0zYPGoOhcZ9zji9C0_RDG1rvb75hQ-JdMyOU81l4SfXJcumH3De1l7O85kODONyzYCKA=="
  org = "moms-sewing-room"
  active = true
```
2. Run the following in a Python interpreter:
```
import time
from datetime import datetime
from influxdb_client import InfluxDBClient, Point, WritePrecision
from influxdb_client.client.write_api import WriteOptions

client = InfluxDBClient.from_config_file('~/.influxdbv2/configs')
write_api = client.write_api(
        write_options=WriteOptions(
            batch_size=500,  # number of data points per batch
            flush_interval=1000,  # milliseconds before batch is written
            jitter_interval=0,
            retry_interval=1000,  # milliseconds before retry after failed write
            max_retries=5,
            max_retry_delay=10_000,  # give up after this many milliseconds
            exponential_base=2
        )
    )

point = Point.from_dict(
    dictionary={
        'measurement': 'my_measurement',
        'fields': {'field1': 1.0},
        'time': datetime.utcnow(),
    },
    write_precision=WritePrecision.NS,
)
write_api.write(bucket='telem', record=point)

# wait long enough for write attempts to fail
time.sleep(15)
```

__Expected behavior:__
Points are written to the bucket successfully.

__Actual behavior:__
Multiple unsuccessful attempts to write points, emitting the following to the console:

```
The retriable error occurred during request. Reason: '<urllib3.connection.HTTPConnection object at 0x7fbf5b9150d0>: Failed to establish a new connection: [Errno -2] Name or service not known'.
The retriable error occurred during request. Reason: '<urllib3.connection.HTTPConnection object at 0x7fbf5b8fdbe0>: Failed to establish a new connection: [Errno -2] Name or service not known'.
The retriable error occurred during request. Reason: '<urllib3.connection.HTTPConnection object at 0x7fbf5b913640>: Failed to establish a new connection: [Errno -2] Name or service not known'.
The retriable error occurred during request. Reason: '<urllib3.connection.HTTPConnection object at 0x7fbf5b915970>: Failed to establish a new connection: [Errno -2] Name or service not known'.
The retriable error occurred during request. Reason: '<urllib3.connection.HTTPConnection object at 0x7fbf5b915640>: Failed to establish a new connection: [Errno -2] Name or service not known'.
The batch item wasn't processed successfully because: HTTPConnectionPool(host='"http', port=80): Max retries exceeded with url: //localhost:8086%22/api/v2/write?org=%22moms-sewing-room%22&bucket=telem&precision=ns (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x7fbf5b913af0>: Failed to establish a new connection: [Errno -2] Name or service not known'))
```

The useful clue is this part:
```
Max retries exceeded with url: //localhost:8086%22/api/v2/write?org=%22moms-sewing-room%22&bucket=telem&precision=ns
```

__Additional details:__

Based on the error message, clearly the URL is missing the "http:" up front, and there are a bunch of "%22" instances scattered around, which is the URL encoding for `"`. Inspecting the `InfluxDBClient` object that was created:

```
In [5]: client.url
Out[5]: '"http://localhost:8086"'
```

The strings from the config file were captured with the quotation marks included (this applies to token and org as well). If I edit the config file to remove the quotation marks I'm able to write to the database with no problems.

User Franky1 [replied](https://community.influxdata.com/t/strings-in-config-file-created-by-influx-setup-incompatible-with-python-influxdbclient-from-config-file/18659/2?u=nerdenceman) to my post on the user forum with some insight:

> The Python client uses the python standard library `configparser` to read the ini file. The configparser lib does not actually expect quotes in the ini file for strings as a value. If it does, the quotes are included in the read string. This seems to be the case here.


__Specifications:__
 - Client Version: 1.14.0
 - InfluxDB Version: 2.0.4
 - Platform: Linux Mint 20.1 (based on Ubuntu Focal)

Discussion:
codecov[bot]: # [Codecov](https://codecov.io/gh/influxdata/influxdb-client-python/pull/203?src=pr&el=h1) Report
> Merging [#203](https://codecov.io/gh/influxdata/influxdb-client-python/pull/203?src=pr&el=desc) (7759e72) into [master](https://codecov.io/gh/influxdata/influxdb-client-python/commit/75f457964671b2fc97ae7600386876d7d76f3db4?el=desc) (75f4579) will **increase** coverage by `0.01%`.
> The diff coverage is `100.00%`.

[![Impacted file tree graph](https://codecov.io/gh/influxdata/influxdb-client-python/pull/203/graphs/tree.svg?width=650&height=150&src=pr&token=PcyupQSPFt)](https://codecov.io/gh/influxdata/influxdb-client-python/pull/203?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master     #203      +/-   ##
==========================================
+ Coverage   89.93%   89.94%   +0.01%     
==========================================
  Files          26       26              
  Lines        1967     1970       +3     
==========================================
+ Hits         1769     1772       +3     
  Misses        198      198              
```


| [Impacted Files](https://codecov.io/gh/influxdata/influxdb-client-python/pull/203?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [influxdb\_client/client/influxdb\_client.py](https://codecov.io/gh/influxdata/influxdb-client-python/pull/203/diff?src=pr&el=tree#diff-aW5mbHV4ZGJfY2xpZW50L2NsaWVudC9pbmZsdXhkYl9jbGllbnQucHk=) | `98.48% <100.00%> (+0.03%)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/influxdata/influxdb-client-python/pull/203?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/influxdata/influxdb-client-python/pull/203?src=pr&el=footer). Last update [75f4579...7759e72](https://codecov.io/gh/influxdata/influxdb-client-python/pull/203?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 36afb4f..249f243 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,8 @@
 ## 1.16.0 [unreleased]
 
+### Features
+1. [#203](https://github.com/influxdata/influxdb-client-python/pull/203): Allow configuring client via TOML file.
+
 ### Documentation
 1. [#202](https://github.com/influxdata/influxdb-client-python/pull/202): Added an example how to use RxPY and sync batching
 
diff --git a/README.rst b/README.rst
index cc9d396..12b9a52 100644
--- a/README.rst
+++ b/README.rst
@@ -379,7 +379,7 @@ _______
 Via Configuration file
 ______________________
 
-In a ini configuration file you are able to specify default tags by ``tags`` segment.
+In a `init <https://docs.python.org/3/library/configparser.html>`_ configuration file you are able to specify default tags by ``tags`` segment.
 
 .. code-block:: python
 
@@ -398,6 +398,8 @@ In a ini configuration file you are able to specify default tags by ``tags`` seg
     customer = California Miner
     data_center = ${env.data_center}
 
+You could also use a `TOML <https://toml.io/en/>`_ format for the configuration file.
+
 Via Environment Properties
 __________________________
 You are able to specify default tags by environment properties with prefix ``INFLUXDB_V2_TAG_``.
diff --git a/influxdb_client/client/influxdb_client.py b/influxdb_client/client/influxdb_client.py
index 35765b2..e078f50 100644
--- a/influxdb_client/client/influxdb_client.py
+++ b/influxdb_client/client/influxdb_client.py
@@ -69,44 +69,79 @@ class InfluxDBClient(object):
     @classmethod
     def from_config_file(cls, config_file: str = "config.ini", debug=None, enable_gzip=False):
         """
-        Configure client via '*.ini' file in segment 'influx2'.
+        Configure client via configuration file. The configuration has to be under 'influx' section.
 
-        Supported options:
+        The supported formats:
+            - https://docs.python.org/3/library/configparser.html
+            - https://toml.io/en/
+
+        Configuration options:
             - url
             - org
             - token
             - timeout,
             - verify_ssl
             - ssl_ca_cert
+
+        config.ini example::
+
+            [influx2]
+            url=http://localhost:8086
+            org=my-org
+            token=my-token
+            timeout=6000
+
+            [tags]
+            id = 132-987-655
+            customer = California Miner
+            data_center = ${env.data_center}
+
+        config.toml example::
+
+            [influx2]
+                url = "http://localhost:8086"
+                token = "my-token"
+                org = "my-org"
+                timeout = 6000
+
+            [tags]
+                id = "132-987-655"
+                customer = "California Miner"
+                data_center = "${env.data_center}"
+
         """
         config = configparser.ConfigParser()
         config.read(config_file)
 
-        url = config['influx2']['url']
-        token = config['influx2']['token']
+        def config_value(key: str):
+            return config['influx2'][key].strip('"')
+
+        url = config_value('url')
+        token = config_value('token')
 
         timeout = None
 
         if config.has_option('influx2', 'timeout'):
-            timeout = config['influx2']['timeout']
+            timeout = config_value('timeout')
 
         org = None
 
         if config.has_option('influx2', 'org'):
-            org = config['influx2']['org']
+            org = config_value('org')
 
         verify_ssl = True
         if config.has_option('influx2', 'verify_ssl'):
-            verify_ssl = config['influx2']['verify_ssl']
+            verify_ssl = config_value('verify_ssl')
 
         ssl_ca_cert = None
         if config.has_option('influx2', 'ssl_ca_cert'):
-            ssl_ca_cert = config['influx2']['ssl_ca_cert']
+            ssl_ca_cert = config_value('ssl_ca_cert')
 
         default_tags = None
 
         if config.has_section('tags'):
-            default_tags = dict(config.items('tags'))
+            tags = {k: v.strip('"') for k, v in config.items('tags')}
+            default_tags = dict(tags)
 
         if timeout:
             return cls(url, token, debug=debug, timeout=int(timeout), org=org, default_tags=default_tags,


Test patch:
diff --git a/tests/config.toml b/tests/config.toml
new file mode 100644
index 0000000..8f9522e
--- /dev/null
+++ b/tests/config.toml
@@ -0,0 +1,11 @@
+[influx2]
+  url = "http://localhost:8086"
+  token = "my-token"
+  org = "my-org"
+  active = true
+  timeout = 6000
+
+[tags]
+  id = "132-987-655"
+  customer = "California Miner"
+  data_center = "${env.data_center}"
diff --git a/tests/test_InfluxDBClient.py b/tests/test_InfluxDBClient.py
index 241fa9a..894a7f6 100644
--- a/tests/test_InfluxDBClient.py
+++ b/tests/test_InfluxDBClient.py
@@ -47,6 +47,26 @@ class InfluxDBClientTest(unittest.TestCase):
         self.assertEqual(health.status, "pass")
         self.assertEqual(health.name, "influxdb")
 
+    def test_init_from_ini_file(self):
+        self.client = InfluxDBClient.from_config_file(f'{os.path.dirname(__file__)}/config.ini')
+
+        self.assertConfig()
+
+    def test_init_from_toml_file(self):
+        self.client = InfluxDBClient.from_config_file(f'{os.path.dirname(__file__)}/config.toml')
+
+        self.assertConfig()
+
+    def assertConfig(self):
+        self.assertEqual("http://localhost:8086", self.client.url)
+        self.assertEqual("my-org", self.client.org)
+        self.assertEqual("my-token", self.client.token)
+        self.assertEqual(6000, self.client.timeout)
+        self.assertEqual(3, len(self.client.default_tags))
+        self.assertEqual("132-987-655", self.client.default_tags["id"])
+        self.assertEqual("California Miner", self.client.default_tags["customer"])
+        self.assertEqual("${env.data_center}", self.client.default_tags["data_center"])
+
     def test_init_from_file_ssl_default(self):
         self.client = InfluxDBClient.from_config_file(f'{os.path.dirname(__file__)}/config.ini')