Skip to content

Commit 917f923

Browse files
committed
Merge branch 'oss-next' into ngdg_master_ft
2 parents 0e919a0 + 0d72845 commit 917f923

21 files changed

+285
-157
lines changed

CHANGELOG.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ Bug Fixes
4545
* re-raising the CQLEngineException will fail on Python 3 (PYTHON-1166)
4646
* asyncio message chunks can be processed discontinuously (PYTHON-1185)
4747
* Reconnect attempts persist after downed node removed from peers (PYTHON-1181)
48+
* Connection fails to validate ssl certificate hostname when SSLContext.check_hostname is set (PYTHON-1186)
4849

4950
Others
5051
------

README.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ Getting Help
7272
------------
7373
Your best options for getting help with the driver are the
7474
`mailing list <https://groups.google.com/a/lists.datastax.com/forum/#!forum/python-driver-user>`_
75-
and the ``#datastax-drivers`` channel in the `DataStax Academy Slack <https://academy.datastax.com/slack>`_.
75+
and the `DataStax Community <https://community.datastax.com>`_.
7676

7777
License
7878
-------

cassandra/connection.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -769,7 +769,14 @@ def factory(cls, endpoint, timeout, *args, **kwargs):
769769
return conn
770770

771771
def _wrap_socket_from_context(self):
772-
self._socket = self.ssl_context.wrap_socket(self._socket, **(self.ssl_options or {}))
772+
ssl_options = self.ssl_options or {}
773+
# PYTHON-1186: set the server_hostname only if the SSLContext has
774+
# check_hostname enabled and it is not already provided by the EndPoint ssl options
775+
if (self.ssl_context.check_hostname and
776+
'server_hostname' not in ssl_options):
777+
ssl_options = ssl_options.copy()
778+
ssl_options['server_hostname'] = self.endpoint.address
779+
self._socket = self.ssl_context.wrap_socket(self._socket, **ssl_options)
773780

774781
def _initiate_connection(self, sockaddr):
775782
self._socket.connect(sockaddr)

docs/getting_started.rst

Lines changed: 76 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,6 @@ by executing a ``USE <keyspace>`` query:
110110
# or you can do this instead
111111
session.execute('USE users')
112112
113-
114113
Executing Queries
115114
-----------------
116115
Now that we have a :class:`.Session` we can begin to execute queries. The simplest
@@ -153,13 +152,45 @@ examples are equivalent:
153152
If you prefer another result format, such as a ``dict`` per row, you
154153
can change the :attr:`~.Session.row_factory` attribute.
155154

156-
For queries that will be run repeatedly, you should use
157-
`Prepared statements <#prepared-statements>`_.
155+
As mentioned in our `Drivers Best Practices Guide <https://docs.datastax.com/en/devapp/doc/devapp/driversBestPractices.html#driversBestPractices__usePreparedStatements>`_,
156+
it is highly recommended to use `Prepared statements <#prepared-statement>`_ for your
157+
frequently run queries.
158+
159+
.. _prepared-statement:
160+
161+
Prepared Statements
162+
-------------------
163+
Prepared statements are queries that are parsed by Cassandra and then saved
164+
for later use. When the driver uses a prepared statement, it only needs to
165+
send the values of parameters to bind. This lowers network traffic
166+
and CPU utilization within Cassandra because Cassandra does not have to
167+
re-parse the query each time.
168+
169+
To prepare a query, use :meth:`.Session.prepare()`:
170+
171+
.. code-block:: python
172+
173+
user_lookup_stmt = session.prepare("SELECT * FROM users WHERE user_id=?")
174+
175+
users = []
176+
for user_id in user_ids_to_query:
177+
user = session.execute(user_lookup_stmt, [user_id])
178+
users.append(user)
179+
180+
:meth:`~.Session.prepare()` returns a :class:`~.PreparedStatement` instance
181+
which can be used in place of :class:`~.SimpleStatement` instances or literal
182+
string queries. It is automatically prepared against all nodes, and the driver
183+
handles re-preparing against new nodes and restarted nodes when necessary.
184+
185+
Note that the placeholders for prepared statements are ``?`` characters. This
186+
is different than for simple, non-prepared statements (although future versions
187+
of the driver may use the same placeholders for both).
158188

