Skip to content

Commit b62c65d

Browse files
committed
refactor(dpi_ng): promote shared auth, config, exceptions, and odata_client to dpi_ng base level
1 parent 6b20348 commit b62c65d

23 files changed

Lines changed: 765 additions & 576 deletions
Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,44 @@
1-
"""DPI Next Gen SDK modules."""
1+
"""DPI Next Gen SDK modules.
2+
3+
Shared building blocks for all DPI NG capabilities:
4+
5+
- :mod:`sap_cloud_sdk.core.dpi_ng.auth` — AuthProvider ABC + strategies
6+
- :mod:`sap_cloud_sdk.core.dpi_ng.config` — BaseCapabilityConfig
7+
- :mod:`sap_cloud_sdk.core.dpi_ng.exceptions` — DPINGError hierarchy
8+
- :mod:`sap_cloud_sdk.core.dpi_ng.odata_client` — BaseODataClient
9+
"""
10+
11+
from .auth import AuthProvider, BearerTokenAuth, ClientCertificateAuth, ClientCredentialsAuth
12+
from .config import BaseCapabilityConfig
13+
from .exceptions import (
14+
AuthenticationError,
15+
AuthorizationError,
16+
ClientCreationError,
17+
ConflictError,
18+
DPINGError,
19+
NotFoundError,
20+
ODataError,
21+
ValidationError,
22+
)
23+
from .odata_client import BaseODataClient
24+
25+
__all__ = [
26+
# auth
27+
"AuthProvider",
28+
"BearerTokenAuth",
29+
"ClientCredentialsAuth",
30+
"ClientCertificateAuth",
31+
# config
32+
"BaseCapabilityConfig",
33+
# exceptions
34+
"DPINGError",
35+
"ClientCreationError",
36+
"AuthenticationError",
37+
"AuthorizationError",
38+
"ValidationError",
39+
"NotFoundError",
40+
"ConflictError",
41+
"ODataError",
42+
# odata transport
43+
"BaseODataClient",
44+
]

src/sap_cloud_sdk/core/dpi_ng/consent/auth.py renamed to src/sap_cloud_sdk/core/dpi_ng/auth.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
"""Authentication strategy implementations for the Consent SDK.
1+
"""Authentication strategy implementations shared across all DPI NG capabilities.
22
33
Each provider implements AuthProvider.apply(), which configures the
44
requests.Session passed to it to inject the chosen auth mechanism.
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
"""Base configuration shared across all DPI NG capabilities."""
2+
3+
import logging
4+
import re
5+
from dataclasses import dataclass, field
6+
7+
from .auth import AuthProvider, ClientCertificateAuth
8+
9+
logger = logging.getLogger(__name__)
10+
11+
_URL_PATTERN = re.compile(r"^https?://[^\s/$.?#].[^\s]*$")
12+
13+
14+
@dataclass
15+
class BaseCapabilityConfig:
16+
"""Base configuration for a DPI NG capability client.
17+
18+
Subclasses add a ``service_path`` (and any other capability-specific fields)
19+
on top of these shared fields.
20+
21+
Args:
22+
base_url: URL of the DPI external service router
23+
(e.g. ``https://api.service.<region>.ngdpi.dpp.cloud.sap``).
24+
This URL can be found in the credentials of the ``data-privacy-integration``
25+
service instance.
26+
auth: Authentication strategy - one of BearerTokenAuth, ClientCredentialsAuth,
27+
or ClientCertificateAuth.
28+
timeout: HTTP request timeout in seconds (default 30).
29+
verify_ssl: Verify TLS certificates - set False only in local dev.
30+
Overridden by ``ClientCertificateAuth`` when a custom ``ca_file`` is provided.
31+
tenant_id: Tenant identifier sent as the ``x-tenant-id`` HTTP header.
32+
**Required** for ``ClientCertificateAuth`` — mTLS does not carry a
33+
tenant claim, so the service router needs it to route requests to the
34+
correct tenant. Must not be provided for ``BearerTokenAuth`` or
35+
``ClientCredentialsAuth``, which already embed the tenant identity in
36+
the token.
37+
"""
38+
39+
base_url: str
40+
auth: AuthProvider
41+
timeout: float = 30.0
42+
verify_ssl: bool = True
43+
tenant_id: str | None = field(default=None)
44+
45+
def __post_init__(self) -> None:
46+
"""Validate config after dataclass construction.
47+
48+
Raises:
49+
ValueError: If *base_url* is not a valid HTTP(S) URL, *auth* is not
50+
an ``AuthProvider`` instance, ``ClientCertificateAuth`` is used without
51+
*tenant_id*, or *tenant_id* is provided with a non-cert auth type.
52+
"""
53+
logger.info("Invoked BaseCapabilityConfig.__post_init__")
54+
if not _URL_PATTERN.match(self.base_url):
55+
logger.error("Invalid base_url — value=%r", self.base_url)
56+
raise ValueError(
57+
f"base_url must be a valid HTTP(S) URL, got: {self.base_url!r}"
58+
)
59+
if not isinstance(self.auth, AuthProvider):
60+
logger.error(
61+
"auth is not an AuthProvider instance — type=%s", type(self.auth)
62+
)
63+
raise ValueError("auth must be an AuthProvider instance")
64+
self.base_url = self.base_url.rstrip("/")
65+
is_cert_auth = isinstance(self.auth, ClientCertificateAuth)
66+
if is_cert_auth and not self.tenant_id:
67+
logger.error("tenant_id is required for ClientCertificateAuth")
68+
raise ValueError("tenant_id is required when using ClientCertificateAuth")
69+
if not is_cert_auth and self.tenant_id is not None:
70+
logger.error("tenant_id is not applicable for %s", type(self.auth).__name__)
71+
raise ValueError(
72+
f"tenant_id must not be set for {type(self.auth).__name__}; "
73+
"it is only valid for ClientCertificateAuth"
74+
)
75+
logger.debug(
76+
"Config validated — base_url=%s verify_ssl=%s",
77+
self.base_url,
78+
self.verify_ssl,
79+
)
80+
logger.info("Exiting BaseCapabilityConfig.__post_init__")

