crate 2.2.0__tar.gz → 2.2.1b2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. {crate-2.2.0 → crate-2.2.1b2}/PKG-INFO +1 -1
  2. {crate-2.2.0 → crate-2.2.1b2}/tests/client/test_cursor.py +132 -0
  3. {crate-2.2.0 → crate-2.2.1b2}/.gitignore +0 -0
  4. {crate-2.2.0 → crate-2.2.1b2}/CHANGES.rst +0 -0
  5. {crate-2.2.0 → crate-2.2.1b2}/CONTRIBUTING.rst +0 -0
  6. {crate-2.2.0 → crate-2.2.1b2}/DEVELOP.rst +0 -0
  7. {crate-2.2.0 → crate-2.2.1b2}/LICENSE +0 -0
  8. {crate-2.2.0 → crate-2.2.1b2}/NOTICE +0 -0
  9. {crate-2.2.0 → crate-2.2.1b2}/README.rst +0 -0
  10. {crate-2.2.0 → crate-2.2.1b2}/docs/.gitignore +0 -0
  11. {crate-2.2.0 → crate-2.2.1b2}/docs/Makefile +0 -0
  12. {crate-2.2.0 → crate-2.2.1b2}/docs/_extra/robots.txt +0 -0
  13. {crate-2.2.0 → crate-2.2.1b2}/docs/blobs.rst +0 -0
  14. {crate-2.2.0 → crate-2.2.1b2}/docs/build.json +0 -0
  15. {crate-2.2.0 → crate-2.2.1b2}/docs/by-example/blob.rst +0 -0
  16. {crate-2.2.0 → crate-2.2.1b2}/docs/by-example/client.rst +0 -0
  17. {crate-2.2.0 → crate-2.2.1b2}/docs/by-example/connection.rst +0 -0
  18. {crate-2.2.0 → crate-2.2.1b2}/docs/by-example/cursor.rst +0 -0
  19. {crate-2.2.0 → crate-2.2.1b2}/docs/by-example/http.rst +0 -0
  20. {crate-2.2.0 → crate-2.2.1b2}/docs/by-example/https.rst +0 -0
  21. {crate-2.2.0 → crate-2.2.1b2}/docs/by-example/index.rst +0 -0
  22. {crate-2.2.0 → crate-2.2.1b2}/docs/conf.py +0 -0
  23. {crate-2.2.0 → crate-2.2.1b2}/docs/connect.rst +0 -0
  24. {crate-2.2.0 → crate-2.2.1b2}/docs/data-types.rst +0 -0
  25. {crate-2.2.0 → crate-2.2.1b2}/docs/docutils.conf +0 -0
  26. {crate-2.2.0 → crate-2.2.1b2}/docs/getting-started.rst +0 -0
  27. {crate-2.2.0 → crate-2.2.1b2}/docs/index-all.rst +0 -0
  28. {crate-2.2.0 → crate-2.2.1b2}/docs/index.rst +0 -0
  29. {crate-2.2.0 → crate-2.2.1b2}/docs/other-options.rst +0 -0
  30. {crate-2.2.0 → crate-2.2.1b2}/docs/query.rst +0 -0
  31. {crate-2.2.0 → crate-2.2.1b2}/docs/requirements.txt +0 -0
  32. {crate-2.2.0 → crate-2.2.1b2}/examples/README.rst +0 -0
  33. {crate-2.2.0 → crate-2.2.1b2}/pyproject.toml +0 -0
  34. {crate-2.2.0 → crate-2.2.1b2}/tests/__init__.py +0 -0
  35. {crate-2.2.0 → crate-2.2.1b2}/tests/assets/import/test_a.json +0 -0
  36. {crate-2.2.0 → crate-2.2.1b2}/tests/assets/mappings/locations.sql +0 -0
  37. {crate-2.2.0 → crate-2.2.1b2}/tests/assets/pki/cacert_invalid.pem +0 -0
  38. {crate-2.2.0 → crate-2.2.1b2}/tests/assets/pki/cacert_valid.pem +0 -0
  39. {crate-2.2.0 → crate-2.2.1b2}/tests/assets/pki/client_invalid.pem +0 -0
  40. {crate-2.2.0 → crate-2.2.1b2}/tests/assets/pki/client_valid.pem +0 -0
  41. {crate-2.2.0 → crate-2.2.1b2}/tests/assets/pki/readme.rst +0 -0
  42. {crate-2.2.0 → crate-2.2.1b2}/tests/assets/pki/server_valid.pem +0 -0
  43. {crate-2.2.0 → crate-2.2.1b2}/tests/assets/settings/test_a.json +0 -0
  44. {crate-2.2.0 → crate-2.2.1b2}/tests/client/__init__.py +0 -0
  45. {crate-2.2.0 → crate-2.2.1b2}/tests/client/settings.py +0 -0
  46. {crate-2.2.0 → crate-2.2.1b2}/tests/client/test_blob.py +0 -0
  47. {crate-2.2.0 → crate-2.2.1b2}/tests/client/test_connection.py +0 -0
  48. {crate-2.2.0 → crate-2.2.1b2}/tests/client/test_exceptions.py +0 -0
  49. {crate-2.2.0 → crate-2.2.1b2}/tests/client/test_http.py +0 -0
  50. {crate-2.2.0 → crate-2.2.1b2}/tests/client/test_serialization.py +0 -0
  51. {crate-2.2.0 → crate-2.2.1b2}/tests/client/test_utils.py +0 -0
  52. {crate-2.2.0 → crate-2.2.1b2}/tests/conftest.py +0 -0
  53. {crate-2.2.0 → crate-2.2.1b2}/tests/test_docs.py +0 -0
  54. {crate-2.2.0 → crate-2.2.1b2}/tests/testing/__init__.py +0 -0
  55. {crate-2.2.0 → crate-2.2.1b2}/tests/testing/test_layer.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: crate
