Skip to content

Commit 4d160db

Browse files
authored
fix(main): ensure compatibility with the latest connect-python (#123)
* chore: update "connect-python[compiler] package * chore: add 'protoc-gen-connect-python' package (now separate) * chore: fix ConnectRPC Python client generation path * chore: update connect-python references in dependency checks and generation script * chore: remove old generated ConnectRPC code * chore(core): migrate to connect-python 0.6.0 BREAKING CHANGE: Upgrade from connect-python 0.4.2 to 0.6.0 The connect-python library underwent a major API change between versions 0.4.x and 0.6.0, requiring updates throughout the codebase. Changes: - Update dependencies: split connect-python[compiler] into separate packages - connect-python>=0.6.0,<0.7 - protoc-gen-connect-python>=0.6.0,<0.7 - Fix binary name: protoc-gen-connect_python → protoc-gen-connect-python The new version uses hyphens instead of underscores in the binary name - Regenerate proto files with new API - Deleted 15 old *_pb2_connect.py files - Generated 15 new *_connect.py files - Fixed imports to use relative paths - Update SDK implementation (kas_connect_rpc_client.py) - Replace urllib3.PoolManager with httpx.Client - Use AccessServiceClientSync instead of AccessServiceClient - Update client instantiation: base_url and session parameters - Change extra_headers parameter to headers - Add proper context manager usage for HTTP clients - Update test mocks (test_kas_client.py) - Patch httpx.Client instead of urllib3.PoolManager - Update mock setup for context managers - Fix import paths for new module structure - Update proto module exports (kas/__init__.py) - Export AccessServiceClientSync instead of AsyncAccessServiceClient * chore: fix AccessServiceClientSync instantiation * chore: fix tests * fix(main): 'KAS.close()' method * fix(main): warn & recommend context-manager use * fix(main): run 'uv sync -U' for 'otdf-python-proto' * fix(main): run 'uv sync -U' for 'otdf-python'
1 parent ce1c5b4 commit 4d160db

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

54 files changed

+8508
-6565
lines changed

otdf-python-proto/buf.gen.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ plugins:
1818
out: src/otdf_python_proto
1919

2020
# Connect Python client generation (preferred)
21-
- local: ../.venv/bin/protoc-gen-connect_python
21+
- local: ../.venv/bin/protoc-gen-connect-python
2222
out: src/otdf_python_proto
2323
opt:
2424
- paths=source_relative

otdf-python-proto/generated/__init__.py

Whitespace-only changes.

otdf-python-proto/scripts/generate_connect_proto.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ def check_dependencies() -> bool:
3737
print(" # macOS: brew install bufbuild/buf/buf")
3838
print(" # Or: go install github.com/bufbuild/buf/cmd/buf@latest")
3939
elif dep == "connect-python":
40-
print(" uv add connect-python[compiler]")
40+
print(" uv add connect-python protoc-gen-connect-python")
4141
return False
4242

4343
return True
@@ -141,9 +141,9 @@ def run_buf_generate(proto_gen_dir: Path) -> bool:
141141
print("Generating protobuf and Connect RPC files...")
142142

143143
try:
144-
# First, get the path to protoc-gen-connect_python
144+
# First, get the path to protoc-gen-connect-python
145145
result = subprocess.run(
146-
["uv", "run", "which", "protoc-gen-connect_python"],
146+
["uv", "run", "which", "protoc-gen-connect-python"],
147147
cwd=proto_gen_dir,
148148
capture_output=True,
149149
text=True,
@@ -159,7 +159,7 @@ def run_buf_generate(proto_gen_dir: Path) -> bool:
159159

160160
# Replace the local plugin path
161161
updated_content = content.replace(
162-
"- local: protoc-gen-connect_python", f"- local: {connect_plugin_path}"
162+
"- local: protoc-gen-connect-python", f"- local: {connect_plugin_path}"
163163
)
164164

165165
with buf_gen_path.open("w") as f:

otdf-python-proto/src/otdf_python_proto/__init__.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,25 +13,21 @@
1313
__version__ = "0.0.0"
1414

1515
# Import submodules to make them available
16-
from . import authorization
16+
# Note: authorization, entityresolution, wellknownconfiguration and policy subdirectories
17+
# are imported lazily to avoid import errors from generated protobuf files
1718
from . import common
1819
from . import entity
19-
from . import entityresolution
2020
from . import kas
2121
from . import legacy_grpc
2222
from . import logger
2323
from . import policy
24-
from . import wellknownconfiguration
2524

2625
# Export main module categories
2726
__all__ = [
28-
"authorization",
2927
"common",
3028
"entity",
31-
"entityresolution",
3229
"kas",
3330
"legacy_grpc",
3431
"logger",
3532
"policy",
36-
"wellknownconfiguration",
3733
]
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,11 @@
11
"""authorization protobuf definitions."""
2+
3+
from .authorization_connect import (
4+
AuthorizationServiceClient,
5+
AuthorizationServiceClientSync,
6+
)
7+
8+
__all__ = [
9+
"AuthorizationServiceClient",
10+
"AuthorizationServiceClientSync",
11+
]
Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
1+
# -*- coding: utf-8 -*-
2+
# Generated by https://github.com/connectrpc/connect-python. DO NOT EDIT!
3+
# source: authorization/authorization.proto
4+
5+
from collections.abc import AsyncGenerator, AsyncIterator, Iterable, Iterator, Mapping
6+
from typing import Protocol
7+
8+
from connectrpc.client import ConnectClient, ConnectClientSync
9+
from connectrpc.code import Code
10+
from connectrpc.errors import ConnectError
11+
from connectrpc.interceptor import Interceptor, InterceptorSync
12+
from connectrpc.method import IdempotencyLevel, MethodInfo
13+
from connectrpc.request import Headers, RequestContext
14+
from connectrpc.server import ConnectASGIApplication, ConnectWSGIApplication, Endpoint, EndpointSync
15+
from . import authorization_pb2 as authorization_dot_authorization__pb2
16+
17+
18+
class AuthorizationService(Protocol):
19+
async def get_decisions(self, request: authorization_dot_authorization__pb2.GetDecisionsRequest, ctx: RequestContext) -> authorization_dot_authorization__pb2.GetDecisionsResponse:
20+
raise ConnectError(Code.UNIMPLEMENTED, "Not implemented")
21+
22+
async def get_decisions_by_token(self, request: authorization_dot_authorization__pb2.GetDecisionsByTokenRequest, ctx: RequestContext) -> authorization_dot_authorization__pb2.GetDecisionsByTokenResponse:
23+
raise ConnectError(Code.UNIMPLEMENTED, "Not implemented")
24+
25+
async def get_entitlements(self, request: authorization_dot_authorization__pb2.GetEntitlementsRequest, ctx: RequestContext) -> authorization_dot_authorization__pb2.GetEntitlementsResponse:
26+
raise ConnectError(Code.UNIMPLEMENTED, "Not implemented")
27+
28+
29+
class AuthorizationServiceASGIApplication(ConnectASGIApplication[AuthorizationService]):
30+
def __init__(self, service: AuthorizationService | AsyncGenerator[AuthorizationService], *, interceptors: Iterable[Interceptor]=(), read_max_bytes: int | None = None) -> None:
31+
super().__init__(
32+
service=service,
33+
endpoints=lambda svc: {
34+
"/authorization.AuthorizationService/GetDecisions": Endpoint.unary(
35+
method=MethodInfo(
36+
name="GetDecisions",
37+
service_name="authorization.AuthorizationService",
38+
input=authorization_dot_authorization__pb2.GetDecisionsRequest,
39+
output=authorization_dot_authorization__pb2.GetDecisionsResponse,
40+
idempotency_level=IdempotencyLevel.UNKNOWN,
41+
),
42+
function=svc.get_decisions,
43+
),
44+
"/authorization.AuthorizationService/GetDecisionsByToken": Endpoint.unary(
45+
method=MethodInfo(
46+
name="GetDecisionsByToken",
47+
service_name="authorization.AuthorizationService",
48+
input=authorization_dot_authorization__pb2.GetDecisionsByTokenRequest,
49+
output=authorization_dot_authorization__pb2.GetDecisionsByTokenResponse,
50+
idempotency_level=IdempotencyLevel.UNKNOWN,
51+
),
52+
function=svc.get_decisions_by_token,
53+
),
54+
"/authorization.AuthorizationService/GetEntitlements": Endpoint.unary(
55+
method=MethodInfo(
56+
name="GetEntitlements",
57+
service_name="authorization.AuthorizationService",
58+
input=authorization_dot_authorization__pb2.GetEntitlementsRequest,
59+
output=authorization_dot_authorization__pb2.GetEntitlementsResponse,
60+
idempotency_level=IdempotencyLevel.UNKNOWN,
61+
),
62+
function=svc.get_entitlements,
63+
),
64+
},
65+
interceptors=interceptors,
66+
read_max_bytes=read_max_bytes,
67+
)
68+
69+
@property
70+
def path(self) -> str:
71+
"""Returns the URL path to mount the application to when serving multiple applications."""
72+
return "/authorization.AuthorizationService"
73+
74+
75+
class AuthorizationServiceClient(ConnectClient):
76+
async def get_decisions(
77+
self,
78+
request: authorization_dot_authorization__pb2.GetDecisionsRequest,
79+
*,
80+
headers: Headers | Mapping[str, str] | None = None,
81+
timeout_ms: int | None = None,
82+
) -> authorization_dot_authorization__pb2.GetDecisionsResponse:
83+
return await self.execute_unary(
84+
request=request,
85+
method=MethodInfo(
86+
name="GetDecisions",
87+
service_name="authorization.AuthorizationService",
88+
input=authorization_dot_authorization__pb2.GetDecisionsRequest,
89+
output=authorization_dot_authorization__pb2.GetDecisionsResponse,
90+
idempotency_level=IdempotencyLevel.UNKNOWN,
91+
),
92+
headers=headers,
93+
timeout_ms=timeout_ms,
94+
)
95+
96+
async def get_decisions_by_token(
97+
self,
98+
request: authorization_dot_authorization__pb2.GetDecisionsByTokenRequest,
99+
*,
100+
headers: Headers | Mapping[str, str] | None = None,
101+
timeout_ms: int | None = None,
102+
) -> authorization_dot_authorization__pb2.GetDecisionsByTokenResponse:
103+
return await self.execute_unary(
104+
request=request,
105+
method=MethodInfo(
106+
name="GetDecisionsByToken",
107+
service_name="authorization.AuthorizationService",
108+
input=authorization_dot_authorization__pb2.GetDecisionsByTokenRequest,
109+
output=authorization_dot_authorization__pb2.GetDecisionsByTokenResponse,
110+
idempotency_level=IdempotencyLevel.UNKNOWN,
111+
),
112+
headers=headers,
113+
timeout_ms=timeout_ms,
114+
)
115+
116+
async def get_entitlements(
117+
self,
118+
request: authorization_dot_authorization__pb2.GetEntitlementsRequest,
119+
*,
120+
headers: Headers | Mapping[str, str] | None = None,
121+
timeout_ms: int | None = None,
122+
) -> authorization_dot_authorization__pb2.GetEntitlementsResponse:
123+
return await self.execute_unary(
124+
request=request,
125+
method=MethodInfo(
126+
name="GetEntitlements",
127+
service_name="authorization.AuthorizationService",
128+
input=authorization_dot_authorization__pb2.GetEntitlementsRequest,
129+
output=authorization_dot_authorization__pb2.GetEntitlementsResponse,
130+
idempotency_level=IdempotencyLevel.UNKNOWN,
131+
),
132+
headers=headers,
133+
timeout_ms=timeout_ms,
134+
)
135+
136+
137+
class AuthorizationServiceSync(Protocol):
138+
def get_decisions(self, request: authorization_dot_authorization__pb2.GetDecisionsRequest, ctx: RequestContext) -> authorization_dot_authorization__pb2.GetDecisionsResponse:
139+
raise ConnectError(Code.UNIMPLEMENTED, "Not implemented")
140+
def get_decisions_by_token(self, request: authorization_dot_authorization__pb2.GetDecisionsByTokenRequest, ctx: RequestContext) -> authorization_dot_authorization__pb2.GetDecisionsByTokenResponse:
141+
raise ConnectError(Code.UNIMPLEMENTED, "Not implemented")
142+
def get_entitlements(self, request: authorization_dot_authorization__pb2.GetEntitlementsRequest, ctx: RequestContext) -> authorization_dot_authorization__pb2.GetEntitlementsResponse:
143+
raise ConnectError(Code.UNIMPLEMENTED, "Not implemented")
144+
145+
146+
class AuthorizationServiceWSGIApplication(ConnectWSGIApplication):
147+
def __init__(self, service: AuthorizationServiceSync, interceptors: Iterable[InterceptorSync]=(), read_max_bytes: int | None = None) -> None:
148+
super().__init__(
149+
endpoints={
150+
"/authorization.AuthorizationService/GetDecisions": EndpointSync.unary(
151+
method=MethodInfo(
152+
name="GetDecisions",
153+
service_name="authorization.AuthorizationService",
154+
input=authorization_dot_authorization__pb2.GetDecisionsRequest,
155+
output=authorization_dot_authorization__pb2.GetDecisionsResponse,
156+
idempotency_level=IdempotencyLevel.UNKNOWN,
157+
),
158+
function=service.get_decisions,
159+
),
160+
"/authorization.AuthorizationService/GetDecisionsByToken": EndpointSync.unary(
161+
method=MethodInfo(
162+
name="GetDecisionsByToken",
163+
service_name="authorization.AuthorizationService",
164+
input=authorization_dot_authorization__pb2.GetDecisionsByTokenRequest,
165+
output=authorization_dot_authorization__pb2.GetDecisionsByTokenResponse,
166+
idempotency_level=IdempotencyLevel.UNKNOWN,
167+
),
168+
function=service.get_decisions_by_token,
169+
),
170+
"/authorization.AuthorizationService/GetEntitlements": EndpointSync.unary(
171+
method=MethodInfo(
172+
name="GetEntitlements",
173+
service_name="authorization.AuthorizationService",
174+
input=authorization_dot_authorization__pb2.GetEntitlementsRequest,
175+
output=authorization_dot_authorization__pb2.GetEntitlementsResponse,
176+
idempotency_level=IdempotencyLevel.UNKNOWN,
177+
),
178+
function=service.get_entitlements,
179+
),
180+
},
181+
interceptors=interceptors,
182+
read_max_bytes=read_max_bytes,
183+
)
184+
185+
@property
186+
def path(self) -> str:
187+
"""Returns the URL path to mount the application to when serving multiple applications."""
188+
return "/authorization.AuthorizationService"
189+
190+
191+
class AuthorizationServiceClientSync(ConnectClientSync):
192+
def get_decisions(
193+
self,
194+
request: authorization_dot_authorization__pb2.GetDecisionsRequest,
195+
*,
196+
headers: Headers | Mapping[str, str] | None = None,
197+
timeout_ms: int | None = None,
198+
) -> authorization_dot_authorization__pb2.GetDecisionsResponse:
199+
return self.execute_unary(
200+
request=request,
201+
method=MethodInfo(
202+
name="GetDecisions",
203+
service_name="authorization.AuthorizationService",
204+
input=authorization_dot_authorization__pb2.GetDecisionsRequest,
205+
output=authorization_dot_authorization__pb2.GetDecisionsResponse,
206+
idempotency_level=IdempotencyLevel.UNKNOWN,
207+
),
208+
headers=headers,
209+
timeout_ms=timeout_ms,
210+
)
211+
212+
def get_decisions_by_token(
213+
self,
214+
request: authorization_dot_authorization__pb2.GetDecisionsByTokenRequest,
215+
*,
216+
headers: Headers | Mapping[str, str] | None = None,
217+
timeout_ms: int | None = None,
218+
) -> authorization_dot_authorization__pb2.GetDecisionsByTokenResponse:
219+
return self.execute_unary(
220+
request=request,
221+
method=MethodInfo(
222+
name="GetDecisionsByToken",
223+
service_name="authorization.AuthorizationService",
224+
input=authorization_dot_authorization__pb2.GetDecisionsByTokenRequest,
225+
output=authorization_dot_authorization__pb2.GetDecisionsByTokenResponse,
226+
idempotency_level=IdempotencyLevel.UNKNOWN,
227+
),
228+
headers=headers,
229+
timeout_ms=timeout_ms,
230+
)
231+
232+
def get_entitlements(
233+
self,
234+
request: authorization_dot_authorization__pb2.GetEntitlementsRequest,
235+
*,
236+
headers: Headers | Mapping[str, str] | None = None,
237+
timeout_ms: int | None = None,
238+
) -> authorization_dot_authorization__pb2.GetEntitlementsResponse:
239+
return self.execute_unary(
240+
request=request,
241+
method=MethodInfo(
242+
name="GetEntitlements",
243+
service_name="authorization.AuthorizationService",
244+
input=authorization_dot_authorization__pb2.GetEntitlementsRequest,
245+
output=authorization_dot_authorization__pb2.GetEntitlementsResponse,
246+
idempotency_level=IdempotencyLevel.UNKNOWN,
247+
),
248+
headers=headers,
249+
timeout_ms=timeout_ms,
250+
)

0 commit comments

Comments
 (0)