src/sap_cloud_sdk/core/dpi_ng/consent/__init__.py

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

1818
from sap_cloud_sdk.core.telemetry import Module, Operation, record_metrics
1919

20-
from .auth import (
20+
from sap_cloud_sdk.core.dpi_ng.auth import (
2121
AuthProvider,
2222
BearerTokenAuth,
2323
ClientCertificateAuth,
@@ -30,13 +30,13 @@
3030
ConsentService,
3131
ConsentTemplateService,
3232
)
33-
from .config import ConsentSDKConfig
34-
from .exceptions import (
33+
from .config import ConsentConfig
34+
from sap_cloud_sdk.core.dpi_ng.exceptions import (
3535
AuthenticationError,
3636
AuthorizationError,
3737
ClientCreationError,
3838
ConflictError,
39-
ConsentSDKError,
39+
DPINGError,
4040
NotFoundError,
4141
ODataError,
4242
ValidationError,
@@ -62,20 +62,20 @@ class ConsentClient:
6262

6363
def __init__(
6464
self,
65-
config: ConsentSDKConfig,
65+
config: ConsentConfig,
6666
*,
6767
_telemetry_source: Module | None = None,
6868
) -> None:
6969
"""Initialise all service clients from the given config.
7070
7171
Args:
72-
config: Validated ``ConsentSDKConfig`` containing the base URL and auth strategy.
72+
config: Validated ``ConsentConfig`` containing the base URL and auth strategy.
7373
_telemetry_source: Internal parameter; not for end-user use.
7474
"""
75-
from .client import _ODataClient
75+
from .client import _ConsentODataClient
7676

7777
self._telemetry_source = _telemetry_source
78-
self._odata = _ODataClient(config)
78+
self._odata = _ConsentODataClient(config)
7979
self.consents: ConsentService = ConsentService(
8080
self._odata, _telemetry_source=_telemetry_source
8181
)
@@ -107,7 +107,7 @@ def __exit__(self, *_: object) -> None:
107107

108108
@record_metrics(Module.DPI_NG, Operation.DPI_NG_CONSENT_CREATE_CLIENT)
109109
def create_client(
110-
config: ConsentSDKConfig | None = None,
110+
config: ConsentConfig | None = None,
111111
*,
112112
base_url: str | None = None,
113113
auth: AuthProvider | None = None,
@@ -118,7 +118,7 @@ def create_client(
118118
"""Create a ConsentClient with explicit configuration or individual keyword arguments.
119119
120120
Args:
121-
config: Pre-built ``ConsentSDKConfig``. When provided, all other kwargs
121+
config: Pre-built ``ConsentConfig``. When provided, all other kwargs
122122
are ignored.
123123
base_url: URL of the DPI external service router
124124
(e.g. ``https://api.service.<region>.ngdpi.dpp.cloud.sap``).
@@ -147,7 +147,7 @@ def create_client(
147147
raise ValueError(
148148
"base_url and auth are required when config is not provided"
149149
)
150-
config = ConsentSDKConfig(
150+
config = ConsentConfig(
151151
base_url=base_url,
152152
auth=auth,
153153
timeout=timeout,
@@ -162,14 +162,14 @@ def create_client(
162162
# factory + top-level client
163163
"create_client",
164164
"ConsentClient",
165-
"ConsentSDKConfig",
165+
"ConsentConfig",
166166
# auth strategies
167167
"AuthProvider",
168168
"BearerTokenAuth",
169169
"ClientCredentialsAuth",
170170
"ClientCertificateAuth",
171171
# exceptions
172-
"ConsentSDKError",
172+
"DPINGError",
173173
"ClientCreationError",
174174
"AuthenticationError",
175175
"AuthorizationError",

0 commit comments

Comments
 (0)