3
- Version: 2.2.0
3
+ Version: 2.2.1b2
4
4
  Summary: CrateDB Python Client
5
5
  Author-email: "Crate.io" <office@crate.io>
6
6
  License-Expression: Apache-2.0
@@ -125,6 +125,92 @@ def test_cursor_executemany(mocked_connection):
125
125
  assert response["results"] == result
126
126
 
127
127
 
128
+ def test_executemany_with_named_params(mocked_connection):
129
+ """
130
+ Verify that executemany() translates pyformat %(name)s placeholders to
131
+ positional $N markers and converts each dict row to a positional list.
132
+
133
+ """
134
+ response = {
135
+ "col_types": [],
136
+ "cols": [],
137
+ "duration": 123,
138
+ "results": [{"rowcount": 1}, {"rowcount": 1}],
139
+ }
140
+ with mock.patch.object(
141
+ mocked_connection.client, "sql", return_value=response
142
+ ):
143
+ cursor = mocked_connection.cursor()
144
+ cursor.executemany(
145
+ "INSERT INTO characters (name, age) VALUES (%(name)s, %(age)s)",
146
+ [
147
+ {"name": "Arthur", "age": 42},
148
+ {"name": "Bill", "age": 35},
149
+ ],
150
+ )
151
+ sql, _params, bulk_args = mocked_connection.client.sql.call_args[0]
152
+ assert sql == "INSERT INTO characters (name, age) VALUES ($1, $2)"
153
+ assert bulk_args == [["Arthur", 42], ["Bill", 35]]
154
+
155
+
156
+ def test_executemany_with_named_params_missing_key(mocked_connection):
157
+ """
158
+ Verify that executemany() raises ProgrammingError when a row is missing a
159
+ key that appears as a placeholder in the SQL.
160
+ """
161
+ cursor = mocked_connection.cursor()
162
+ with pytest.raises(
163
+ ProgrammingError, match="Named parameter 'age' not found"
164
+ ):
165
+ cursor.executemany(
166
+ "INSERT INTO characters (name, age) VALUES (%(name)s, %(age)s)",
167
+ [
168
+ {"name": "Arthur", "age": 42},
169
+ {"name": "Bill"}, # missing 'age'
170
+ ],
171
+ )
172
+ mocked_connection.client.sql.assert_not_called()
173
+
174
+
175
+ def test_executemany_with_named_params_repeated(mocked_connection):
176
+ """
177
+ Verify that a placeholder name used multiple times in the SQL maps to the
178
+ same $N position in every occurrence, and the value appears only once in
179
+ each row's positional list.
180
+ """
181
+ response = {
182
+ "col_types": [],
183
+ "cols": [],
184
+ "duration": 123,
185
+ "results": [{"rowcount": 1}, {"rowcount": 1}],
186
+ }
187
+ with mock.patch.object(
188
+ mocked_connection.client, "sql", return_value=response
189
+ ):
190
+ cursor = mocked_connection.cursor()
191
+ cursor.executemany(
192
+ "INSERT INTO t (a, b) VALUES (%(x)s, %(x)s)",
193
+ [{"x": 1}, {"x": 2}],
194
+ )
195
+ sql, _params, bulk_args = mocked_connection.client.sql.call_args[0]
196
+ assert sql == "INSERT INTO t (a, b) VALUES ($1, $1)"
197
+ assert bulk_args == [[1], [2]]
198
+
199
+
200
+ def test_executemany_with_mixed_param_types(mocked_connection):
201
+ """
202
+ Verify that executemany() raises a clear ProgrammingError when the
203
+ parameter sequence mixes dicts and non-dicts while the SQL uses pyformat.
204
+ """
205
+ cursor = mocked_connection.cursor()
206
+ with pytest.raises(ProgrammingError, match="requires all parameter rows"):
207
+ cursor.executemany(
208
+ "INSERT INTO characters (name) VALUES (%(name)s)",
209
+ [{"name": "Arthur"}, ["Trillian"]], # second row is a list
210
+ )
211
+ mocked_connection.client.sql.assert_not_called()
212
+
213
+
128
214
  def test_create_with_timezone_as_datetime_object(mocked_connection):
