dbx-tools-postgres 0.6.185__tar.gz → 0.6.188__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.
@@ -1,14 +1,15 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: dbx-tools-postgres
3
- Version: 0.6.185
3
+ Version: 0.6.188
4
4
  Summary: WorkspaceClient-backed Lakebase Postgres resolution, SQLAlchemy engines, advisory locks, and LISTEN/NOTIFY topic bus
5
5
  Requires-Dist: asyncpg>=0.30
6
6
  Requires-Dist: databricks-sdk>=0.63.0
7
7
  Requires-Dist: greenlet>=3.2
8
8
  Requires-Dist: psycopg[binary]>=3.2.9
9
9
  Requires-Dist: sqlalchemy>=2.0.41
10
- Requires-Dist: dbx-tools-core==0.6.185
11
- Requires-Python: >=3.11, <3.14
10
+ Requires-Dist: dbx-tools-core==0.6.188
11
+ Requires-Dist: dbx-tools-core-rs==0.6.188
12
+ Requires-Python: >=3.11, <4
12
13
  Project-URL: Source, https://github.com/reggie-db/dbx-tools/tree/main/packages/py/postgres
13
14
  Description-Content-Type: text/markdown
14
15
 
@@ -16,8 +17,8 @@ Description-Content-Type: text/markdown
16
17
 
17
18
  Python Lakebase/Postgres connection setup, advisory locks, and topic fan-out for
18
19
  services that already hold a Databricks `WorkspaceClient`. This package is
19
- the Python counterpart to `@dbx-tools/postgres` plus `@dbx-tools/appkit`'s
20
- address parsing.
20
+ the Python counterpart to `@dbx-tools/postgres`. Lakebase address parsing comes
21
+ directly from the generated `dbx-tools-core-rs` Rust bindings.
21
22
 
22
23
  Install from PyPI:
23
24
 
@@ -34,7 +35,7 @@ pip install "dbx-tools-postgres @ git+https://github.com/reggie-db/dbx-tools.git
34
35
  Key features:
35
36
 
36
37
  - accepts the same Postgres URI, Lakebase resource path, hostname, and project-id
37
- address shapes as `@dbx-tools/appkit`;
38
+ address shapes as `@dbx-tools/appkit` through the native Rust parser;
38
39
  - resolves missing autoscaling endpoint fields through
39
40
  `WorkspaceClient.api_client`;
40
41
  - resolves provisioned Lakebase instance DNS through
@@ -2,8 +2,8 @@
2
2
 
3
3
  Python Lakebase/Postgres connection setup, advisory locks, and topic fan-out for
4
4
  services that already hold a Databricks `WorkspaceClient`. This package is
5
- the Python counterpart to `@dbx-tools/postgres` plus `@dbx-tools/appkit`'s
6
- address parsing.
5
+ the Python counterpart to `@dbx-tools/postgres`. Lakebase address parsing comes
6
+ directly from the generated `dbx-tools-core-rs` Rust bindings.
7
7
 
8
8
  Install from PyPI:
9
9
 
@@ -20,7 +20,7 @@ pip install "dbx-tools-postgres @ git+https://github.com/reggie-db/dbx-tools.git
20
20
  Key features:
21
21
 
22
22
  - accepts the same Postgres URI, Lakebase resource path, hostname, and project-id
23
- address shapes as `@dbx-tools/appkit`;
23
+ address shapes as `@dbx-tools/appkit` through the native Rust parser;
24
24
  - resolves missing autoscaling endpoint fields through
25
25
  `WorkspaceClient.api_client`;
26
26
  - resolves provisioned Lakebase instance DNS through
@@ -1,16 +1,17 @@
1
1
  [project]
2
2
  name = "dbx-tools-postgres"
3
- version = "0.6.185"
3
+ version = "0.6.188"
4
4
  description = "WorkspaceClient-backed Lakebase Postgres resolution, SQLAlchemy engines, advisory locks, and LISTEN/NOTIFY topic bus"
5
5
  readme = "README.md"
6
- requires-python = ">=3.11,<3.14"
6
+ requires-python = ">=3.11,<4"
7
7
  dependencies = [
8
8
  "asyncpg>=0.30",
9
9
  "databricks-sdk>=0.63.0",
10
10
  "greenlet>=3.2",
11
11
  "psycopg[binary]>=3.2.9",
12
12
  "sqlalchemy>=2.0.41",
13
- "dbx-tools-core==0.6.185",
13
+ "dbx-tools-core==0.6.188",
14
+ "dbx-tools-core-rs==0.6.188",
14
15
  ]
15
16
 
16
17
  [project.urls]
@@ -2,11 +2,11 @@
2
2
 
3
3
  [project]
4
4
  name = "dbx-tools-postgres"
5
- version = "0.6.185"
5
+ version = "0.6.188"
6
6
  description = "WorkspaceClient-backed Lakebase Postgres resolution, SQLAlchemy engines, advisory locks, and LISTEN/NOTIFY topic bus"
7
7
  readme = "README.md"
8
- requires-python = ">=3.11,<3.14"
9
- dependencies = ["asyncpg>=0.30", "databricks-sdk>=0.63.0", "greenlet>=3.2", "psycopg[binary]>=3.2.9", "sqlalchemy>=2.0.41", "dbx-tools-core==0.6.185"]
8
+ requires-python = ">=3.11,<4"
9
+ dependencies = ["asyncpg>=0.30", "databricks-sdk>=0.63.0", "greenlet>=3.2", "psycopg[binary]>=3.2.9", "sqlalchemy>=2.0.41", "dbx-tools-core==0.6.188", "dbx-tools-core-rs==0.6.188"]
10
10
 
11
11
  [project.urls]
12
12
  Source = "https://github.com/reggie-db/dbx-tools/tree/main/packages/py/postgres"
@@ -0,0 +1,31 @@
1
+ from typing import Literal, TypeAlias
2
+
3
+ from dbx_tools.core_rs.bindings import (
4
+ ParsedAddress,
5
+ parse_address,
6
+ parse_resource_path,
7
+ )
8
+ from dbx_tools.core_rs.bindings import (
9
+ SslMode as NativeSslMode,
10
+ )
11
+
12
+ """Lakebase address types and parsers backed by the Databricks Rust bindings."""
13
+
14
+ SslMode = Literal["require", "disable", "prefer"]
15
+ SSL_MODES: tuple[SslMode, ...] = ("require", "disable", "prefer")
16
+ LakebaseConnectionInputs: TypeAlias = ParsedAddress
17
+
18
+ parseAddress = parse_address
19
+ parseResourcePath = parse_resource_path
20
+
21
+ __all__ = [
22
+ "SSL_MODES",
23
+ "LakebaseConnectionInputs",
24
+ "NativeSslMode",
25
+ "ParsedAddress",
26
+ "SslMode",
27
+ "parseAddress",
28
+ "parseResourcePath",
29
+ "parse_address",
30
+ "parse_resource_path",
31
+ ]
@@ -13,7 +13,14 @@ from sqlalchemy import create_engine as sqlalchemy_create_engine
13
13
  from sqlalchemy.ext.asyncio import AsyncEngine
14
14
  from sqlalchemy.ext.asyncio import create_async_engine as sqlalchemy_create_async_engine
15
15
 
16
- from .address import SSL_MODES, ParsedAddress, SslMode, parse_address, parse_resource_path
16
+ from .address import (
17
+ SSL_MODES,
18
+ NativeSslMode,
19
+ ParsedAddress,
20
+ SslMode,
21
+ parse_address,
22
+ parse_resource_path,
23
+ )
17
24
 
18
25
  """Lakebase connection resolution and connect-time credential injection."""
19
26
 
@@ -474,7 +481,7 @@ def _parse_port(value: object) -> int:
474
481
  def _parse_ssl_mode(value: object) -> SslMode:
475
482
  if value is None or value == "":
476
483
  return _DEFAULT_SSL_MODE
477
- mode = str(value).strip().lower()
484
+ mode = value.name.lower() if isinstance(value, NativeSslMode) else str(value).strip().lower()
478
485
  if mode not in SSL_MODES:
479
486
  raise ValueError(f"PGSSLMODE must be one of {', '.join(SSL_MODES)}, got {value!r}")
480
487
  return mode # type: ignore[return-value]
@@ -1,114 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import re
4
- from dataclasses import asdict, dataclass
5
- from typing import Literal
6
- from urllib.parse import parse_qs, unquote, urlparse
7
-
8
- SslMode = Literal["require", "disable", "prefer"]
9
- SSL_MODES: tuple[SslMode, ...] = ("require", "disable", "prefer")
10
-
11
- _URL_SCHEME_RE = re.compile(r"^(postgres|postgresql)://", re.IGNORECASE)
12
- _PROJECT_ID_RE = re.compile(r"^[a-z][a-z0-9-]{0,61}[a-z0-9]$|^[a-z]$")
13
- _HOSTNAME_HINT_RE = re.compile(r"^[a-z0-9][a-z0-9-]*(\.[a-z0-9][a-z0-9-]*)+$", re.IGNORECASE)
14
-
15
-
16
- @dataclass(frozen=True, slots=True)
17
- class LakebaseConnectionInputs:
18
- project: str | None = None
19
- branch: str | None = None
20
- endpoint: str | None = None
21
- database: str | None = None
22
- host: str | None = None
23
- port: int | None = None
24
- ssl_mode: SslMode | None = None
25
-
26
- def as_dict(self) -> dict[str, object]:
27
- return {key: value for key, value in asdict(self).items() if value is not None}
28
-
29
-
30
- @dataclass(frozen=True, slots=True)
31
- class ParsedAddress(LakebaseConnectionInputs):
32
- endpoint_id: str | None = None
33
- database_resource_id: str | None = None
34
- user: str | None = None
35
-
36
-
37
- def parse_address(value: str | None) -> ParsedAddress:
38
- if not value or not (address := value.strip()):
39
- return ParsedAddress()
40
- if _URL_SCHEME_RE.match(address):
41
- return _parse_uri(address)
42
- if address.startswith("projects/"):
43
- return _parse_resource_path_segments(address)
44
- if "." in address and _HOSTNAME_HINT_RE.fullmatch(address):
45
- return ParsedAddress(host=address)
46
- if _PROJECT_ID_RE.fullmatch(address):
47
- return ParsedAddress(project=address)
48
- return ParsedAddress()
49
-
50
-
51
- def parse_resource_path(value: str | None) -> ParsedAddress:
52
- if not value or not (address := value.strip()).startswith("projects/"):
53
- return ParsedAddress()
54
- return _parse_resource_path_segments(address)
55
-
56
-
57
- def _parse_uri(address: str) -> ParsedAddress:
58
- try:
59
- parsed = urlparse(address)
60
- port = parsed.port
61
- except ValueError:
62
- return ParsedAddress()
63
- if parsed.scheme.lower() not in {"postgres", "postgresql"}:
64
- return ParsedAddress()
65
- ssl_mode_value = parse_qs(parsed.query).get("sslmode") or parse_qs(parsed.query).get("sslMode")
66
- ssl_mode = ssl_mode_value[0].lower() if ssl_mode_value else None
67
- return ParsedAddress(
68
- host=parsed.hostname or None,
69
- port=port,
70
- user=unquote(parsed.username) if parsed.username else None,
71
- database=unquote(parsed.path.removeprefix("/")) or None,
72
- ssl_mode=ssl_mode if ssl_mode in SSL_MODES else None,
73
- )
74
-
75
-
76
- def _parse_resource_path_segments(address: str) -> ParsedAddress:
77
- parts = address.split("/")
78
- if len(parts) < 2 or parts[0] != "projects" or not parts[1]:
79
- return ParsedAddress()
80
- project = parts[1]
81
- if len(parts) == 2:
82
- return ParsedAddress(project=project)
83
- if len(parts) == 4 and parts[2] == "branches" and parts[3]:
84
- return ParsedAddress(project=project, branch=parts[3])
85
- if (
86
- len(parts) == 6
87
- and parts[2] == "branches"
88
- and parts[3]
89
- and parts[4] == "endpoints"
90
- and parts[5]
91
- ):
92
- return ParsedAddress(
93
- project=project,
94
- branch=parts[3],
95
- endpoint=address,
96
- endpoint_id=parts[5],
97
- )
98
- if (
99
- len(parts) == 6
100
- and parts[2] == "branches"
101
- and parts[3]
102
- and parts[4] == "databases"
103
- and parts[5]
104
- ):
105
- return ParsedAddress(
106
- project=project,
107
- branch=parts[3],
108
- database_resource_id=parts[5],
109
- )
110
- return ParsedAddress()
111
-
112
-
113
- parseAddress = parse_address
114
- parseResourcePath = parse_resource_path