Repository: InfluxCommunity/influxdb3-python

Issue:
pyarrow._flight.FlightUnavailableError: Flight returned unavailable error, with message: failed to connect to all addresses; last error: UNKNOWN:  Ssl handshake failed: SSL_ERROR_SSL
### Specifications

* Client Version: influxdb3-python   0.10.0
* InfluxDB Version: influxdb:20240819-1176644
* Platform: MacOS Sequoia Version 15.2 (24C101)


### Code sample to reproduce problem
```
from influxdb_client_3 import InfluxDBClient3


host = "influx.ntw.xxx.yyy"
database_name = "test/5m"
token = "my_api_key"
table = "test"

client = InfluxDBClient3(host=host,
                        database=database_name,
                        token=token,
                        verify_ssl=False,
                        
                        )

result = client.query("SELECT * from test")

print(result)
```
### Expected behavior

(.venv) petr@test:~/influx3-playground$ python influx3_read.py 
pyarrow.Table
co: int64
hum: double
room: string
sensor: string
temp: double
time: timestamp[ns] not null
----
co: [[0,0,0,0,0,1,0,0,0,0,0],[0],[4]]
hum: [[35.9,36.2,36.1,36,36,36.5,35.9,35.9,36,36,35.9],[36],[36.9]]
room: [["Kitchen","Kitchen","Kitchen","Kitchen","Kitchen","Kitchen","Living Room","Living Room","Living Room","Living Room","Living Room"],["Living Room"],["garaga"]]
sensor: [[null,null,null,null,null,null,null,null,null,null,null],[null],["K001"]]
temp: [[21,23,22.7,22.4,22.5,22.8,21.1,21.4,21.8,22.2,22.2],[22.4],[72.2]]
time: [[2024-12-19 11:40:00.000000000,2024-12-19 12:40:00.000000000,2024-12-19 13:40:00.000000000,2024-12-19 14:40:00.000000000,2024-12-19 15:40:00.000000000,2024-12-19 16:40:00.000000000,2024-12-19 11:40:00.000000000,2024-12-19 12:40:00.000000000,2024-12-19 13:40:00.000000000,2024-12-19 14:40:00.000000000,2024-12-19 15:40:00.000000000],[2024-12-19 16:40:00.000000000],[2024-12-18 20:00:00.000000000]]

### Actual behavior

E0123 06:31:31.376124000 8673821248 ssl_transport_security.cc:1501]    Handshake failed with fatal error SSL_ERROR_SSL: error:0A000086:SSL routines::certificate verify failed.
Traceback (most recent call last):
  File "/Users/petrsimik/Projects/Work/network.git.cz.o2/influx3-playground/influx3_read.py", line 17, in <module>
    result = client.query("SELECT * from test")
  File "/Users/petrsimik/Projects/Work/network.git.cz.o2/influx3-playground/.venv/lib/python3.13/site-packages/influxdb_client_3/__init__.py", line 267, in query
    return self._query_api.query(query=query, language=language, mode=mode, database=database, **kwargs)
           ~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/petrsimik/Projects/Work/network.git.cz.o2/influx3-playground/.venv/lib/python3.13/site-packages/influxdb_client_3/query/query_api.py", line 97, in query
    raise e
  File "/Users/petrsimik/Projects/Work/network.git.cz.o2/influx3-playground/.venv/lib/python3.13/site-packages/influxdb_client_3/query/query_api.py", line 81, in query
    flight_reader = self._do_get(ticket, _options)
  File "/Users/petrsimik/Projects/Work/network.git.cz.o2/influx3-playground/.venv/lib/python3.13/site-packages/influxdb_client_3/query/query_api.py", line 100, in _do_get
    return self._flight_client.do_get(ticket, options)
           ~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^
  File "pyarrow/_flight.pyx", line 1633, in pyarrow._flight.FlightClient.do_get
  File "pyarrow/_flight.pyx", line 68, in pyarrow._flight.check_flight_status
pyarrow._flight.FlightUnavailableError: Flight returned unavailable error, with message: failed to connect to all addresses; last error: UNKNOWN: ipv4:10.58.71.20:443: Ssl handshake failed: SSL_ERROR_SSL: error:0A000086:SSL routines::certificate verify failed. gRPC client debug context: UNKNOWN:failed to connect to all addresses; last error: UNKNOWN: ipv4:10.58.71.20:443: Ssl handshake failed: SSL_ERROR_SSL: error:0A000086:SSL routines::certificate verify failed {grpc_status:14, created_time:"2025-01-23T06:31:31.378154+01:00"}. Client context: IOError: Server never sent a data message. Detail: Internal

### Additional info

So I tried ssl False, enforce context or all other  options and still getting the err

`python
from influxdb_client_3 import InfluxDBClient3
import ssl 

cert_path = "influx.ntw.xxx.yy.pem"
ssl_context = ssl.create_default_context(cafile=cert_path)

host = "influx.ntw..xxx.yy"
database_name = "test_petr/5m"
token = "token"
table = "test"

client = InfluxDBClient3(host=host,
                        database=database_name,
                        token=token,
                        # verify_ssl=False,
                        ssl_context=ssl_context
                        )

result = client.query("SELECT * from test")

print(result)
`