129
215
  """
130
216
  The cursor can return timezone-aware `datetime` objects when requested.
@@ -243,6 +329,34 @@ def test_execute_with_bulk_args(mocked_connection):
243
329
  mocked_connection.client.sql.assert_called_once_with(statement, None, [[1]])
244
330
 
245
331
 
332
+ def test_execute_with_pyformat_sql_and_bulk_parameters(mocked_connection):
333
+ """
334
+ cursor.execute() converts %(name)s SQL to $N when bulk_parameters is
335
+ provided. Rows are already positional; only the SQL needs conversion.
336
+ """
337
+ cursor = mocked_connection.cursor()
338
+ sql = "INSERT INTO t (id, val) VALUES (%(id)s, %(val)s)"
339
+ bulk = [[1, "hello"], [2, "world"]]
340
+ cursor.execute(sql, bulk_parameters=bulk)
341
+ mocked_connection.client.sql.assert_called_once_with(
342
+ "INSERT INTO t (id, val) VALUES ($1, $2)", None, bulk
343
+ )
344
+
345
+
346
+ def test_execute_with_pyformat_sql_and_bulk_parameters_no_placeholders(
347
+ mocked_connection,
348
+ ):
349
+ """
350
+ SQL without %(name)s placeholders is passed through unchanged
351
+ even when bulk_parameters is provided.
352
+ """
353
+ cursor = mocked_connection.cursor()
354
+ sql = "INSERT INTO t (id, val) VALUES (?, ?)"
355
+ bulk = [[1, "hello"], [2, "world"]]
356
+ cursor.execute(sql, bulk_parameters=bulk)
357
+ mocked_connection.client.sql.assert_called_once_with(sql, None, bulk)
358
+
359
+
246
360
  def test_execute_custom_converter(mocked_connection):
247
361
  """
248
362
  Verify that a custom converter is correctly applied when passed to a cursor.
@@ -565,6 +679,24 @@ def test_execute_with_named_params_missing(mocked_connection):
565
679
  mocked_connection.client.sql.assert_not_called()
566
680
 
567
681
 
682
+ def test_execute_with_named_params_non_identifier_keys(mocked_connection):
683
+ """
684
+ Verify that %(name)s placeholders whose name contains characters outside
685
+ [a-zA-Z0-9_] are still converted to positional $N markers.
686
+
687
+ """
688
+ cursor = mocked_connection.cursor()
689
+
690
+ cursor.execute(
691
+ "UPDATE characters SET data['x'] = %(data['x'])s WHERE name = %(name)s",
692
+ {"data['x']": 42, "name": "Berlin"},
693
+ )
694
+ sql, args, _ = mocked_connection.client.sql.call_args[0]
695
+ assert "%" not in sql
696
+ assert sql == "UPDATE characters SET data['x'] = $1 WHERE name = $2"
697
+ assert args == [42, "Berlin"]
698
+
699
+
568
700
  def test_cursor_close(mocked_connection):
569
701
  """
570
702
  Verify that a cursor is not closed if not specifically closed.
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes