From e5b9c2951a217bbbc03ac8893ffa358eef787df3 Mon Sep 17 00:00:00 2001 From: Daniel Perrefort Date: Thu, 23 Jul 2026 16:36:17 -0400 Subject: [PATCH 1/2] Drops schema module --- keystone_client/client.py | 22 +++-- keystone_client/schema.py | 40 --------- .../test_client/test_AsyncKeystoneClient.py | 6 +- .../test_client/test_KeystoneClient.py | 6 +- tests/unit_tests/test_schema/__init__.py | 0 tests/unit_tests/test_schema/test_Endpoint.py | 87 ------------------- 6 files changed, 16 insertions(+), 145 deletions(-) delete mode 100644 keystone_client/schema.py delete mode 100644 tests/unit_tests/test_schema/__init__.py delete mode 100644 tests/unit_tests/test_schema/test_Endpoint.py diff --git a/keystone_client/client.py b/keystone_client/client.py index 6cbdf66..9c2d05b 100644 --- a/keystone_client/client.py +++ b/keystone_client/client.py @@ -9,18 +9,16 @@ import httpx from httpx import HTTPStatusError + from keystone_client.http import AsyncHTTPClient, HTTPClient -from keystone_client.schema import Endpoint, Schema class ClientBase(abc.ABC): """Base client class with shared application constants and helpers.""" - schema = Schema() - - LOGIN_ENDPOINT = Endpoint('authentication/login') - LOGOUT_ENDPOINT = Endpoint('authentication/logout') - IDENTITY_ENDPOINT = Endpoint('authentication/whoami') + _login_endpoint = 'authentication/login' + _logout_endpoint = 'authentication/logout' + _identity_endpoint = 'authentication/whoami' @abc.abstractmethod def login(self, username: str, password: str, timeout: int) -> None: @@ -68,7 +66,7 @@ def login(self, username: str, password: str, timeout: int = httpx.USE_CLIENT_DE """ self.http_post( - endpoint=self.LOGIN_ENDPOINT, + endpoint=self._login_endpoint, json={'username': username, 'password': password}, timeout=timeout ).raise_for_status() @@ -81,7 +79,7 @@ def logout(self, timeout: int = httpx.USE_CLIENT_DEFAULT) -> None: """ response = self.http_post( - endpoint=self.LOGOUT_ENDPOINT, + endpoint=self._logout_endpoint, timeout=timeout ) @@ -101,7 +99,7 @@ def is_authenticated(self, timeout: int = httpx.USE_CLIENT_DEFAULT) -> dict: timeout: Seconds before the request times out. """ - response = self.http_get(self.IDENTITY_ENDPOINT, timeout=timeout) + response = self.http_get(self._identity_endpoint, timeout=timeout) return self._handle_identity_response(response) @@ -121,7 +119,7 @@ async def login(self, username: str, password: str, timeout: int = httpx.USE_CLI """ response = await self.http_post( - endpoint=self.LOGIN_ENDPOINT, + endpoint=self._login_endpoint, json={'username': username, 'password': password}, timeout=timeout ) @@ -136,7 +134,7 @@ async def logout(self, timeout: int = httpx.USE_CLIENT_DEFAULT) -> None: """ response = await self.http_post( - endpoint=self.LOGOUT_ENDPOINT, + endpoint=self._logout_endpoint, timeout=timeout ) @@ -156,5 +154,5 @@ async def is_authenticated(self, timeout: int = httpx.USE_CLIENT_DEFAULT) -> dic timeout: Seconds before the request times out. """ - response = await self.http_get(self.IDENTITY_ENDPOINT, timeout=timeout) + response = await self.http_get(self._identity_endpoint, timeout=timeout) return self._handle_identity_response(response) diff --git a/keystone_client/schema.py b/keystone_client/schema.py deleted file mode 100644 index 6c7f0bc..0000000 --- a/keystone_client/schema.py +++ /dev/null @@ -1,40 +0,0 @@ -"""Schema objects used to define available API endpoints.""" - -from dataclasses import dataclass -from os import path - - -class Endpoint(str): - """API endpoint agnostic to the baseAPI URL.""" - - def join_url(self, base: str, *append) -> str: - """Join the endpoint with a base URL. - - This method returns URLs in a format that avoids trailing slash - redirects from the Keystone API. - - Args: - base: The base URL. - *append: Partial paths to append onto the url. - - Returns: - The base URL join with the endpoint. - """ - - url = path.join(base, self) - for partial_path in filter(lambda x: x is not None, append): - url = path.join(url, str(partial_path)) - - return url.rstrip('/') + '/' - - -@dataclass -class Schema: - """Schema defining the complete set of API endpoints.""" - - allocations: Endpoint = Endpoint("allocations/allocations") - clusters: Endpoint = Endpoint("allocations/clusters") - requests: Endpoint = Endpoint("allocations/requests") - teams: Endpoint = Endpoint("users/teams") - memberships: Endpoint = Endpoint("users/memberships") - users: Endpoint = Endpoint("users/users") diff --git a/tests/unit_tests/test_client/test_AsyncKeystoneClient.py b/tests/unit_tests/test_client/test_AsyncKeystoneClient.py index a56a419..3c03ea0 100644 --- a/tests/unit_tests/test_client/test_AsyncKeystoneClient.py +++ b/tests/unit_tests/test_client/test_AsyncKeystoneClient.py @@ -28,7 +28,7 @@ def handler(request: httpx.Request) -> httpx.Response: payload = json.loads(request.content.decode()) self.assertEqual('POST', request.method) - self.assertEqual(AsyncKeystoneClient.LOGIN_ENDPOINT, url_path) + self.assertEqual(AsyncKeystoneClient._login_endpoint, url_path) self.assertEqual({"username": self.username, "password": self.password}, payload) return httpx.Response(200) @@ -66,7 +66,7 @@ def handler(request: httpx.Request) -> httpx.Response: url_path = request.url.path.strip('/') self.assertEqual('POST', request.method) - self.assertEqual(AsyncKeystoneClient.LOGOUT_ENDPOINT, url_path) + self.assertEqual(AsyncKeystoneClient._logout_endpoint, url_path) return httpx.Response(200) @@ -105,7 +105,7 @@ def handler(request: httpx.Request) -> httpx.Response: url_path = request.url.path.strip('/') self.assertEqual('GET', request.method) - self.assertEqual(AsyncKeystoneClient.IDENTITY_ENDPOINT, url_path) + self.assertEqual(AsyncKeystoneClient._identity_endpoint, url_path) return httpx.Response(200, json=expected_data) diff --git a/tests/unit_tests/test_client/test_KeystoneClient.py b/tests/unit_tests/test_client/test_KeystoneClient.py index 649c312..6b8f62c 100644 --- a/tests/unit_tests/test_client/test_KeystoneClient.py +++ b/tests/unit_tests/test_client/test_KeystoneClient.py @@ -28,7 +28,7 @@ def handler(request: httpx.Request) -> httpx.Response: payload = json.loads(request.content.decode()) self.assertEqual('POST', request.method) - self.assertEqual(KeystoneClient.LOGIN_ENDPOINT, url_path) + self.assertEqual(KeystoneClient._login_endpoint, url_path) self.assertEqual({"username": self.username, "password": self.password}, payload) return httpx.Response(200) @@ -66,7 +66,7 @@ def handler(request: httpx.Request) -> httpx.Response: url_path = request.url.path.strip('/') self.assertEqual('POST', request.method) - self.assertEqual(KeystoneClient.LOGOUT_ENDPOINT, url_path) + self.assertEqual(KeystoneClient._logout_endpoint, url_path) return httpx.Response(200) @@ -105,7 +105,7 @@ def handler(request: httpx.Request) -> httpx.Response: url_path = request.url.path.strip('/') self.assertEqual('GET', request.method) - self.assertEqual(KeystoneClient.IDENTITY_ENDPOINT, url_path) + self.assertEqual(KeystoneClient._identity_endpoint, url_path) return httpx.Response(200, json=expected_data) diff --git a/tests/unit_tests/test_schema/__init__.py b/tests/unit_tests/test_schema/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/tests/unit_tests/test_schema/test_Endpoint.py b/tests/unit_tests/test_schema/test_Endpoint.py deleted file mode 100644 index 62c4290..0000000 --- a/tests/unit_tests/test_schema/test_Endpoint.py +++ /dev/null @@ -1,87 +0,0 @@ -"""Test the formatting of API endpoints and URLs.""" - -from unittest import TestCase - -from keystone_client.schema import Endpoint - - -class JoinUrlMethod(TestCase): - """Test the joining of URL parts via the `join_url` method.""" - - def test_with_trailing_slash(self) -> None: - """Verify a base URL with a trailing slash is joined correctly.""" - - endpoint = Endpoint("authentication/new") - base_url = "https://api.example.com/" - expected_result = "https://api.example.com/authentication/new/" - self.assertEqual(expected_result, endpoint.join_url(base_url)) - - def test_without_trailing_slash(self) -> None: - """Verify a base URL with a trailing slash is joined correctly.""" - - endpoint = Endpoint("authentication/new") - base_url = "https://api.example.com" - expected_result = "https://api.example.com/authentication/new/" - self.assertEqual(expected_result, endpoint.join_url(base_url)) - - def test_with_endpoint_trailing_slash(self) -> None: - """Verify an endpoint with a trailing slash is joined correctly.""" - - endpoint = Endpoint("authentication/new/") - base_url = "https://api.example.com" - expected_result = "https://api.example.com/authentication/new/" - self.assertEqual(expected_result, endpoint.join_url(base_url)) - - def test_without_endpoint_trailing_slash(self) -> None: - """Verify an endpoint without a trailing slash is joined correctly.""" - - endpoint = Endpoint("authentication/new") - base_url = "https://api.example.com" - expected_result = "https://api.example.com/authentication/new/" - self.assertEqual(expected_result, endpoint.join_url(base_url)) - - def test_with_append_trailing_slash(self) -> None: - """Verify an append path with a trailing slash is joined correctly.""" - - endpoint = Endpoint("authentication") - base_url = "https://api.example.com" - append_path = "new/" - expected_result = "https://api.example.com/authentication/new/" - self.assertEqual(expected_result, endpoint.join_url(base_url, append_path)) - - def test_without_append_trailing_slash(self) -> None: - """Verify an append path without a trailing slash is joined correctly.""" - - endpoint = Endpoint("authentication") - base_url = "https://api.example.com" - append_path = "new" - expected_result = "https://api.example.com/authentication/new/" - self.assertEqual(expected_result, endpoint.join_url(base_url, append_path)) - - def test_with_mixed_trailing_slash_in_append(self) -> None: - """Verify mixed trailing slashes in append paths are handled correctly.""" - - endpoint = Endpoint("authentication") - base_url = "https://api.example.com" - append_path1 = "new/" - append_path2 = "extra" - expected_result = "https://api.example.com/authentication/new/extra/" - self.assertEqual(expected_result, endpoint.join_url(base_url, append_path1, append_path2)) - - def test_int_append_argument(self) -> None: - """Verify an integer append argument is joined correctly.""" - - endpoint = Endpoint("authentication") - base_url = "https://api.example.com" - append_path = 123 - expected_result = "https://api.example.com/authentication/123/" - self.assertEqual(expected_result, endpoint.join_url(base_url, str(append_path))) - - def test_none_append_argument(self) -> None: - """Verify a `None` append argument is ignored in joining.""" - - endpoint = Endpoint("authentication") - base_url = "https://api.example.com" - append_path = None - expected_result = "https://api.example.com/authentication/" - self.assertEqual(expected_result, endpoint.join_url(base_url, append_path)) From 214a81b6d638cddbc64e0cfeb4a2df5da6db3378 Mon Sep 17 00:00:00 2001 From: Daniel Perrefort Date: Thu, 23 Jul 2026 16:38:59 -0400 Subject: [PATCH 2/2] Updates auth attribute names --- keystone_client/client.py | 18 +++++++++--------- .../test_client/test_AsyncKeystoneClient.py | 6 +++--- .../test_client/test_KeystoneClient.py | 6 +++--- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/keystone_client/client.py b/keystone_client/client.py index 9c2d05b..84802e3 100644 --- a/keystone_client/client.py +++ b/keystone_client/client.py @@ -16,9 +16,9 @@ class ClientBase(abc.ABC): """Base client class with shared application constants and helpers.""" - _login_endpoint = 'authentication/login' - _logout_endpoint = 'authentication/logout' - _identity_endpoint = 'authentication/whoami' + LOGIN_ENDPOINT = 'authentication/login' + LOGOUT_ENDPOINT = 'authentication/logout' + IDENTITY_ENDPOINT = 'authentication/whoami' @abc.abstractmethod def login(self, username: str, password: str, timeout: int) -> None: @@ -66,7 +66,7 @@ def login(self, username: str, password: str, timeout: int = httpx.USE_CLIENT_DE """ self.http_post( - endpoint=self._login_endpoint, + endpoint=self.LOGIN_ENDPOINT, json={'username': username, 'password': password}, timeout=timeout ).raise_for_status() @@ -79,7 +79,7 @@ def logout(self, timeout: int = httpx.USE_CLIENT_DEFAULT) -> None: """ response = self.http_post( - endpoint=self._logout_endpoint, + endpoint=self.LOGOUT_ENDPOINT, timeout=timeout ) @@ -99,7 +99,7 @@ def is_authenticated(self, timeout: int = httpx.USE_CLIENT_DEFAULT) -> dict: timeout: Seconds before the request times out. """ - response = self.http_get(self._identity_endpoint, timeout=timeout) + response = self.http_get(self.IDENTITY_ENDPOINT, timeout=timeout) return self._handle_identity_response(response) @@ -119,7 +119,7 @@ async def login(self, username: str, password: str, timeout: int = httpx.USE_CLI """ response = await self.http_post( - endpoint=self._login_endpoint, + endpoint=self.LOGIN_ENDPOINT, json={'username': username, 'password': password}, timeout=timeout ) @@ -134,7 +134,7 @@ async def logout(self, timeout: int = httpx.USE_CLIENT_DEFAULT) -> None: """ response = await self.http_post( - endpoint=self._logout_endpoint, + endpoint=self.LOGOUT_ENDPOINT, timeout=timeout ) @@ -154,5 +154,5 @@ async def is_authenticated(self, timeout: int = httpx.USE_CLIENT_DEFAULT) -> dic timeout: Seconds before the request times out. """ - response = await self.http_get(self._identity_endpoint, timeout=timeout) + response = await self.http_get(self.IDENTITY_ENDPOINT, timeout=timeout) return self._handle_identity_response(response) diff --git a/tests/unit_tests/test_client/test_AsyncKeystoneClient.py b/tests/unit_tests/test_client/test_AsyncKeystoneClient.py index 3c03ea0..a56a419 100644 --- a/tests/unit_tests/test_client/test_AsyncKeystoneClient.py +++ b/tests/unit_tests/test_client/test_AsyncKeystoneClient.py @@ -28,7 +28,7 @@ def handler(request: httpx.Request) -> httpx.Response: payload = json.loads(request.content.decode()) self.assertEqual('POST', request.method) - self.assertEqual(AsyncKeystoneClient._login_endpoint, url_path) + self.assertEqual(AsyncKeystoneClient.LOGIN_ENDPOINT, url_path) self.assertEqual({"username": self.username, "password": self.password}, payload) return httpx.Response(200) @@ -66,7 +66,7 @@ def handler(request: httpx.Request) -> httpx.Response: url_path = request.url.path.strip('/') self.assertEqual('POST', request.method) - self.assertEqual(AsyncKeystoneClient._logout_endpoint, url_path) + self.assertEqual(AsyncKeystoneClient.LOGOUT_ENDPOINT, url_path) return httpx.Response(200) @@ -105,7 +105,7 @@ def handler(request: httpx.Request) -> httpx.Response: url_path = request.url.path.strip('/') self.assertEqual('GET', request.method) - self.assertEqual(AsyncKeystoneClient._identity_endpoint, url_path) + self.assertEqual(AsyncKeystoneClient.IDENTITY_ENDPOINT, url_path) return httpx.Response(200, json=expected_data) diff --git a/tests/unit_tests/test_client/test_KeystoneClient.py b/tests/unit_tests/test_client/test_KeystoneClient.py index 6b8f62c..649c312 100644 --- a/tests/unit_tests/test_client/test_KeystoneClient.py +++ b/tests/unit_tests/test_client/test_KeystoneClient.py @@ -28,7 +28,7 @@ def handler(request: httpx.Request) -> httpx.Response: payload = json.loads(request.content.decode()) self.assertEqual('POST', request.method) - self.assertEqual(KeystoneClient._login_endpoint, url_path) + self.assertEqual(KeystoneClient.LOGIN_ENDPOINT, url_path) self.assertEqual({"username": self.username, "password": self.password}, payload) return httpx.Response(200) @@ -66,7 +66,7 @@ def handler(request: httpx.Request) -> httpx.Response: url_path = request.url.path.strip('/') self.assertEqual('POST', request.method) - self.assertEqual(KeystoneClient._logout_endpoint, url_path) + self.assertEqual(KeystoneClient.LOGOUT_ENDPOINT, url_path) return httpx.Response(200) @@ -105,7 +105,7 @@ def handler(request: httpx.Request) -> httpx.Response: url_path = request.url.path.strip('/') self.assertEqual('GET', request.method) - self.assertEqual(KeystoneClient._identity_endpoint, url_path) + self.assertEqual(KeystoneClient.IDENTITY_ENDPOINT, url_path) return httpx.Response(200, json=expected_data)