the same err:

`
E0123 11:
47:58.190666000 8673821248 ssl_transport_security.cc:1501]    Handshake failed with fatal error SSL_ERROR_SSL: error:0A000086:SSL routines::certificate verify failed.
Traceback (most recent call last):
  File "/Users/petrsimik/Projects/Work/network.git.cz.o2/influx3-playground/influx3_read.py", line 19, in <module>
    result = client.query("SELECT * from test")
  File "/Users/petrsimik/Projects/Work/network.git.cz.o2/influx3-playground/.venv/lib/python3.13/site-packages/influxdb_client_3/__init__.py", line 267, in query
    return self._query_api.query(query=query, language=language, mode=mode, database=database, **kwargs)
           ~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/petrsimik/Projects/Work/network.git.cz.o2/influx3-playground/.venv/lib/python3.13/site-packages/influxdb_client_3/query/query_api.py", line 97, in query
    raise e
  File "/Users/petrsimik/Projects/Work/network.git.cz.o2/influx3-playground/.venv/lib/python3.13/site-packages/influxdb_client_3/query/query_api.py", line 81, in query
    flight_reader = self._do_get(ticket, _options)
  File "/Users/petrsimik/Projects/Work/network.git.cz.o2/influx3-playground/.venv/lib/python3.13/site-packages/influxdb_client_3/query/query_api.py", line 100, in _do_get
    return self._flight_client.do_get(ticket, options)
           ~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^
  File "pyarrow/_flight.pyx", line 1633, in pyarrow._flight.FlightClient.do_get
  File "pyarrow/_flight.pyx", line 68, in pyarrow._flight.check_flight_status
pyarrow._flight.FlightUnavailableError: Flight returned unavailable error, with message: failed to connect to all addresses; last error: UNKNOWN: ipv4:10.58.71.20:443: Ssl handshake failed: SSL_ERROR_SSL: error:0A000086:SSL routines::certificate verify failed. gRPC client debug context: UNKNOWN:failed to connect to all addresses; last error: UNKNOWN: ipv4:10.58.71.20:443: Ssl handshake failed: SSL_ERROR_SSL: error:0A000086:SSL routines::certificate verify failed {grpc_status:14, created_time:"2025-01-23T11:47:58.191946+01:00"}. Client context: IOError: Server never sent a data message. Detail: Internal
`

checking ssl handshake works
`
import ssl
import socket

def evaluate_ssl_handshake(host, port=443):
    try:
        # Create a custom SSL context with your CA bundle
        context = ssl.create_default_context(cafile="influx.ntw.xxx.yy.pem")
        
        # Set up the socket with an SSL context
        with socket.create_connection((host, port)) as sock:
            with context.wrap_socket(sock, server_hostname=host) as ssl_sock:
                # Print SSL/TLS certificate details
                cert = ssl_sock.getpeercert()
                print(f"SSL handshake to {host} successful!")
                print("Certificate details:")
                for key, value in cert.items():
                    print(f"{key}: {value}")
                print(f"TLS version: {ssl_sock.version()}")
                print(f"Cipher suite: {ssl_sock.cipher()}")
    except ssl.SSLError as e:
        print(f"SSL handshake failed: {e}")
    except Exception as e:
        print(f"An error occurred: {e}")

# Specify the host and port
host = "influx.ntw..xxx.yy"
evaluate_ssl_handshake(host)
`
this results 
 python stash/shake2.py
SSL handshake to influx.ntw.xxx.yy successful!

Discussion:
@bednar: Hi @PetrSimik, it looks like there’s an issue with the detection of SSL communication on our side. Can you try adding https as a prefix to the host?@PetrSimik: I tried https and the same result@scaleoutsean: I'm getting this as well and I'm not even trying to use HTTPS. 

It'd be good to have an example on how to avoid HTTPS when connecting and use http://...:8181 to avoid overheads when evaluating or prototyping. 





@bednar: @karel-rehor working on this@karel-rehor: I've managed to reproduce this using a self-signed certificate and the following client configuration 

