diff --git a/CHANGELOG.md b/CHANGELOG.md index f650b65..31a9a2c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Add [CONTRIBUTING.md] and [SECURITY.md] [#92] * This project is now following the [Best Practices] set forth by the Core Infrastructure Initiative! See [CII badge details]. [#92] +* Add `construct_from_cf_env` method to construct client instances from + Data Attribute Recommendation service binding on SAP Business Technology + Platform. [#97] [CONTRIBUTING.md]: /CONTRIBUTING.md [SECURITY.md]: /SECURITY.md @@ -18,6 +21,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [CII badge details]: https://bestpractices.coreinfrastructure.org/en/projects/4514 [#92]: https://github.com/SAP/data-attribute-recommendation-python-sdk/pull/92 +[#97]: https://github.com/SAP/data-attribute-recommendation-python-sdk/pull/92 ### Changed diff --git a/requirements.txt b/requirements.txt index 7a58b6b..dff5a5c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,3 @@ -requests==2.23.0 -typing-extensions==3.7.4.1 +cfenv==0.5.3 +requests==2.25.1 +typing-extensions==3.10.0.0 diff --git a/sap/aibus/dar/client/base_client.py b/sap/aibus/dar/client/base_client.py index 2d8b73f..d701216 100644 --- a/sap/aibus/dar/client/base_client.py +++ b/sap/aibus/dar/client/base_client.py @@ -3,6 +3,8 @@ """ from typing import TypeVar, Type +from cfenv import AppEnv + from sap.aibus.dar.client.util.credentials import ( CredentialsSource, OnlineCredentialsSource, @@ -87,6 +89,23 @@ def construct_from_jwt(cls: Type[DARClient], dar_url: str, token: str) -> DARCli source = StaticCredentialsSource(token) return cls(dar_url, source) + @classmethod + def construct_from_cf_env(cls: Type[DARClient]) -> DARClient: + """ + Constructs a DARClient from service binding in a CloudFoundry app. + + This is useful when the SDK is used in a CloudFoundry application on the + SAP Business Technology Platform where the application is bound to an instance + of the Data Attribute Recommendation service. + + This constructor assumes that only one instance of the service is bound + to the app. + :return: the client instance + """ + env = AppEnv() + dar = env.get_service(label="data-attribute-recommendation") + return cls.construct_from_service_key(dar.credentials) + class BaseClientWithSession(BaseClient): """ diff --git a/setup.py b/setup.py index 1567d33..8c4594a 100644 --- a/setup.py +++ b/setup.py @@ -27,7 +27,7 @@ def get_long_version(): author="Michael Haas", author_email="michael.haas01@sap.com", url="https://github.com/sap/data-attribute-recommendation-python-sdk", - install_requires=["requests>=2.20.0", "typing-extensions>=3.7.4.1"], + install_requires=["requests~=2.20.0", "typing-extensions~=3.7.4.1", "cfenv~=0.5.3"], packages=find_packages(exclude=["tests"]), include_package_data=True, python_requires="~=3.5", diff --git a/tests/sap/aibus/dar/client/test_data_manager_client.py b/tests/sap/aibus/dar/client/test_data_manager_client.py index 08a398a..3fb57d6 100644 --- a/tests/sap/aibus/dar/client/test_data_manager_client.py +++ b/tests/sap/aibus/dar/client/test_data_manager_client.py @@ -1,4 +1,5 @@ from io import BytesIO, StringIO +import json from typing import Any from unittest.mock import create_autospec, call, Mock, MagicMock @@ -122,6 +123,51 @@ def test_create_from_jwt_enforces_https(self): # RFC 4266 URLs should also be rejected self.clazz.construct_from_jwt("gopher://host:70/1", jwt) + def test_create_from_cf_env(self, monkeypatch): + vcap_services = { + "data-attribute-recommendation-staging": [ + { + "binding_guid": "XXXX", + "binding_name": None, + "credentials": { + "swagger": { + "dm": self.dar_url + "data-manager/doc/ui", + "inference": self.dar_url + "inference/doc/ui", + "mm": self.dar_url + "model-manager/doc/ui", + }, + "uaa": { + "clientid": self.clientid, + "clientsecret": self.clientsecret, + "identityzone": "dar-saas-test-app", + "identityzoneid": "XXX", + "subaccountid": "XXX", + "tenantid": "XXX", + "tenantmode": "dedicated", + "uaadomain": "authentication.sap.hana.ondemand.com", + "url": self.uaa_url, + "verificationkey": "XXX", + "xsappname": "XXX", + "zoneid": "XXXX", + }, + "url": self.dar_url, + }, + "instance_guid": "XXX", + "instance_name": "dar-instance-3", + "label": "data-attribute-recommendation", + "name": "dar-instance-3", + "plan": "standard", + "provider": None, + "syslog_drain_url": None, + "tags": [], + "volume_mounts": [], + } + ] + } + + monkeypatch.setenv("VCAP_SERVICES", json.dumps(vcap_services)) + client = self.clazz.construct_from_cf_env() + self._assert_fields_initialized(client) + def _assert_fields_initialized(self, client): assert isinstance(client.credentials_source, OnlineCredentialsSource) assert client.credentials_source.clientid == self.clientid