confluent-sql 0.3.0__tar.gz → 0.3.1__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 (48) hide show
  1. {confluent_sql-0.3.0 → confluent_sql-0.3.1}/.semaphore/publish_to_codeartifact.yml +1 -1
  2. {confluent_sql-0.3.0 → confluent_sql-0.3.1}/.semaphore/publish_to_pypi.yml +1 -1
  3. {confluent_sql-0.3.0 → confluent_sql-0.3.1}/.semaphore/semaphore.yml +2 -2
  4. confluent_sql-0.3.1/CHANGELOG.md +46 -0
  5. {confluent_sql-0.3.0 → confluent_sql-0.3.1}/PKG-INFO +1 -1
  6. {confluent_sql-0.3.0 → confluent_sql-0.3.1}/pyproject.toml +1 -1
  7. {confluent_sql-0.3.0 → confluent_sql-0.3.1}/src/confluent_sql/connection.py +45 -6
  8. {confluent_sql-0.3.0 → confluent_sql-0.3.1}/tests/conftest.py +2 -0
  9. {confluent_sql-0.3.0 → confluent_sql-0.3.1}/tests/unit/test_connection_unit.py +90 -1
  10. confluent_sql-0.3.1/uv.lock +439 -0
  11. confluent_sql-0.3.0/CHANGELOG.md +0 -37
  12. confluent_sql-0.3.0/uv.lock +0 -439
  13. {confluent_sql-0.3.0 → confluent_sql-0.3.1}/.github/CODEOWNERS +0 -0
  14. {confluent_sql-0.3.0 → confluent_sql-0.3.1}/.gitignore +0 -0
  15. {confluent_sql-0.3.0 → confluent_sql-0.3.1}/ARCHITECTURE.md +0 -0
  16. {confluent_sql-0.3.0 → confluent_sql-0.3.1}/DBAPI_EXTENSIONS.md +0 -0
  17. {confluent_sql-0.3.0 → confluent_sql-0.3.1}/LICENSE.txt +0 -0
  18. {confluent_sql-0.3.0 → confluent_sql-0.3.1}/Makefile +0 -0
  19. {confluent_sql-0.3.0 → confluent_sql-0.3.1}/README.md +0 -0
  20. {confluent_sql-0.3.0 → confluent_sql-0.3.1}/STREAMING.md +0 -0
  21. {confluent_sql-0.3.0 → confluent_sql-0.3.1}/TYPES.md +0 -0
  22. {confluent_sql-0.3.0 → confluent_sql-0.3.1}/examples/errors.py +0 -0
  23. {confluent_sql-0.3.0 → confluent_sql-0.3.1}/examples/simple_append_only_streaming_query_example.py +0 -0
  24. {confluent_sql-0.3.0 → confluent_sql-0.3.1}/examples/snapshot_mode_tuple_cursor_simple_example.py +0 -0
  25. {confluent_sql-0.3.0 → confluent_sql-0.3.1}/service.yml +0 -0
  26. {confluent_sql-0.3.0 → confluent_sql-0.3.1}/src/confluent_sql/__init__.py +0 -0
  27. {confluent_sql-0.3.0 → confluent_sql-0.3.1}/src/confluent_sql/__version__.py +0 -0
  28. {confluent_sql-0.3.0 → confluent_sql-0.3.1}/src/confluent_sql/changelog_compressor.py +0 -0
  29. {confluent_sql-0.3.0 → confluent_sql-0.3.1}/src/confluent_sql/cursor.py +0 -0
  30. {confluent_sql-0.3.0 → confluent_sql-0.3.1}/src/confluent_sql/exceptions.py +0 -0
  31. {confluent_sql-0.3.0 → confluent_sql-0.3.1}/src/confluent_sql/execution_mode.py +0 -0
  32. {confluent_sql-0.3.0 → confluent_sql-0.3.1}/src/confluent_sql/result_readers.py +0 -0
  33. {confluent_sql-0.3.0 → confluent_sql-0.3.1}/src/confluent_sql/statement.py +0 -0
  34. {confluent_sql-0.3.0 → confluent_sql-0.3.1}/src/confluent_sql/types.py +0 -0
  35. {confluent_sql-0.3.0 → confluent_sql-0.3.1}/tests/__init__.py +0 -0
  36. {confluent_sql-0.3.0 → confluent_sql-0.3.1}/tests/integration/conftest.py +0 -0
  37. {confluent_sql-0.3.0 → confluent_sql-0.3.1}/tests/integration/test_connection.py +0 -0
  38. {confluent_sql-0.3.0 → confluent_sql-0.3.1}/tests/integration/test_cursor.py +0 -0
  39. {confluent_sql-0.3.0 → confluent_sql-0.3.1}/tests/integration/test_fetch.py +0 -0
  40. {confluent_sql-0.3.0 → confluent_sql-0.3.1}/tests/unit/conftest.py +0 -0
  41. {confluent_sql-0.3.0 → confluent_sql-0.3.1}/tests/unit/test_changelog_compressor_unit.py +0 -0
  42. {confluent_sql-0.3.0 → confluent_sql-0.3.1}/tests/unit/test_changelog_unit.py +0 -0
  43. {confluent_sql-0.3.0 → confluent_sql-0.3.1}/tests/unit/test_connection_unit_properties.py +0 -0
  44. {confluent_sql-0.3.0 → confluent_sql-0.3.1}/tests/unit/test_cursor_unit.py +0 -0
  45. {confluent_sql-0.3.0 → confluent_sql-0.3.1}/tests/unit/test_execution_mode_unit.py +0 -0
  46. {confluent_sql-0.3.0 → confluent_sql-0.3.1}/tests/unit/test_result_readers_unit.py +0 -0
  47. {confluent_sql-0.3.0 → confluent_sql-0.3.1}/tests/unit/test_statement_unit.py +0 -0
  48. {confluent_sql-0.3.0 → confluent_sql-0.3.1}/tests/unit/test_types_unit.py +0 -0
@@ -10,7 +10,7 @@ global_job_config:
10
10
  - checkout
11
11
  - . vault-setup
12
12
  - curl -LsSf https://astral.sh/uv/0.10.11/install.sh | sh
13
- - uv sync
13
+ - uv sync --frozen
14
14
 
15
15
  blocks:
16
16
  - name: Publish `confluent_sql` to codeartifact
@@ -10,7 +10,7 @@ global_job_config:
10
10
  - checkout
11
11
  - . vault-setup
12
12
  - curl -LsSf https://astral.sh/uv/0.10.11/install.sh | sh
13
- - uv sync
13
+ - uv sync --frozen
14
14
 
15
15
  blocks:
16
16
  - name: Publish `confluent_sql` to PyPi
@@ -21,7 +21,7 @@ global_job_config:
21
21
  - checkout
22
22
  - . vault-setup
23
23
  - curl -LsSf https://astral.sh/uv/0.10.11/install.sh | sh
24
- - uv sync
24
+ - uv sync --frozen
25
25
  - export VERSION=$(grep '^version' pyproject.toml | cut -d'"' -f2)
26
26
 
27
27
  blocks:
@@ -31,7 +31,7 @@ blocks:
31
31
  jobs:
32
32
  - name: "Lint, Type Check, and Test"
33
33
  commands:
34
- - uv run ruff check && uv run pyright && uv run pytest -m unit --junitxml=test-results.xml
34
+ - uv run --frozen ruff check && uv run --frozen pyright && uv run --frozen pytest -m unit --junitxml=test-results.xml
35
35
  epilogue:
36
36
  always:
37
37
  commands:
@@ -0,0 +1,46 @@
1
+ # Change Log
2
+
3
+ All notable changes to this dbapi driver will be documented in this file.
4
+
5
+ ## 0.3.1, 2026-05-21
6
+
7
+ ### Added
8
+
9
+ - New `http_timeout_secs` parameter for `connect()` to let the caller control how long to wait in HTTP requests.
10
+
11
+ ## 0.3.0, 2026-04-09
12
+
13
+ ### Changed - Breaking
14
+
15
+ - `connect()` / `Connection.__init__()`: Renamed `environment` parameter to `environment_id` to clarify that an environment ID (_not_ name) is expected. The internal attribute `Connection.environment` has also been renamed to `Connection.environment_id`. Update all calls from `connect(environment="env-123")` to `connect(environment_id="env-123")`. (#92)
16
+ - `Cursor.execute()` and peers: Respelled and re-typed the `statement_label: str | None` parameter to be `statement_labels: list[str] | None` to allow multiple labels to be applied to a statement, including `HIDDEN_LABEL`.
17
+
18
+ ### Added
19
+
20
+ - New `Connection.get_statement(statement)` method to retrieve statement metadata by name or refresh a Statement object with the latest server state. Accepts either a statement name (string) or a Statement object. Returns a Statement object with current phase, schema, and execution traits. (#86)
21
+ - New `StatementNotFoundError` exception, a subclass of `OperationalError`, raised by `Connection.get_statement(statement)` when attempting to retrieve a statement that does not exist. Provides programmatic access to the statement name via the `statement_name` attribute.
22
+ - New constant `confluent_sql.HIDDEN_LABEL` used for driving `Cursor.execute()` to indicate that the statement should be hidden in default listings in Confluent Cloud UIs. This feature is intended to be used for minor queries, such as when investigating `INFORMATION_SCHEMA`.
23
+ - Added documentation regarding use of `connect(endpoint=)` parameter to make use of private networking endpoints (README.md, docstrings).
24
+
25
+ ## 0.2.0, 2026-03-26
26
+
27
+ ### Changed
28
+
29
+ - Respelled the `connect()` parameter `dbname` to `database`. The old spelling `dbname` is deprecated and will be removed in after one release cycle.
30
+ - Class `SqlNone` now gracefully strips trailing `NOT NULL` constraints from type names (case-insensitively), so that `str(SqlNone("DATE NOT NULL"))` returns valid FlinkSQL `"cast (null as DATE)"`.
31
+ - `connect()` is now keyword-only callable.
32
+ - The `host` parameter for `Connection.__init__()` has been renamed to `endpoint`.
33
+ - Clarified and improved documentation around Flink region API key use.
34
+
35
+ ### Added
36
+
37
+ - New optional keyword parameter `properties: PropertiesDict | None` on `Cursor.execute()` and related methods to allow callers to provide [statement execution properties](https://docs.confluent.io/cloud/current/flink/reference/statements/set.html#table-options). Note: connection or cursor-level properties for default catalog, database, and execution mode cannot be overridden by this parameter.
38
+ - New optional `endpoint` parameter on `connect()` and `Connection.__init__` to allow users to specify a custom Confluent Cloud API base endpoint (e.g., for private networking, staging, etc.). Mutually exclusive with (`cloud_provider`, `cloud_region`) -- either `endpoint` or (`cloud_provider`, `cloud_region`) must be provided. This replaces the `host` parameter in `Connection.__init__()`. (#66)
39
+
40
+ ### Removed
41
+
42
+ - The unused control-plane `api_key` and `api_secret` `connect()` parameters have been removed. The Flink Region API key params `flink_api_key` and `flink_api_secret` remain.
43
+
44
+ ## 0.1.x
45
+
46
+ Early access release of the driver.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: confluent-sql
3
- Version: 0.3.0
3
+ Version: 0.3.1
4
4
  Summary: DB-API v2 compliant driver for Confluent Cloud Flink SQL
5
5
  Project-URL: Repository, https://github.com/confluentinc/confluent-sql
6
6
  Project-URL: Documentation, https://github.com/confluentinc/confluent-sql?tab=readme-ov-file#confluent-sql
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "confluent-sql"
7
- version = "0.3.0"
7
+ version = "0.3.1"
8
8
  description = "DB-API v2 compliant driver for Confluent Cloud Flink SQL"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -48,6 +48,7 @@ def connect( # noqa: PLR0913
48
48
  dbname: str | None = None, # deprecated, use database parameter
49
49
  result_page_fetch_pause_millis: int = 100,
50
50
  http_user_agent: str | None = None,
51
+ http_timeout_secs: float | None = None,
51
52
  ) -> Connection:
52
53
  """
53
54
  Create a connection to a Confluent SQL service.
@@ -87,6 +88,9 @@ def connect( # noqa: PLR0913
87
88
  between 1-100 characters. Defaults to
88
89
  "Confluent-SQL-Dbapi/v<version> (https://confluent.io; support@confluent.io)"
89
90
  where version is from __version__.py
91
+ http_timeout_secs: Timeout in seconds for HTTP requests made by the underlying httpx
92
+ client. Must be a positive number. If None (default), the httpx default
93
+ of 5 seconds applies to connect, read, write, and pool operations.
90
94
 
91
95
  Returns:
92
96
  A Connection object representing the database connection
@@ -144,6 +148,7 @@ def connect( # noqa: PLR0913
144
148
  database=database or dbname, # dbname is deprecated.
145
149
  statement_results_page_fetch_pause_millis=result_page_fetch_pause_millis,
146
150
  http_user_agent=http_user_agent,
151
+ http_timeout_secs=http_timeout_secs,
147
152
  )
148
153
 
149
154
 
@@ -181,6 +186,7 @@ class Connection:
181
186
  _database: str | None
182
187
  _client: httpx.Client
183
188
  _http_user_agent: str
189
+ _http_timeout_secs: float | None
184
190
 
185
191
  _row_type_registry: RowTypeRegistry
186
192
  """Registry for user-defined row types, see register_row_type()."""
@@ -202,6 +208,7 @@ class Connection:
202
208
  database: str | None = None,
203
209
  statement_results_page_fetch_pause_millis: int = 100,
204
210
  http_user_agent: str | None = None,
211
+ http_timeout_secs: float | None = None,
205
212
  ):
206
213
  """
207
214
  Initialize a new connection to a Confluent SQL service.
@@ -231,6 +238,9 @@ class Connection:
231
238
  http_user_agent: User-Agent header for HTTP requests. String, 1-100 chars.
232
239
  Defaults to the value of DEFAULT_USER_AGENT, which includes the
233
240
  driver name/version, documentation URL, and support email.
241
+ http_timeout_secs: Timeout in seconds applied to the underlying httpx client.
242
+ Must be a positive number. If None (default), the httpx default
243
+ of 5 seconds applies.
234
244
  """
235
245
  self.environment_id = environment_id
236
246
  self.compute_pool_id = compute_pool_id
@@ -254,6 +264,22 @@ class Connection:
254
264
  http_user_agent if http_user_agent is not None else self.DEFAULT_USER_AGENT
255
265
  )
256
266
 
267
+ if http_timeout_secs is not None:
268
+ # Reject bool explicitly: bool is a subclass of int in Python, but a True/False
269
+ # value here is almost certainly a programming error rather than a valid timeout.
270
+ if isinstance(http_timeout_secs, bool) or not isinstance(
271
+ http_timeout_secs, (int, float)
272
+ ):
273
+ raise InterfaceError(
274
+ f"http_timeout_secs must be a number, "
275
+ f"got {type(http_timeout_secs).__name__}"
276
+ )
277
+ if http_timeout_secs <= 0:
278
+ raise InterfaceError(
279
+ f"http_timeout_secs must be positive, got {http_timeout_secs}"
280
+ )
281
+ self._http_timeout_secs = http_timeout_secs
282
+
257
283
  if not endpoint and not (cloud_provider and cloud_region):
258
284
  raise InterfaceError(
259
285
  "cloud_provider and cloud_region are required when endpoint is not provided"
@@ -274,14 +300,17 @@ class Connection:
274
300
 
275
301
  # Create httpx client for making API calls
276
302
  basic_auth = httpx.BasicAuth(username=flink_api_key, password=flink_api_secret)
277
- self._client = httpx.Client(
278
- auth=basic_auth,
279
- base_url=base_url,
280
- headers={
303
+ client_kwargs: dict[str, Any] = {
304
+ "auth": basic_auth,
305
+ "base_url": base_url,
306
+ "headers": {
281
307
  "Content-Type": "application/json",
282
308
  "User-Agent": self._http_user_agent,
283
309
  },
284
- )
310
+ }
311
+ if self._http_timeout_secs is not None:
312
+ client_kwargs["timeout"] = self._http_timeout_secs
313
+ self._client = httpx.Client(**client_kwargs)
285
314
 
286
315
  self._row_type_registry = RowTypeRegistry()
287
316
 
@@ -784,7 +813,9 @@ class Connection:
784
813
  response.raise_for_status()
785
814
  except httpx.HTTPStatusError as e:
786
815
  if e.response.status_code != 404:
787
- raise OperationalError("Error deleting statement") from e
816
+ raise OperationalError(
817
+ "Error deleting statement", http_status_code=e.response.status_code
818
+ ) from e
788
819
  # If the response is 404, it means we don't need to delete the statement.
789
820
  logger.info(f"Statement '{statement_name}' not found while deleting, ignoring")
790
821
 
@@ -803,6 +834,14 @@ class Connection:
803
834
  """
804
835
  return self._closed
805
836
 
837
+ @property
838
+ def http_timeout_secs(self) -> float | None:
839
+ """
840
+ Get the configured timeout (in seconds) for HTTP requests, or None if the
841
+ httpx default (5 seconds) is in effect.
842
+ """
843
+ return self._http_timeout_secs
844
+
806
845
  @property
807
846
  def http_user_agent(self) -> str:
808
847
  """
@@ -59,6 +59,7 @@ def connection_factory() -> Generator[ConnectionFactory, None, None]:
59
59
  database: str | None = None,
60
60
  result_page_fetch_pause_millis: int = 100,
61
61
  http_user_agent: str | None = None,
62
+ http_timeout_secs: float | None = None,
62
63
  endpoint: str | None = None,
63
64
  ) -> Connection:
64
65
  if flink_api_key is None:
@@ -92,6 +93,7 @@ def connection_factory() -> Generator[ConnectionFactory, None, None]:
92
93
  database=database,
93
94
  result_page_fetch_pause_millis=result_page_fetch_pause_millis,
94
95
  http_user_agent=http_user_agent,
96
+ http_timeout_secs=http_timeout_secs,
95
97
  endpoint=endpoint,
96
98
  )
97
99
 
@@ -153,8 +153,9 @@ class TestConnectionDeleteStatementErrors:
153
153
  response_mock.raise_for_status = raise_internal_server_error
154
154
  request_mock.return_value = response_mock
155
155
 
156
- with pytest.raises(OperationalError, match="Error deleting statement"):
156
+ with pytest.raises(OperationalError, match="Error deleting statement") as exc_info:
157
157
  invalid_credential_connection.delete_statement("statement-with-error")
158
+ assert exc_info.value.http_status_code == 500
158
159
 
159
160
 
160
161
  @pytest.mark.unit
@@ -1490,6 +1491,94 @@ class TestHttpUserAgentProperty:
1490
1491
  http_agent_connection_factory(http_user_agent=invalid_value)
1491
1492
 
1492
1493
 
1494
+ @pytest.mark.unit
1495
+ class TestHttpTimeoutSecs:
1496
+ """Tests for the http_timeout_secs constructor parameter and property."""
1497
+
1498
+ def test_default_is_none_and_httpx_default_applies(
1499
+ self, invalid_credential_connection: Connection
1500
+ ):
1501
+ """When not provided, the property is None and the httpx default (5s) is used."""
1502
+ assert invalid_credential_connection.http_timeout_secs is None
1503
+ # httpx's default Timeout(timeout=5.0) is wrapped in a Timeout object on the client.
1504
+ assert invalid_credential_connection._client.timeout == httpx.Timeout(5.0)
1505
+
1506
+ @pytest.mark.parametrize("timeout_value", [0.5, 1, 10, 30.0, 120])
1507
+ def test_custom_timeout_via_constructor(
1508
+ self, connection_factory: ConnectionFactory, timeout_value
1509
+ ):
1510
+ """A custom timeout is stored on the connection and applied to the httpx client."""
1511
+ conn = connection_factory(
1512
+ environment_id="env-id",
1513
+ organization_id="org-id",
1514
+ compute_pool_id="cp-id",
1515
+ cloud_provider="aws",
1516
+ cloud_region="us-east-1",
1517
+ flink_api_key="key",
1518
+ flink_api_secret="secret",
1519
+ http_timeout_secs=timeout_value,
1520
+ )
1521
+ assert conn.http_timeout_secs == timeout_value
1522
+ # httpx wraps the timeout into a Timeout(connect, read, write, pool) object.
1523
+ assert conn._client.timeout == httpx.Timeout(timeout_value)
1524
+
1525
+ @pytest.mark.parametrize(
1526
+ "invalid_value,expected_error",
1527
+ [
1528
+ (0, "http_timeout_secs must be positive, got 0"),
1529
+ (-1, "http_timeout_secs must be positive, got -1"),
1530
+ (-0.5, "http_timeout_secs must be positive, got -0.5"),
1531
+ ],
1532
+ )
1533
+ def test_rejects_non_positive(
1534
+ self,
1535
+ connection_factory: ConnectionFactory,
1536
+ invalid_value,
1537
+ expected_error,
1538
+ ):
1539
+ """Zero and negative values are rejected."""
1540
+ with pytest.raises(InterfaceError, match=expected_error):
1541
+ connection_factory(
1542
+ environment_id="env-id",
1543
+ organization_id="org-id",
1544
+ compute_pool_id="cp-id",
1545
+ cloud_provider="aws",
1546
+ cloud_region="us-east-1",
1547
+ flink_api_key="key",
1548
+ flink_api_secret="secret",
1549
+ http_timeout_secs=invalid_value,
1550
+ )
1551
+
1552
+ @pytest.mark.parametrize(
1553
+ "invalid_value,expected_error",
1554
+ [
1555
+ ("5", "http_timeout_secs must be a number, got str"),
1556
+ ([5], "http_timeout_secs must be a number, got list"),
1557
+ ({"secs": 5}, "http_timeout_secs must be a number, got dict"),
1558
+ (True, "http_timeout_secs must be a number, got bool"),
1559
+ (False, "http_timeout_secs must be a number, got bool"),
1560
+ ],
1561
+ )
1562
+ def test_rejects_non_numeric(
1563
+ self,
1564
+ connection_factory: ConnectionFactory,
1565
+ invalid_value,
1566
+ expected_error,
1567
+ ):
1568
+ """Non-numeric types (including bool) are rejected."""
1569
+ with pytest.raises(InterfaceError, match=expected_error):
1570
+ connection_factory(
1571
+ environment_id="env-id",
1572
+ organization_id="org-id",
1573
+ compute_pool_id="cp-id",
1574
+ cloud_provider="aws",
1575
+ cloud_region="us-east-1",
1576
+ flink_api_key="key",
1577
+ flink_api_secret="secret",
1578
+ http_timeout_secs=invalid_value,
1579
+ )
1580
+
1581
+
1493
1582
  @pytest.mark.unit
1494
1583
  class TestComputePoolIdParameter:
1495
1584
  """Test the compute_pool_id parameter for Connection methods."""