```
...
    client = InfluxDBClient3(host=host,
                             database=database,
                             org=org,
                             token=token,
                             auth_scheme="Bearer",
                             debug=True,
                             verify_ssl=True,
                             ssl_ca_cert="/home/pipin/bonitoo/qa/temp/influxdb3-python-issue117/cert.pem")
...
```
Which on query results in... 
```
E0219 10:39:05.227325446   14498 ssl_transport_security.cc:1501]       
Handshake failed with fatal error SSL_ERROR_SSL: error:0A000086:SSL routines::certificate verify failed.

tests/test_explore_issue117.py:16 (test_all)
def test_all():
        print("\ninfluxdb3 version: " + VERSION)
...
E0219 10:39:05.227325446   14498 ssl_transport_security.cc:1501]       
Handshake failed with fatal error SSL_ERROR_SSL: error:0A000086:SSL routines::certificate verify failed.

tests/test_explore_issue117.py:16 (test_all)
def test_all():
        print("\ninfluxdb3 version: " + VERSION)
...
E   pyarrow._flight.FlightUnavailableError: Flight returned unavailable error, with message: failed to connect to all addresses; 
last error: UNKNOWN: ipv4:44.231.79.1:443: Ssl handshake failed: SSL_ERROR_SSL: error:0A000086:SSL routines::certificate verify failed. 
gRPC client debug context: UNKNOWN:failed to connect to all addresses; 
last error: UNKNOWN: ipv4:44.231.79.1:443: Ssl handshake failed: SSL_ERROR_SSL: error:0A000086:SSL routines::certificate verify failed {grpc_status:14, created_time:"2025-02-19T10:39:05.228437472+01:00"}. 
Client context: IOError: Server never sent a data message. Detail: Internal

pyarrow/_flight.pyx:68: FlightUnavailableError
```
Investigating further. @karel-rehor: __Possible Workaround__

When querying it is possible to direct GRPC to the SSL roots file with the following environment variable.  

```
export GRPC_DEFAULT_SSL_ROOTS_FILE_PATH=/path/to/certs/cert.pem
```

This seems to be working in local tests.

Will look further into whether SSL arguments are working with both the write (HTTP/1,1 REST) and query (GRPC) apis. 

Further information: https://github.com/grpc/grpc/blob/master/include/grpc/grpc_security_constants.h#L54-L58.

Solution patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4ca15cf..ab0df23 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,10 @@
 
 ## 0.12.0 [unreleased] 
 