159189
Passing Parameters to CQL Queries
160190
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
161-
When executing non-prepared statements, the driver supports two forms of
162-
parameter place-holders: positional and named.
191+
Althought it is not recommended, you can also pass parameters to non-prepared
192+
statements. The driver supports two forms of parameter place-holders: positional
193+
and named.
163194

164195
Positional parameters are used with a ``%s`` placeholder. For example,
165196
when you execute:
@@ -376,34 +407,6 @@ in a :class:`~.SimpleStatement`:
376407
consistency_level=ConsistencyLevel.QUORUM)
377408
session.execute(query, ('John', 42))
378409
379-
Prepared Statements
380-
-------------------
381-
Prepared statements are queries that are parsed by Cassandra and then saved
382-
for later use. When the driver uses a prepared statement, it only needs to
383-
send the values of parameters to bind. This lowers network traffic
384-
and CPU utilization within Cassandra because Cassandra does not have to
385-
re-parse the query each time.
386-
387-
To prepare a query, use :meth:`.Session.prepare()`:
388-
389-
.. code-block:: python
390-
391-
user_lookup_stmt = session.prepare("SELECT * FROM users WHERE user_id=?")
392-
393-
users = []
394-
for user_id in user_ids_to_query:
395-
user = session.execute(user_lookup_stmt, [user_id])
396-
users.append(user)
397-
398-
:meth:`~.Session.prepare()` returns a :class:`~.PreparedStatement` instance
399-
which can be used in place of :class:`~.SimpleStatement` instances or literal
400-
string queries. It is automatically prepared against all nodes, and the driver
401-
handles re-preparing against new nodes and restarted nodes when necessary.
402-
403-
Note that the placeholders for prepared statements are ``?`` characters. This
404-
is different than for simple, non-prepared statements (although future versions
405-
of the driver may use the same placeholders for both).
406-
407410
Setting a Consistency Level with Prepared Statements
408411
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
409412
To specify a consistency level for prepared statements, you have two options.
@@ -434,3 +437,43 @@ level on that:
434437
user3_lookup = user_lookup_stmt.bind([user_id3])
435438
user3_lookup.consistency_level = ConsistencyLevel.ALL
436439
user3 = session.execute(user3_lookup)
440+
441+
Speculative Execution
442+
^^^^^^^^^^^^^^^^^^^^^
443+
444+
Speculative execution is a way to minimize latency by preemptively executing several
445+
instances of the same query against different nodes. For more details about this
446+
technique, see `Speculative Execution with DataStax Drivers <https://docs.datastax.com/en/devapp/doc/devapp/driversSpeculativeRetry.html>`_.
447+
448+
To enable speculative execution:
449+
450+
* Configure a :class:`~.policies.SpeculativeExecutionPolicy` with the ExecutionProfile
451+
* Mark your query as idempotent, which mean it can be applied multiple
452+
times without changing the result of the initial application.
453+
See `Query Idempotence <https://docs.datastax.com/en/devapp/doc/devapp/driversQueryIdempotence.html>`_ for more details.
454+
455+
456+
Example:
457+
458+
.. code-block:: python
459+
460+
from cassandra.cluster import Cluster, ExecutionProfile, EXEC_PROFILE_DEFAULT
461+
from cassandra.policies import ConstantSpeculativeExecutionPolicy
462+
from cassandra.query import SimpleStatement
463+
464+
# Configure the speculative execution policy
465+
ep = ExecutionProfile(
466+
speculative_execution_policy=ConstantSpeculativeExecutionPolicy(delay=.5, max_attempts=10)
467+
)
468+
cluster = Cluster(..., execution_profiles={EXEC_PROFILE_DEFAULT: ep})
469+
session = cluster.connect()
470+
471+
# Mark the query idempotent
472+
query = SimpleStatement(
473+
"UPDATE my_table SET list_col = [1] WHERE pk = 1",
474+
is_idempotent=True
475+
)
476+
477+
# Execute. A new query will be sent to the server every 0.5 second
478+
# until we receive a response, for a max number attempts of 10.
479+
session.execute(query)

docs/index.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ Visit the :doc:`FAQ section <faq>` in this documentation.
100100

101101
Please send questions to the `mailing list <https://groups.google.com/a/lists.datastax.com/forum/#!forum/python-driver-user>`_.
102102

103-
Alternatively, you can use the `#datastax-drivers` channel in the DataStax Acadamy Slack to ask questions in real time.
103+
Alternatively, you can use the `DataStax Community <https://community.datastax.com>`_.
104104

105105
Reporting Issues
106106
----------------

docs/security.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,14 @@ To enable SSL with version 3.17.0 and higher, you will need to set :attr:`.Clust
6969
to a dict of options. These will be passed as kwargs to ``ssl.SSLContext.wrap_socket()``
7070
when new sockets are created.
7171

72+
If you create your SSLContext using `ssl.create_default_context <https://docs.python.org/3/library/ssl.html#ssl.create_default_context>`_,
73+
be aware that SSLContext.check_hostname is set to True by default, so the hostname validation will be done
74+
by Python and not the driver. For this reason, we need to set the server_hostname at best effort, which is the
75+
resolved ip address. If this validation needs to be done against the FQDN, consider enabling it using the ssl_options
76+
as described in the following examples or implement your own :class:`~.connection.EndPoint` and
77+
:class:`~.connection.EndPointFactory`.
78+
79+
7280
The following examples assume you have generated your Cassandra certificate and
7381
keystore files with these intructions:
7482

docs/user_defined_types.rst

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,16 @@ new type through ``CREATE TYPE`` statements in CQL::
99

1010
Version 2.1 of the Python driver adds support for user-defined types.
1111

12-
Registering a Class to Map to a UDT
13-
-----------------------------------
12+
Registering a UDT
13+
-----------------
1414
You can tell the Python driver to return columns of a specific UDT as
15-
instances of a class by registering them with your :class:`~.Cluster`
15+
instances of a class or a dict by registering them with your :class:`~.Cluster`
1616
instance through :meth:`.Cluster.register_user_type`:
1717

18+
19+
Map a Class to a UDT
20+
++++++++++++++++++++
21+
1822
.. code-block:: python
1923
2024
cluster = Cluster(protocol_version=3)
@@ -39,7 +43,29 @@ instance through :meth:`.Cluster.register_user_type`:
3943
# results will include Address instances
4044
results = session.execute("SELECT * FROM users")
4145
row = results[0]
42-
print row.id, row.location.street, row.location.zipcode
46+
print(row.id, row.location.street, row.location.zipcode)
47+
48+
Map a dict to a UDT
49+
+++++++++++++++++++
50+
51+
.. code-block:: python
52+
53+
cluster = Cluster(protocol_version=3)
54+
session = cluster.connect()
55+
session.set_keyspace('mykeyspace')
56+
session.execute("CREATE TYPE address (street text, zipcode int)")
57+
session.execute("CREATE TABLE users (id int PRIMARY KEY, location frozen<address>)")
58+
59+
cluster.register_user_type('mykeyspace', 'address', dict)
60+
61+
# insert a row using a prepared statement and a tuple
62+
insert_statement = session.prepare("INSERT INTO mykeyspace.users (id, location) VALUES (?, ?)")
63+
session.execute(insert_statement, [0, ("123 Main St.", 78723)])
64+
65+
# results will include dict instances
66+
results = session.execute("SELECT * FROM users")
67+
row = results[0]
68+
print(row.id, row.location['street'], row.location['zipcode'])
4369
4470
Using UDTs Without Registering Them
4571
-----------------------------------
@@ -79,7 +105,7 @@ for the UDT:
79105
results = session.execute("SELECT * FROM users")
80106
first_row = results[0]
81107
address = first_row.location
82-
print address # prints "Address(street='123 Main St.', zipcode=78723)"
108+
print(address) # prints "Address(street='123 Main St.', zipcode=78723)"
83109
street = address.street
84110
zipcode = address.street
85111

tests/integration/long/ssl/.keystore

-2.23 KB
Binary file not shown.
4.22 KB
Binary file not shown.

tests/integration/long/ssl/cassandra.pem

Lines changed: 0 additions & 19 deletions
This file was deleted.

0 commit comments

Comments
 (0)