+### Bug Fixes
+
+1. [#121](https://github.com/InfluxCommunity/influxdb3-python/pull/121): Fix use of arguments `verify_ssl` and `ssl_ca_cert` in `QueryApi`.
+
 ## 0.11.0 [2025-02-27]
 
 ### Bug Fixes
diff --git a/Examples/basic_ssl_example.py b/Examples/basic_ssl_example.py
new file mode 100644
index 0000000..98096c9
--- /dev/null
+++ b/Examples/basic_ssl_example.py
@@ -0,0 +1,104 @@
+import os
+import time
+
+import pyarrow
+
+from config import Config
+from influxdb_client_3 import InfluxDBClient3
+
+bad_cert = """-----BEGIN CERTIFICATE-----
+MIIFDTCCAvWgAwIBAgIUYzpfisy9xLrhiZd+D9vOdzC3+iswDQYJKoZIhvcNAQEL
+BQAwFjEUMBIGA1UEAwwLdGVzdGhvc3QuaW8wHhcNMjUwMjI4MTM1NTMyWhcNMzUw
+MjI2MTM1NTMyWjAWMRQwEgYDVQQDDAt0ZXN0aG9zdC5pbzCCAiIwDQYJKoZIhvcN
+AQEBBQADggIPADCCAgoCggIBAN1lwqXYP8UMvjb56SpUEj2OpoEDRfLeWrEiHkOl
+xoymvJGaXZNEpDXo2TTdysCoYWEjz9IY6GlqSo2Yssf5BZkQwMOw7MdyRwCigzrh
+OAKbyCfsvEgfNFrXEdSDpaxW++5SToeErudYXc+sBfnI1NB4W3GBGqqIvx8fqaB3
+1EU9ql2sKKxI0oYIQD/If9rQEyLFKeWdD8iT6YST1Vugkvd34NPmaqV5+pjdSb4z
+a8olavwUoslqFUeILqIq+WZZbOlgCcJYKcBAmELRnsxGaABRtMwMZx+0D+oKo4Kl
+QQtOcER+RHkBHyYFghZIBnzudfbP9NadknOz3AilJbJolXfXJqeQhRD8Ob49kkhe
+OwjAppHnaZGWjYZMLIfnwwXBwkS7bSwF16Wot83cpL46Xvg6xcl12An4JaoF798Q
+cXyYrWCgvbqjVR7694gxqLGzk138AKTDSbER1h1rfqCqkk7soE0oWCs7jiCk2XvD
+49qVfHtd50KYJ4/yP1XL0PmLL0Hw1kvOxLVkFENc1zkoYXJRt2Ec6j9dajmGlsFn
+0bLLap6UIlIGQFuvcLf4bvsIi9FICy2jBjaIdM4UAWbReG+52+180HEleAwi5bAN
+HY61WVXc4X+N0E2y8HWc1QaRioU7R4XZ5HXKs7OTWkKFZUU2JDFHAKdiiAU78qLU
+7GApAgMBAAGjUzBRMB0GA1UdDgQWBBT2vPFo0mzh9ls4xJUiAgSK+B5LpTAfBgNV
+HSMEGDAWgBT2vPFo0mzh9ls4xJUiAgSK+B5LpTAPBgNVHRMBAf8EBTADAQH/MA0G
+CSqGSIb3DQEBCwUAA4ICAQC4TJNPx476qhiMi8anISv9lo9cnLju+qNhcz7wupBH
+3Go6bVQ7TCbSt2QpAyY64mdnRqHsXeGvZXCnabOpeKRDeAPBtRjc6yNKuXybqFtn
+W3PZEs/OYc659TUA+MoBzSXYStN9yiiYXyVFqVn+Rw6kM9tKh0GgAU7f5P+8IGuR
+gXJbCjkbdJO7JUiVGEEmkjUHyqFxMHaZ8V6uazs52qIFyt7OYQTeV9HdoW8D9vAt
+GfzYwzRDzbsZeIJqqDzLe7NOyxEyqZHCbtNpGcOyaLOl7ZBS52WsqaUZtL+9PjqD
+2TWj4WUFkOWQpTvWKHqM6//Buv4GjnTBShQKm+h+rxcGkdRMF6/sKwxPbr39P3RJ
+TMfJA3u5UuowT44VaA2jkQzqIbxH9+3EA+0qPbqPJchOSr0pHSncqvR9FYcr7ayN
+b6UDFnjeliyEqqksUO0arbvaO9FfB0kH8lU1NOKaQNO++Xj69GZMC6s721cNdad0
+qqcdtyXWeOBBchguYDrSUIgLnUTHEwwzOmcNQ36hO5eX282BJy3ZLT3JU6MJopjz
+vkbDDAxSrpZMcaoAWSrxgJAETeYiO4YbfORIzPkwdUkEIr6XY02Pi7MdkDGQ5hiB
+TavA8+oXRa4b9BR3bCWcg8S/t4uOTTLkeTcQbONPh5A5IRySLCU+CwqB+/+VlO8X
+Aw==
+-----END CERTIFICATE-----"""
+
+
+def write_cert(cert, file_name):
+    f = open(file_name, "w")
+    f.write(cert)
+    f.close()
+
+
+def remove_cert(file_name):
+    os.remove(file_name)
+
+
+def print_results(results: list):
+    print("%-6s%-6s%-6s%-24s" % ("id", "speed", "ticks", "time"))
+    for result in results:
+        print("%-6s%-6.2f%-6i%-24s" % (result['id'], result['speed'], result['ticks'], result['time']))
+
+
+def main() -> None:
+    print("Main")
+    temp_cert_file = "temp_cert.pem"
+    conf = Config()
+
+    write_and_query_with_explicit_sys_cert(conf)
+
+    write_cert(bad_cert, temp_cert_file)
+    query_with_verify_ssl_off(conf, temp_cert_file)
+    remove_cert(temp_cert_file)
+
+
+def write_and_query_with_explicit_sys_cert(conf):
+    print("\nwrite and query with typical linux system cert\n")
+    with InfluxDBClient3(token=conf.token,
+                         host=conf.host,
+                         org=conf.org,
+                         database=conf.database,
+                         ssl_ca_cert="/etc/ssl/certs/ca-certificates.crt",
+                         verify_ssl=True) as _client:
+        now = time.time_ns()
+        lp = f"escooter,id=zx80 speed=3.14,ticks=42i {now - (10 * 1_000_000_000)}"
+        _client.write(lp)
+
+        query = "SELECT * FROM \"escooter\" ORDER BY time DESC"
+        reader: pyarrow.Table = _client.query(query, mode="")
+        print_results(reader.to_pylist())
+
+
+def query_with_verify_ssl_off(conf, cert):
+    print("\nquerying with verify_ssl off\n")
+
+    # Note that the passed root cert above is bad
+    # Switch verify_ssl to True to throw SSL_ERROR_SSL
+    with InfluxDBClient3(token=conf.token,
+                         host=conf.host,
+                         org=conf.org,
+                         database=conf.database,
+                         ssl_ca_cert=cert,
+                         verify_ssl=False) as _client:
+
+        query = "SELECT * FROM \"escooter\"  ORDER BY time DESC"
+        reader: pyarrow.Table = _client.query(query, mode="")
+        print_results(reader.to_pylist())
+
+
+if __name__ == "__main__":
+    main()
diff --git a/influxdb_client_3/__init__.py b/influxdb_client_3/__init__.py
index 40ab8a4..6f736d2 100644
--- a/influxdb_client_3/__init__.py
+++ b/influxdb_client_3/__init__.py
@@ -2,7 +2,7 @@ import urllib.parse
 import pyarrow as pa
 import importlib.util
 
-from influxdb_client_3.query.query_api import QueryApi as _QueryApi
+from influxdb_client_3.query.query_api import QueryApi as _QueryApi, QueryApiOptionsBuilder
 from influxdb_client_3.read_file import UploadFile
 from influxdb_client_3.write_client import InfluxDBClient as _InfluxDBClient, WriteOptions, Point
 from influxdb_client_3.write_client.client.exceptions import InfluxDBError
@@ -165,9 +165,18 @@ class InfluxDBClient3:
             connection_string = f"grpc+tls://{hostname}:{port}"
         else:
             connection_string = f"grpc+tcp://{hostname}:{port}"
+
+        q_opts_builder = QueryApiOptionsBuilder()
+        kw_keys = kwargs.keys()
+        if kw_keys.__contains__('ssl_ca_cert'):
+            q_opts_builder.root_certs(kwargs.get('ssl_ca_cert', None))
+        if kw_keys.__contains__('verify_ssl'):
+            q_opts_builder.tls_verify(kwargs.get('verify_ssl', True))
+        if kw_keys.__contains__('proxy'):
+            q_opts_builder.proxy(kwargs.get('proxy', None))
         self._query_api = _QueryApi(connection_string=connection_string, token=token,
                                     flight_client_options=flight_client_options,
-                                    proxy=kwargs.get("proxy", None))
+                                    proxy=kwargs.get("proxy", None), options=q_opts_builder.build())
 
     def write(self, record=None, database=None, **kwargs):
         """
diff --git a/influxdb_client_3/query/query_api.py b/influxdb_client_3/query/query_api.py
index 7d03a95..ee0725f 100644
--- a/influxdb_client_3/query/query_api.py
+++ b/influxdb_client_3/query/query_api.py
@@ -7,11 +7,96 @@ from pyarrow.flight import FlightClient, Ticket, FlightCallOptions, FlightStream
 from influxdb_client_3.version import USER_AGENT
 
 
+class QueryApiOptions(object):
+    """
+    Structure for encapsulating options for the QueryApi
+
+    Attributes
+    ----------
+    tls_root_certs (bytes):  contents of an SSL root certificate or chain read as bytes
+    tls_verify (bool): whether to verify SSL certificates or not
+    proxy (str): URL to a proxy server
+    flight_client_options (dict): base set of flight client options passed to internal pyarrow.flight.FlightClient
+    """
+    tls_root_certs: bytes = None
+    tls_verify: bool = None
+    proxy: str = None
+    flight_client_options: dict = None
+
+    def __init__(self, root_certs_path, verify, proxy, flight_client_options):
+        """
+        Initialize a set of QueryApiOptions
+
+        :param root_certs_path: path to a certificate .pem file.
+        :param verify: whether to verify SSL certificates or not.
+        :param proxy: URL of a proxy server, if required.
+        :param flight_client_options: set of flight_client_options
+               to be passed to internal pyarrow.flight.FlightClient.
+        """
+        if root_certs_path:
+            self.tls_root_certs = self._read_certs(root_certs_path)
+        self.tls_verify = verify
+        self.proxy = proxy
+        self.flight_client_options = flight_client_options
+
+    def _read_certs(self, path):
+        with open(path, "rb") as certs_file:
+            return certs_file.read()
+
+
+class QueryApiOptionsBuilder(object):
+    """
+    Helper class to make adding QueryApiOptions more dynamic.
+
+    Example:
+
+    .. code-block:: python
+
+        options = QueryApiOptionsBuilder()\
+            .proxy("http://internal.tunnel.proxy:8080") \
+            .root_certs("/home/fred/.etc/ssl/alt_certs.pem") \
+            .tls_verify(True) \
+            .build()
+
+        client = QueryApi(connection, token, None, None, options)
+    """
+    _root_certs_path = None
+    _tls_verify = True
+    _proxy = None
+    _flight_client_options = None
+
+    def root_certs(self, path):
+        self._root_certs_path = path
+        return self
+
+    def tls_verify(self, verify):
+        self._tls_verify = verify
+        return self
+
+    def proxy(self, proxy):
+        self._proxy = proxy
+        return self
+
+    def flight_client_options(self, flight_client_options):
+        self._flight_client_options = flight_client_options
+        return self
+
+    def build(self):
+        """Build a QueryApiOptions object with previously set values"""
+        return QueryApiOptions(
+            root_certs_path=self._root_certs_path,
+            verify=self._tls_verify,
+            proxy=self._proxy,
+            flight_client_options=self._flight_client_options
+        )
+
+
 class QueryApi(object):
     """
     Implementation for '/api/v2/query' endpoint.
 
     Example:
+
         .. code-block:: python
 
             from influxdb_client import InfluxDBClient
@@ -26,7 +111,7 @@ class QueryApi(object):
                  connection_string,
                  token,
                  flight_client_options,
-                 proxy=None) -> None:
+                 proxy=None, options=None) -> None:
         """
         Initialize defaults.
 
@@ -37,6 +122,15 @@ class QueryApi(object):
         self._token = token
         self._flight_client_options = flight_client_options or {}
         self._proxy = proxy
+        if options:
+            if options.flight_client_options:
+                self._flight_client_options = options.flight_client_options
+            if options.tls_root_certs:
+                self._flight_client_options["tls_root_certs"] = options.tls_root_certs
+            if options.proxy:
+                self._proxy = options.proxy
+            if options.tls_verify is not None:
+                self._flight_client_options["disable_server_verification"] = not options.tls_verify
         self._flight_client_options["generic_options"] = [
             ("grpc.secondary_user_agent", USER_AGENT)
         ]


Test patch:
diff --git a/tests/test_influxdb_client_3_integration.py b/tests/test_influxdb_client_3_integration.py
index 1fb5747..3a51cc6 100644
--- a/tests/test_influxdb_client_3_integration.py
+++ b/tests/test_influxdb_client_3_integration.py
@@ -7,6 +7,7 @@ import unittest
 
 import pyarrow
 import pytest
+from pyarrow._flight import FlightError
 
 from influxdb_client_3 import InfluxDBClient3, InfluxDBError, write_client_options, WriteOptions
 
@@ -122,7 +123,6 @@ class TestInfluxDBClient3Integration(unittest.TestCase):
                              database=self.database,
                              token=self.token,
                              write_client_options=wc_opts) as w_client:
-
             for i in range(0, data_set_size):
                 w_client.write(f'{measurement},location=harfa val={i}i {now - (i * 1_000_000_000)}')
 
@@ -134,7 +134,6 @@ class TestInfluxDBClient3Integration(unittest.TestCase):
                              database=self.database,
                              token=self.token,
                              write_client_options=wc_opts) as r_client:
-
             query = f"SELECT * FROM \"{measurement}\" WHERE time >= now() - interval '3 minute'"
             reader: pyarrow.Table = r_client.query(query)
             list_results = reader.to_pylist()
@@ -165,7 +164,6 @@ class TestInfluxDBClient3Integration(unittest.TestCase):
                              token=self.token,
                              write_client_options=wc_opts,
                              debug=True) as w_client:
-
             for i in range(0, data_size):
                 w_client.write(f'{measurement},location=harfa val={i}i {now - (i * 1_000_000_000)}')
 
@@ -177,10 +175,77 @@ class TestInfluxDBClient3Integration(unittest.TestCase):
                              database=self.database,
                              token=self.token,
                              write_client_options=wc_opts) as r_client:
-
             logging.info("PREPARING QUERY")
 
             query = f"SELECT * FROM \"{measurement}\" WHERE time >= now() - interval '3 hours'"
             reader: pyarrow.Table = r_client.query(query, mode="")
             list_results = reader.to_pylist()
             self.assertEqual(data_size, len(list_results))
+
+    test_cert = """-----BEGIN CERTIFICATE-----
+MIIDUzCCAjugAwIBAgIUZB55ULutbc9gy6xLp1BkTQU7siowDQYJKoZIhvcNAQEL
+BQAwNjE0MDIGA1UEAwwraW5mbHV4ZGIzLWNsdXN0ZXJlZC1zd2FuLmJyYW1ib3Jh
+LnpvbmEtYi5ldTAeFw0yNTAyMTgxNTIyMTJaFw0yNjAyMTgxNTIyMTJaMDYxNDAy
+BgNVBAMMK2luZmx1eGRiMy1jbHVzdGVyZWQtc3dhbi5icmFtYm9yYS56b25hLWIu
+ZXUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCugeNrx0ZfyyP8H4e0
+zDSkKWnEXlVdjMi+ZSHhMbjvvqMkUQGLc/W59AEmMJ0Uiljka9d+F7jdu+oqDq9p
+4kGPhO3Oh7zIG0IGbncj8AwIXMGDNkNyL8s7C1+LoYotlSWDpWwkEKXUeAzdqS63
+CSJFqSJM2dss8qe9BpM6zHWJAKS1I30QT3SXQFEsF5m2F62dXCEEI6pO7jlik8/w
+aI47dTM20QyimVzea48SC/ELO/T4AjbmMeBGlTyCm39KOElOKRTJvB4KESEWaL3r
+EvPZbTh+72PUyrjxiDa56+RmtDPo7EN3uxuRVFX/HWiNnFk7orQLKZg5Kr8wE46R
+KmVvAgMBAAGjWTBXMDYGA1UdEQQvMC2CK2luZmx1eGRiMy1jbHVzdGVyZWQtc3dh
+bi5icmFtYm9yYS56b25hLWIuZXUwHQYDVR0OBBYEFH8et6JCzGD7Ny84aNRtq5Nj
+hvS/MA0GCSqGSIb3DQEBCwUAA4IBAQCuDwARea/Xr3+hmte9A0H+XB8wMPAJ64e8
+QA0qi0oy0gGdLfQHhsBWWmKSYLv7HygTNzb+7uFOTtq1UPLt18F+POPeLIj74QZV
+z89Pbo1TwUMzQ2pgbu0yRvraXIpqXGrPm5GWYp5mopX0rBWKdimbmEMkhZA0sVeH
+IdKIRUY6EyIVG+Z/nbuVqUlgnIWOMp0yg4RRC91zHy3Xvykf3Vai25H/jQpa6cbU
+//MIodzUIqT8Tja5cHXE51bLdUkO1rtNKdM7TUdjzkZ+bAOpqKl+c0FlYZI+F7Ly
++MdCcNgKFc8o8jGiyP6uyAJeg+tSICpFDw00LyuKmU62c7VKuyo7
+-----END CERTIFICATE-----"""
+
+    def create_test_cert(self, cert_file):
+        f = open(cert_file, "w")
+        f.write(self.test_cert)
+        f.close()
+
+    def remove_test_cert(self, cert_file):
+        os.remove(cert_file)
+
+    def test_queries_w_bad_cert(self):
+        cert_file = "test_cert.pem"
+        self.create_test_cert(cert_file)
+        with InfluxDBClient3(host=self.host,
+                             database=self.database,
+                             token=self.token,
+                             verify_ssl=True,
+                             ssl_ca_cert=cert_file,
+                             debug=True) as client:
+            try:
+                query = "SELECT table_name FROM information_schema.tables"
+                client.query(query, mode="")
+                assert False, "query should throw SSL_ERROR"
+            except FlightError as fe:
+                assert str(fe).__contains__('SSL_ERROR_SSL')
+            finally:
+                self.remove_test_cert(cert_file)
+
+    def test_verify_ssl_false(self):
+        cert_file = "test_cert.pem"
+        self.create_test_cert(cert_file)
+        measurement = f'test{random_hex(6)}'
+
+        with InfluxDBClient3(host=self.host,
+                             database=self.database,
+                             token=self.token,
+                             verify_ssl=False,
+                             ssl_ca_cert=cert_file,
+                             debug=True) as client:
+            try:
+                now = time.time_ns()
+                client.write(f'{measurement},location=harfa val=42i {now - 1_000_000_000}')
+                query = f"SELECT * FROM \"{measurement}\""
+                reader: pyarrow.Table = client.query(query, mode="")
+                list_results = reader.to_pylist()
+                assert len(list_results) > 0
+            finally:
+                self.remove_test_cert(cert_file)
diff --git a/tests/test_query.py b/tests/test_query.py
index 1c6ab43..d529ffb 100644
--- a/tests/test_query.py
+++ b/tests/test_query.py
@@ -1,5 +1,6 @@
 import unittest
 import struct
+import os
 from unittest.mock import Mock, ANY
 
 from pyarrow import (
@@ -18,6 +19,7 @@ from pyarrow.flight import (
 )
 
 from influxdb_client_3 import InfluxDBClient3
+from influxdb_client_3.query.query_api import QueryApiOptionsBuilder, QueryApi
 from influxdb_client_3.version import USER_AGENT
 
 
@@ -115,6 +117,37 @@ def test_influx_default_query_headers():
 
 class TestQuery(unittest.TestCase):
 
+    sample_cert = """-----BEGIN CERTIFICATE-----
+MIIFDTCCAvWgAwIBAgIUYzpfisy9xLrhiZd+D9vOdzC3+iswDQYJKoZIhvcNAQEL
+BQAwFjEUMBIGA1UEAwwLdGVzdGhvc3QuaW8wHhcNMjUwMjI4MTM1NTMyWhcNMzUw
+MjI2MTM1NTMyWjAWMRQwEgYDVQQDDAt0ZXN0aG9zdC5pbzCCAiIwDQYJKoZIhvcN
+AQEBBQADggIPADCCAgoCggIBAN1lwqXYP8UMvjb56SpUEj2OpoEDRfLeWrEiHkOl
+xoymvJGaXZNEpDXo2TTdysCoYWEjz9IY6GlqSo2Yssf5BZkQwMOw7MdyRwCigzrh
+OAKbyCfsvEgfNFrXEdSDpaxW++5SToeErudYXc+sBfnI1NB4W3GBGqqIvx8fqaB3
+1EU9ql2sKKxI0oYIQD/If9rQEyLFKeWdD8iT6YST1Vugkvd34NPmaqV5+pjdSb4z
+a8olavwUoslqFUeILqIq+WZZbOlgCcJYKcBAmELRnsxGaABRtMwMZx+0D+oKo4Kl
+QQtOcER+RHkBHyYFghZIBnzudfbP9NadknOz3AilJbJolXfXJqeQhRD8Ob49kkhe
+OwjAppHnaZGWjYZMLIfnwwXBwkS7bSwF16Wot83cpL46Xvg6xcl12An4JaoF798Q
+cXyYrWCgvbqjVR7694gxqLGzk138AKTDSbER1h1rfqCqkk7soE0oWCs7jiCk2XvD
+49qVfHtd50KYJ4/yP1XL0PmLL0Hw1kvOxLVkFENc1zkoYXJRt2Ec6j9dajmGlsFn
+0bLLap6UIlIGQFuvcLf4bvsIi9FICy2jBjaIdM4UAWbReG+52+180HEleAwi5bAN
+HY61WVXc4X+N0E2y8HWc1QaRioU7R4XZ5HXKs7OTWkKFZUU2JDFHAKdiiAU78qLU
+7GApAgMBAAGjUzBRMB0GA1UdDgQWBBT2vPFo0mzh9ls4xJUiAgSK+B5LpTAfBgNV
+HSMEGDAWgBT2vPFo0mzh9ls4xJUiAgSK+B5LpTAPBgNVHRMBAf8EBTADAQH/MA0G
+CSqGSIb3DQEBCwUAA4ICAQC4TJNPx476qhiMi8anISv9lo9cnLju+qNhcz7wupBH
+3Go6bVQ7TCbSt2QpAyY64mdnRqHsXeGvZXCnabOpeKRDeAPBtRjc6yNKuXybqFtn
+W3PZEs/OYc659TUA+MoBzSXYStN9yiiYXyVFqVn+Rw6kM9tKh0GgAU7f5P+8IGuR
+gXJbCjkbdJO7JUiVGEEmkjUHyqFxMHaZ8V6uazs52qIFyt7OYQTeV9HdoW8D9vAt
+GfzYwzRDzbsZeIJqqDzLe7NOyxEyqZHCbtNpGcOyaLOl7ZBS52WsqaUZtL+9PjqD
+2TWj4WUFkOWQpTvWKHqM6//Buv4GjnTBShQKm+h+rxcGkdRMF6/sKwxPbr39P3RJ
+TMfJA3u5UuowT44VaA2jkQzqIbxH9+3EA+0qPbqPJchOSr0pHSncqvR9FYcr7ayN
+b6UDFnjeliyEqqksUO0arbvaO9FfB0kH8lU1NOKaQNO++Xj69GZMC6s721cNdad0
+qqcdtyXWeOBBchguYDrSUIgLnUTHEwwzOmcNQ36hO5eX282BJy3ZLT3JU6MJopjz
+vkbDDAxSrpZMcaoAWSrxgJAETeYiO4YbfORIzPkwdUkEIr6XY02Pi7MdkDGQ5hiB
+TavA8+oXRa4b9BR3bCWcg8S/t4uOTTLkeTcQbONPh5A5IRySLCU+CwqB+/+VlO8X
+Aw==
+-----END CERTIFICATE-----"""
+
     def setUp(self):
         self.client = InfluxDBClient3(
             host="localhost",
@@ -164,3 +197,88 @@ class TestQuery(unittest.TestCase):
         assert client._query_api._proxy == test_proxy
         assert ('grpc.http_proxy', test_proxy) in\
                client._query_api._flight_client_options.get('generic_options')
+
+    def create_cert_file(self, file_name):
+        f = open(file_name, "w")
+        f.write(self.sample_cert)
+        f.close()
+
+    def remove_cert_file(self, file_name):
+        os.remove(file_name)
+
+    def test_query_api_options_builder(self):
+        proxy_name = "http://my.proxy.org"
+        cert_file = "cert_test.pem"
+        self.create_cert_file(cert_file)
+        builder = QueryApiOptionsBuilder()
+        options = builder.proxy(proxy_name)\
+            .root_certs(cert_file)\
+            .tls_verify(False)\
+            .build()
+
+        try:
+            assert options.tls_root_certs.decode('utf-8') == self.sample_cert
+            assert not options.tls_verify
+            assert options.proxy == proxy_name
+        finally:
+            self.remove_cert_file(cert_file)
+
+    def test_query_client_with_options(self):
+        connection = "grpc+tls://localhost:9999"
+        token = "my_token"
+        proxy_name = "http://my.proxy.org"
+        cert_file = "cert_test.pem"
+        private_key = 'our_key'
+        cert_chain = 'mTLS_explicit_chain'
+        self.create_cert_file(cert_file)
+        test_flight_client_options = {'private_key': private_key, 'cert_chain': cert_chain}
+        options = QueryApiOptionsBuilder()\
+            .proxy(proxy_name) \
+            .root_certs(cert_file) \
+            .tls_verify(False) \
+            .flight_client_options(test_flight_client_options) \
+            .build()
+
+        client = QueryApi(connection,
+                          token,
+                          None,
+                          None,
+                          options
+                          )
+
+        try:
+            assert client._token == token
+            assert client._flight_client_options['tls_root_certs'].decode('utf-8') == self.sample_cert
+            assert client._flight_client_options['private_key'] == private_key
+            assert client._flight_client_options['cert_chain'] == cert_chain
+            assert client._proxy == proxy_name
+            fc_opts = client._flight_client_options
+            assert dict(fc_opts['generic_options'])['grpc.secondary_user_agent'].startswith('influxdb3-python/')
+            assert dict(fc_opts['generic_options'])['grpc.http_proxy'] == proxy_name
+        finally:
+            self.remove_cert_file(cert_file)
+
+    def test_client_with_ssl_args(self):
+        cert_name = "cert-test.pem"
+        self.create_cert_file(cert_name)
+        proxy = "http://localhost:9999"
+        local_client = InfluxDBClient3(
+            host="localhost",
+            org="my_org",
+            database="my_db",
+            token="my_token",
+            proxy=proxy,
+            ssl_ca_cert=cert_name,
+            verify_ssl=False
+        )
+
+        try:
+            qapi = local_client._query_api
+            fc_opts = qapi._flight_client_options
+            assert qapi._proxy == proxy
+            assert fc_opts['tls_root_certs'].decode('utf-8') == self.sample_cert
+            assert fc_opts['disable_server_verification']
+            assert dict(fc_opts['generic_options'])['grpc.secondary_user_agent'].startswith('influxdb3-python/')
+            assert dict(fc_opts['generic_options'])['grpc.http_proxy'] == proxy
+        finally:
+            self.remove_cert_file(cert_name)