diff --git a/hcloud/actions/client.py b/hcloud/actions/client.py index 7414726..3105b3c 100644 --- a/hcloud/actions/client.py +++ b/hcloud/actions/client.py @@ -101,4 +101,4 @@ def get_all( Specify how the results are sorted. Choices: `id` `command` `status` `progress` `started` `finished` . You can add one of ":asc", ":desc" to modify sort order. ( ":asc" is default) :return: List[:class:`BoundAction `] """ - return super().get_all(status=status, sort=sort) + return self._iter_pages(self.get_list, status=status, sort=sort) diff --git a/hcloud/certificates/client.py b/hcloud/certificates/client.py index 4478e15..1067dfa 100644 --- a/hcloud/certificates/client.py +++ b/hcloud/certificates/client.py @@ -3,7 +3,7 @@ from typing import TYPE_CHECKING, Any, NamedTuple from ..actions import ActionsPageResult, BoundAction -from ..core import BoundModelBase, ClientEntityBase, GetEntityByNameMixin, Meta +from ..core import BoundModelBase, ClientEntityBase, Meta from .domain import ( Certificate, CreateManagedCertificateResponse, @@ -103,7 +103,7 @@ class CertificatesPageResult(NamedTuple): meta: Meta | None -class CertificatesClient(ClientEntityBase, GetEntityByNameMixin): +class CertificatesClient(ClientEntityBase): _client: Client def get_by_id(self, id: int) -> BoundCertificate: @@ -171,7 +171,7 @@ def get_all( Can be used to filter certificates by labels. The response will only contain certificates matching the label selector. :return: List[:class:`BoundCertificate `] """ - return super().get_all(name=name, label_selector=label_selector) + return self._iter_pages(self.get_list, name=name, label_selector=label_selector) def get_by_name(self, name: str) -> BoundCertificate | None: """Get certificate by name @@ -180,7 +180,7 @@ def get_by_name(self, name: str) -> BoundCertificate | None: Used to get certificate by name. :return: :class:`BoundCertificate ` """ - return super().get_by_name(name) + return self._get_first_by(name=name) def create( self, @@ -338,7 +338,12 @@ def get_actions( Specify how the results are sorted. Choices: `id` `id:asc` `id:desc` `command` `command:asc` `command:desc` `status` `status:asc` `status:desc` `progress` `progress:asc` `progress:desc` `started` `started:asc` `started:desc` `finished` `finished:asc` `finished:desc` :return: List[:class:`BoundAction `] """ - return super().get_actions(certificate, status=status, sort=sort) + return self._iter_pages( + self.get_actions_list, + certificate, + status=status, + sort=sort, + ) def retry_issuance( self, diff --git a/hcloud/core/__init__.py b/hcloud/core/__init__.py index 2e279f7..4e17dac 100644 --- a/hcloud/core/__init__.py +++ b/hcloud/core/__init__.py @@ -1,4 +1,4 @@ from __future__ import annotations -from .client import BoundModelBase, ClientEntityBase, GetEntityByNameMixin # noqa: F401 +from .client import BoundModelBase, ClientEntityBase # noqa: F401 from .domain import BaseDomain, DomainIdentityMixin, Meta, Pagination # noqa: F401 diff --git a/hcloud/core/client.py b/hcloud/core/client.py index 22b0f0b..16c37e5 100644 --- a/hcloud/core/client.py +++ b/hcloud/core/client.py @@ -4,7 +4,6 @@ if TYPE_CHECKING: from .._client import Client - from ..actions import BoundAction class ClientEntityBase: @@ -19,7 +18,7 @@ def __init__(self, client: Client): """ self._client = client - def _get_all( # type: ignore[no-untyped-def] + def _iter_pages( # type: ignore[no-untyped-def] self, list_function: Callable, *args, @@ -32,42 +31,21 @@ def _get_all( # type: ignore[no-untyped-def] # The *PageResult tuples MUST have the following structure # `(result: List[Bound*], meta: Meta)` result, meta = list_function( - page=page, per_page=self.max_per_page, *args, **kwargs + *args, page=page, per_page=self.max_per_page, **kwargs ) if result: results.extend(result) - if ( - meta - and meta.pagination - and meta.pagination.next_page - and meta.pagination.next_page - ): + if meta and meta.pagination and meta.pagination.next_page: page = meta.pagination.next_page else: page = 0 return results - def get_all(self, *args, **kwargs) -> list: # type: ignore[no-untyped-def] + def _get_first_by(self, **kwargs): # type: ignore[no-untyped-def] assert hasattr(self, "get_list") - return self._get_all(self.get_list, *args, **kwargs) - - def get_actions(self, *args, **kwargs) -> list[BoundAction]: # type: ignore[no-untyped-def] - if not hasattr(self, "get_actions_list"): - raise ValueError("this endpoint does not support get_actions method") - - return self._get_all(self.get_actions_list, *args, **kwargs) - - -class GetEntityByNameMixin: - """ - Use as a mixin for ClientEntityBase classes - """ - - def get_by_name(self, name: str): # type: ignore[no-untyped-def] - assert hasattr(self, "get_list") - entities, _ = self.get_list(name=name) + entities, _ = self.get_list(**kwargs) return entities[0] if entities else None diff --git a/hcloud/datacenters/client.py b/hcloud/datacenters/client.py index 2f874c5..ab5aa5e 100644 --- a/hcloud/datacenters/client.py +++ b/hcloud/datacenters/client.py @@ -2,7 +2,7 @@ from typing import TYPE_CHECKING, Any, NamedTuple -from ..core import BoundModelBase, ClientEntityBase, GetEntityByNameMixin, Meta +from ..core import BoundModelBase, ClientEntityBase, Meta from ..locations import BoundLocation from ..server_types import BoundServerType from .domain import Datacenter, DatacenterServerTypes @@ -55,7 +55,7 @@ class DatacentersPageResult(NamedTuple): meta: Meta | None -class DatacentersClient(ClientEntityBase, GetEntityByNameMixin): +class DatacentersClient(ClientEntityBase): _client: Client def get_by_id(self, id: int) -> BoundDatacenter: @@ -109,7 +109,7 @@ def get_all(self, name: str | None = None) -> list[BoundDatacenter]: Can be used to filter datacenters by their name. :return: List[:class:`BoundDatacenter `] """ - return super().get_all(name=name) + return self._iter_pages(self.get_list, name=name) def get_by_name(self, name: str) -> BoundDatacenter | None: """Get datacenter by name @@ -118,4 +118,4 @@ def get_by_name(self, name: str) -> BoundDatacenter | None: Used to get datacenter by name. :return: :class:`BoundDatacenter ` """ - return super().get_by_name(name) + return self._get_first_by(name=name) diff --git a/hcloud/firewalls/client.py b/hcloud/firewalls/client.py index bf3cb52..8696080 100644 --- a/hcloud/firewalls/client.py +++ b/hcloud/firewalls/client.py @@ -3,7 +3,7 @@ from typing import TYPE_CHECKING, Any, NamedTuple from ..actions import ActionsPageResult, BoundAction -from ..core import BoundModelBase, ClientEntityBase, GetEntityByNameMixin, Meta +from ..core import BoundModelBase, ClientEntityBase, Meta from .domain import ( CreateFirewallResponse, Firewall, @@ -158,7 +158,7 @@ class FirewallsPageResult(NamedTuple): meta: Meta | None -class FirewallsClient(ClientEntityBase, GetEntityByNameMixin): +class FirewallsClient(ClientEntityBase): _client: Client def get_actions_list( @@ -218,7 +218,12 @@ def get_actions( :return: List[:class:`BoundAction `] """ - return super().get_actions(firewall, status=status, sort=sort) + return self._iter_pages( + self.get_actions_list, + firewall, + status=status, + sort=sort, + ) def get_by_id(self, id: int) -> BoundFirewall: """Returns a specific Firewall object. @@ -287,7 +292,12 @@ def get_all( Choices: id name created (You can add one of ":asc", ":desc" to modify sort order. ( ":asc" is default)) :return: List[:class:`BoundFirewall `] """ - return super().get_all(label_selector=label_selector, name=name, sort=sort) + return self._iter_pages( + self.get_list, + label_selector=label_selector, + name=name, + sort=sort, + ) def get_by_name(self, name: str) -> BoundFirewall | None: """Get Firewall by name @@ -296,7 +306,7 @@ def get_by_name(self, name: str) -> BoundFirewall | None: Used to get Firewall by name. :return: :class:`BoundFirewall ` """ - return super().get_by_name(name) + return self._get_first_by(name=name) def create( self, diff --git a/hcloud/floating_ips/client.py b/hcloud/floating_ips/client.py index 0c2aa96..58d7fa3 100644 --- a/hcloud/floating_ips/client.py +++ b/hcloud/floating_ips/client.py @@ -3,7 +3,7 @@ from typing import TYPE_CHECKING, Any, NamedTuple from ..actions import ActionsPageResult, BoundAction -from ..core import BoundModelBase, ClientEntityBase, GetEntityByNameMixin, Meta +from ..core import BoundModelBase, ClientEntityBase, Meta from ..locations import BoundLocation from .domain import CreateFloatingIPResponse, FloatingIP @@ -139,7 +139,7 @@ class FloatingIPsPageResult(NamedTuple): meta: Meta | None -class FloatingIPsClient(ClientEntityBase, GetEntityByNameMixin): +class FloatingIPsClient(ClientEntityBase): _client: Client def get_actions_list( @@ -199,7 +199,12 @@ def get_actions( :return: List[:class:`BoundAction `] """ - return super().get_actions(floating_ip, status=status, sort=sort) + return self._iter_pages( + self.get_actions_list, + floating_ip, + status=status, + sort=sort, + ) def get_by_id(self, id: int) -> BoundFloatingIP: """Returns a specific Floating IP object. @@ -263,7 +268,7 @@ def get_all( Can be used to filter networks by their name. :return: List[:class:`BoundFloatingIP `] """ - return super().get_all(label_selector=label_selector, name=name) + return self._iter_pages(self.get_list, label_selector=label_selector, name=name) def get_by_name(self, name: str) -> BoundFloatingIP | None: """Get Floating IP by name @@ -272,7 +277,7 @@ def get_by_name(self, name: str) -> BoundFloatingIP | None: Used to get Floating IP by name. :return: :class:`BoundFloatingIP ` """ - return super().get_by_name(name) + return self._get_first_by(name=name) def create( self, diff --git a/hcloud/images/client.py b/hcloud/images/client.py index 4098672..a7dbef7 100644 --- a/hcloud/images/client.py +++ b/hcloud/images/client.py @@ -3,7 +3,7 @@ from typing import TYPE_CHECKING, Any, NamedTuple from ..actions import ActionsPageResult, BoundAction -from ..core import BoundModelBase, ClientEntityBase, GetEntityByNameMixin, Meta +from ..core import BoundModelBase, ClientEntityBase, Meta from .domain import Image if TYPE_CHECKING: @@ -110,7 +110,7 @@ class ImagesPageResult(NamedTuple): meta: Meta | None -class ImagesClient(ClientEntityBase, GetEntityByNameMixin): +class ImagesClient(ClientEntityBase): _client: Client def get_actions_list( @@ -169,7 +169,12 @@ def get_actions( Specify how the results are sorted. Choices: `id` `command` `status` `progress` `started` `finished` . You can add one of ":asc", ":desc" to modify sort order. ( ":asc" is default) :return: List[:class:`BoundAction `] """ - return super().get_actions(image, sort=sort, status=status) + return self._iter_pages( + self.get_actions_list, + image, + sort=sort, + status=status, + ) def get_by_id(self, id: int) -> BoundImage: """Get a specific Image @@ -274,7 +279,8 @@ def get_all( Include deprecated images in the response. Default: False :return: List[:class:`BoundImage `] """ - return super().get_all( + return self._iter_pages( + self.get_list, name=name, label_selector=label_selector, bound_to=bound_to, @@ -294,7 +300,7 @@ def get_by_name(self, name: str) -> BoundImage | None: Used to get image by name. :return: :class:`BoundImage ` """ - return super().get_by_name(name) + return self._get_first_by(name=name) def get_by_name_and_architecture( self, @@ -309,9 +315,7 @@ def get_by_name_and_architecture( Used to identify the image. :return: :class:`BoundImage ` """ - entities, _ = self.get_list(name=name, architecture=[architecture]) - entity = entities[0] if entities else None - return entity + return self._get_first_by(name=name, architecture=[architecture]) def update( self, diff --git a/hcloud/isos/client.py b/hcloud/isos/client.py index c171673..1ab5fc9 100644 --- a/hcloud/isos/client.py +++ b/hcloud/isos/client.py @@ -3,7 +3,7 @@ from typing import TYPE_CHECKING, Any, NamedTuple from warnings import warn -from ..core import BoundModelBase, ClientEntityBase, GetEntityByNameMixin, Meta +from ..core import BoundModelBase, ClientEntityBase, Meta from .domain import Iso if TYPE_CHECKING: @@ -21,7 +21,7 @@ class IsosPageResult(NamedTuple): meta: Meta | None -class IsosClient(ClientEntityBase, GetEntityByNameMixin): +class IsosClient(ClientEntityBase): _client: Client def get_by_id(self, id: int) -> BoundIso: @@ -111,7 +111,8 @@ def get_all( ) include_architecture_wildcard = include_wildcard_architecture - return super().get_all( + return self._iter_pages( + self.get_list, name=name, architecture=architecture, include_architecture_wildcard=include_architecture_wildcard, @@ -124,4 +125,4 @@ def get_by_name(self, name: str) -> BoundIso | None: Used to get iso by name. :return: :class:`BoundIso ` """ - return super().get_by_name(name) + return self._get_first_by(name=name) diff --git a/hcloud/load_balancer_types/client.py b/hcloud/load_balancer_types/client.py index 6387e62..fa91c01 100644 --- a/hcloud/load_balancer_types/client.py +++ b/hcloud/load_balancer_types/client.py @@ -2,7 +2,7 @@ from typing import TYPE_CHECKING, Any, NamedTuple -from ..core import BoundModelBase, ClientEntityBase, GetEntityByNameMixin, Meta +from ..core import BoundModelBase, ClientEntityBase, Meta from .domain import LoadBalancerType if TYPE_CHECKING: @@ -20,7 +20,7 @@ class LoadBalancerTypesPageResult(NamedTuple): meta: Meta | None -class LoadBalancerTypesClient(ClientEntityBase, GetEntityByNameMixin): +class LoadBalancerTypesClient(ClientEntityBase): _client: Client def get_by_id(self, id: int) -> BoundLoadBalancerType: @@ -77,7 +77,7 @@ def get_all(self, name: str | None = None) -> list[BoundLoadBalancerType]: Can be used to filter Load Balancer type by their name. :return: List[:class:`BoundLoadBalancerType `] """ - return super().get_all(name=name) + return self._iter_pages(self.get_list, name=name) def get_by_name(self, name: str) -> BoundLoadBalancerType | None: """Get Load Balancer type by name @@ -86,4 +86,4 @@ def get_by_name(self, name: str) -> BoundLoadBalancerType | None: Used to get Load Balancer type by name. :return: :class:`BoundLoadBalancerType ` """ - return super().get_by_name(name) + return self._get_first_by(name=name) diff --git a/hcloud/load_balancers/client.py b/hcloud/load_balancers/client.py index b0c6877..e228cc2 100644 --- a/hcloud/load_balancers/client.py +++ b/hcloud/load_balancers/client.py @@ -4,7 +4,7 @@ from ..actions import ActionsPageResult, BoundAction from ..certificates import BoundCertificate -from ..core import BoundModelBase, ClientEntityBase, GetEntityByNameMixin, Meta +from ..core import BoundModelBase, ClientEntityBase, Meta from ..load_balancer_types import BoundLoadBalancerType from ..locations import BoundLocation from ..networks import BoundNetwork @@ -328,7 +328,7 @@ class LoadBalancersPageResult(NamedTuple): meta: Meta | None -class LoadBalancersClient(ClientEntityBase, GetEntityByNameMixin): +class LoadBalancersClient(ClientEntityBase): _client: Client def get_by_id(self, id: int) -> BoundLoadBalancer: @@ -395,7 +395,7 @@ def get_all( Can be used to filter Load Balancers by labels. The response will only contain Load Balancers matching the label selector. :return: List[:class:`BoundLoadBalancer `] """ - return super().get_all(name=name, label_selector=label_selector) + return self._iter_pages(self.get_list, name=name, label_selector=label_selector) def get_by_name(self, name: str) -> BoundLoadBalancer | None: """Get Load Balancer by name @@ -404,7 +404,7 @@ def get_by_name(self, name: str) -> BoundLoadBalancer | None: Used to get Load Balancer by name. :return: :class:`BoundLoadBalancer ` """ - return super().get_by_name(name) + return self._get_first_by(name=name) def create( self, @@ -567,7 +567,12 @@ def get_actions( Specify how the results are sorted. Choices: `id` `id:asc` `id:desc` `command` `command:asc` `command:desc` `status` `status:asc` `status:desc` `progress` `progress:asc` `progress:desc` `started` `started:asc` `started:desc` `finished` `finished:asc` `finished:desc` :return: List[:class:`BoundAction `] """ - return super().get_actions(load_balancer, status=status, sort=sort) + return self._iter_pages( + self.get_actions_list, + load_balancer, + status=status, + sort=sort, + ) def add_service( self, diff --git a/hcloud/locations/client.py b/hcloud/locations/client.py index 5ad0d1f..2e2b6b4 100644 --- a/hcloud/locations/client.py +++ b/hcloud/locations/client.py @@ -2,7 +2,7 @@ from typing import TYPE_CHECKING, Any, NamedTuple -from ..core import BoundModelBase, ClientEntityBase, GetEntityByNameMixin, Meta +from ..core import BoundModelBase, ClientEntityBase, Meta from .domain import Location if TYPE_CHECKING: @@ -20,7 +20,7 @@ class LocationsPageResult(NamedTuple): meta: Meta | None -class LocationsClient(ClientEntityBase, GetEntityByNameMixin): +class LocationsClient(ClientEntityBase): _client: Client def get_by_id(self, id: int) -> BoundLocation: @@ -70,7 +70,7 @@ def get_all(self, name: str | None = None) -> list[BoundLocation]: Can be used to filter locations by their name. :return: List[:class:`BoundLocation `] """ - return super().get_all(name=name) + return self._iter_pages(self.get_list, name=name) def get_by_name(self, name: str) -> BoundLocation | None: """Get location by name @@ -79,4 +79,4 @@ def get_by_name(self, name: str) -> BoundLocation | None: Used to get location by name. :return: :class:`BoundLocation ` """ - return super().get_by_name(name) + return self._get_first_by(name=name) diff --git a/hcloud/networks/client.py b/hcloud/networks/client.py index 4b049e2..416a040 100644 --- a/hcloud/networks/client.py +++ b/hcloud/networks/client.py @@ -3,7 +3,7 @@ from typing import TYPE_CHECKING, Any, NamedTuple from ..actions import ActionsPageResult, BoundAction -from ..core import BoundModelBase, ClientEntityBase, GetEntityByNameMixin, Meta +from ..core import BoundModelBase, ClientEntityBase, Meta from .domain import Network, NetworkRoute, NetworkSubnet if TYPE_CHECKING: @@ -165,7 +165,7 @@ class NetworksPageResult(NamedTuple): meta: Meta | None -class NetworksClient(ClientEntityBase, GetEntityByNameMixin): +class NetworksClient(ClientEntityBase): _client: Client def get_by_id(self, id: int) -> BoundNetwork: @@ -226,7 +226,7 @@ def get_all( Can be used to filter networks by labels. The response will only contain networks matching the label selector. :return: List[:class:`BoundNetwork `] """ - return super().get_all(name=name, label_selector=label_selector) + return self._iter_pages(self.get_list, name=name, label_selector=label_selector) def get_by_name(self, name: str) -> BoundNetwork | None: """Get network by name @@ -235,7 +235,7 @@ def get_by_name(self, name: str) -> BoundNetwork | None: Used to get network by name. :return: :class:`BoundNetwork ` """ - return super().get_by_name(name) + return self._get_first_by(name=name) def create( self, @@ -396,7 +396,12 @@ def get_actions( Specify how the results are sorted. Choices: `id` `id:asc` `id:desc` `command` `command:asc` `command:desc` `status` `status:asc` `status:desc` `progress` `progress:asc` `progress:desc` `started` `started:asc` `started:desc` `finished` `finished:asc` `finished:desc` :return: List[:class:`BoundAction `] """ - return super().get_actions(network, status=status, sort=sort) + return self._iter_pages( + self.get_actions_list, + network, + status=status, + sort=sort, + ) def add_subnet( self, diff --git a/hcloud/placement_groups/client.py b/hcloud/placement_groups/client.py index 517fba5..b551904 100644 --- a/hcloud/placement_groups/client.py +++ b/hcloud/placement_groups/client.py @@ -3,7 +3,7 @@ from typing import TYPE_CHECKING, Any, NamedTuple from ..actions import BoundAction -from ..core import BoundModelBase, ClientEntityBase, GetEntityByNameMixin, Meta +from ..core import BoundModelBase, ClientEntityBase, Meta from .domain import CreatePlacementGroupResponse, PlacementGroup if TYPE_CHECKING: @@ -43,7 +43,7 @@ class PlacementGroupsPageResult(NamedTuple): meta: Meta | None -class PlacementGroupsClient(ClientEntityBase, GetEntityByNameMixin): +class PlacementGroupsClient(ClientEntityBase): _client: Client def get_by_id(self, id: int) -> BoundPlacementGroup: @@ -122,7 +122,12 @@ def get_all( Choices: id name created (You can add one of ":asc", ":desc" to modify sort order. ( ":asc" is default)) :return: List[:class:`BoundPlacementGroup `] """ - return super().get_all(label_selector=label_selector, name=name, sort=sort) + return self._iter_pages( + self.get_list, + label_selector=label_selector, + name=name, + sort=sort, + ) def get_by_name(self, name: str) -> BoundPlacementGroup | None: """Get Placement Group by name @@ -131,7 +136,7 @@ def get_by_name(self, name: str) -> BoundPlacementGroup | None: Used to get Placement Group by name :return: class:`BoundPlacementGroup ` """ - return super().get_by_name(name) + return self._get_first_by(name=name) def create( self, diff --git a/hcloud/primary_ips/client.py b/hcloud/primary_ips/client.py index 05c4e60..a54de6d 100644 --- a/hcloud/primary_ips/client.py +++ b/hcloud/primary_ips/client.py @@ -3,7 +3,7 @@ from typing import TYPE_CHECKING, Any, NamedTuple from ..actions import BoundAction -from ..core import BoundModelBase, ClientEntityBase, GetEntityByNameMixin, Meta +from ..core import BoundModelBase, ClientEntityBase, Meta from .domain import CreatePrimaryIPResponse, PrimaryIP if TYPE_CHECKING: @@ -96,7 +96,7 @@ class PrimaryIPsPageResult(NamedTuple): meta: Meta | None -class PrimaryIPsClient(ClientEntityBase, GetEntityByNameMixin): +class PrimaryIPsClient(ClientEntityBase): _client: Client def get_by_id(self, id: int) -> BoundPrimaryIP: @@ -164,7 +164,7 @@ def get_all( Can be used to filter networks by their name. :return: List[:class:`BoundPrimaryIP `] """ - return super().get_all(label_selector=label_selector, name=name) + return self._iter_pages(self.get_list, label_selector=label_selector, name=name) def get_by_name(self, name: str) -> BoundPrimaryIP | None: """Get Primary IP by name @@ -173,7 +173,7 @@ def get_by_name(self, name: str) -> BoundPrimaryIP | None: Used to get Primary IP by name. :return: :class:`BoundPrimaryIP ` """ - return super().get_by_name(name) + return self._get_first_by(name=name) def create( self, diff --git a/hcloud/server_types/client.py b/hcloud/server_types/client.py index 20b2c9f..12cf34a 100644 --- a/hcloud/server_types/client.py +++ b/hcloud/server_types/client.py @@ -2,7 +2,7 @@ from typing import TYPE_CHECKING, Any, NamedTuple -from ..core import BoundModelBase, ClientEntityBase, GetEntityByNameMixin, Meta +from ..core import BoundModelBase, ClientEntityBase, Meta from .domain import ServerType if TYPE_CHECKING: @@ -20,7 +20,7 @@ class ServerTypesPageResult(NamedTuple): meta: Meta | None -class ServerTypesClient(ClientEntityBase, GetEntityByNameMixin): +class ServerTypesClient(ClientEntityBase): _client: Client def get_by_id(self, id: int) -> BoundServerType: @@ -72,7 +72,7 @@ def get_all(self, name: str | None = None) -> list[BoundServerType]: Can be used to filter server type by their name. :return: List[:class:`BoundServerType `] """ - return super().get_all(name=name) + return self._iter_pages(self.get_list, name=name) def get_by_name(self, name: str) -> BoundServerType | None: """Get Server type by name @@ -81,4 +81,4 @@ def get_by_name(self, name: str) -> BoundServerType | None: Used to get Server type by name. :return: :class:`BoundServerType ` """ - return super().get_by_name(name) + return self._get_first_by(name=name) diff --git a/hcloud/servers/client.py b/hcloud/servers/client.py index 0108309..c128288 100644 --- a/hcloud/servers/client.py +++ b/hcloud/servers/client.py @@ -3,7 +3,7 @@ from typing import TYPE_CHECKING, Any, NamedTuple from ..actions import ActionsPageResult, BoundAction -from ..core import BoundModelBase, ClientEntityBase, GetEntityByNameMixin, Meta +from ..core import BoundModelBase, ClientEntityBase, Meta from ..datacenters import BoundDatacenter from ..firewalls import BoundFirewall from ..floating_ips import BoundFloatingIP @@ -445,7 +445,7 @@ class ServersPageResult(NamedTuple): meta: Meta | None -class ServersClient(ClientEntityBase, GetEntityByNameMixin): +class ServersClient(ClientEntityBase): _client: Client def get_by_id(self, id: int) -> BoundServer: @@ -514,7 +514,12 @@ def get_all( Can be used to filter servers by their status. The response will only contain servers matching the status. :return: List[:class:`BoundServer `] """ - return super().get_all(name=name, label_selector=label_selector, status=status) + return self._iter_pages( + self.get_list, + name=name, + label_selector=label_selector, + status=status, + ) def get_by_name(self, name: str) -> BoundServer | None: """Get server by name @@ -523,7 +528,7 @@ def get_by_name(self, name: str) -> BoundServer | None: Used to get server by name. :return: :class:`BoundServer ` """ - return super().get_by_name(name) + return self._get_first_by(name=name) def create( self, @@ -682,7 +687,12 @@ def get_actions( Specify how the results are sorted. Choices: `id` `id:asc` `id:desc` `command` `command:asc` `command:desc` `status` `status:asc` `status:desc` `progress` `progress:asc` `progress:desc` `started` `started:asc` `started:desc` `finished` `finished:asc` `finished:desc` :return: List[:class:`BoundAction `] """ - return super().get_actions(server, status=status, sort=sort) + return self._iter_pages( + self.get_actions_list, + server, + status=status, + sort=sort, + ) def update( self, diff --git a/hcloud/ssh_keys/client.py b/hcloud/ssh_keys/client.py index 797619f..8a86da8 100644 --- a/hcloud/ssh_keys/client.py +++ b/hcloud/ssh_keys/client.py @@ -2,7 +2,7 @@ from typing import TYPE_CHECKING, Any, NamedTuple -from ..core import BoundModelBase, ClientEntityBase, GetEntityByNameMixin, Meta +from ..core import BoundModelBase, ClientEntityBase, Meta from .domain import SSHKey if TYPE_CHECKING: @@ -41,7 +41,7 @@ class SSHKeysPageResult(NamedTuple): meta: Meta | None -class SSHKeysClient(ClientEntityBase, GetEntityByNameMixin): +class SSHKeysClient(ClientEntityBase): _client: Client def get_by_id(self, id: int) -> BoundSSHKey: @@ -110,8 +110,11 @@ def get_all( Can be used to filter SSH keys by labels. The response will only contain SSH keys matching the label selector. :return: List[:class:`BoundSSHKey `] """ - return super().get_all( - name=name, fingerprint=fingerprint, label_selector=label_selector + return self._iter_pages( + self.get_list, + name=name, + fingerprint=fingerprint, + label_selector=label_selector, ) def get_by_name(self, name: str) -> BoundSSHKey | None: @@ -121,7 +124,7 @@ def get_by_name(self, name: str) -> BoundSSHKey | None: Used to get ssh key by name. :return: :class:`BoundSSHKey ` """ - return super().get_by_name(name) + return self._get_first_by(name=name) def get_by_fingerprint(self, fingerprint: str) -> BoundSSHKey | None: """Get ssh key by fingerprint @@ -130,9 +133,7 @@ def get_by_fingerprint(self, fingerprint: str) -> BoundSSHKey | None: Used to get ssh key by fingerprint. :return: :class:`BoundSSHKey ` """ - response = self.get_list(fingerprint=fingerprint) - sshkeys = response.ssh_keys - return sshkeys[0] if sshkeys else None + return self._get_first_by(fingerprint=fingerprint) def create( self, diff --git a/hcloud/volumes/client.py b/hcloud/volumes/client.py index a00a78b..60b1d9b 100644 --- a/hcloud/volumes/client.py +++ b/hcloud/volumes/client.py @@ -3,7 +3,7 @@ from typing import TYPE_CHECKING, Any, NamedTuple from ..actions import ActionsPageResult, BoundAction -from ..core import BoundModelBase, ClientEntityBase, GetEntityByNameMixin, Meta +from ..core import BoundModelBase, ClientEntityBase, Meta from ..locations import BoundLocation from .domain import CreateVolumeResponse, Volume @@ -134,7 +134,7 @@ class VolumesPageResult(NamedTuple): meta: Meta | None -class VolumesClient(ClientEntityBase, GetEntityByNameMixin): +class VolumesClient(ClientEntityBase): _client: Client def get_by_id(self, id: int) -> BoundVolume: @@ -199,7 +199,11 @@ def get_all( Can be used to filter volumes by their status. The response will only contain volumes matching the status. :return: List[:class:`BoundVolume `] """ - return super().get_all(label_selector=label_selector, status=status) + return self._iter_pages( + self.get_list, + label_selector=label_selector, + status=status, + ) def get_by_name(self, name: str) -> BoundVolume | None: """Get volume by name @@ -208,7 +212,7 @@ def get_by_name(self, name: str) -> BoundVolume | None: Used to get volume by name. :return: :class:`BoundVolume ` """ - return super().get_by_name(name) + return self._get_first_by(name=name) def create( self, @@ -325,7 +329,12 @@ def get_actions( Specify how the results are sorted. Choices: `id` `id:asc` `id:desc` `command` `command:asc` `command:desc` `status` `status:asc` `status:desc` `progress` `progress:asc` `progress:desc` `started` `started:asc` `started:desc` `finished` `finished:asc` `finished:desc` :return: List[:class:`BoundAction `] """ - return super().get_actions(volume, status=status, sort=sort) + return self._iter_pages( + self.get_actions_list, + volume, + status=status, + sort=sort, + ) def update( self, diff --git a/tests/unit/core/test_client.py b/tests/unit/core/test_client.py index 1d4b2da..b8e424f 100644 --- a/tests/unit/core/test_client.py +++ b/tests/unit/core/test_client.py @@ -6,13 +6,7 @@ import pytest from hcloud.actions import ActionsPageResult -from hcloud.core import ( - BaseDomain, - BoundModelBase, - ClientEntityBase, - GetEntityByNameMixin, - Meta, -) +from hcloud.core import BaseDomain, BoundModelBase, ClientEntityBase, Meta class TestBoundModelBase: @@ -96,7 +90,7 @@ class CandiesPageResult(NamedTuple): meta: Meta class CandiesClient(ClientEntityBase): - def get_list(self, status, page=None, per_page=None): + def get_list(self, status=None, page=None, per_page=None): json_content = json_content_function(page) results = [ (r, page, status, per_page) for r in json_content["candies"] @@ -122,7 +116,7 @@ def get_actions_list(self, status, page=None, per_page=None): return constructor - def test_get_all_no_meta(self, client_class_constructor): + def test_iter_pages_no_meta(self, client_class_constructor): json_content = {"candies": [1, 2]} def json_content_function(p): @@ -130,11 +124,11 @@ def json_content_function(p): candies_client = client_class_constructor(json_content_function) - result = candies_client.get_all(status="sweet") + result = candies_client._iter_pages(candies_client.get_list, status="sweet") assert result == [(1, 1, "sweet", 50), (2, 1, "sweet", 50)] - def test_get_all_no_next_page(self, client_class_constructor): + def test_iter_pages_no_next_page(self, client_class_constructor): json_content = { "candies": [1, 2], "meta": {"pagination": {"page": 1, "per_page": 11, "next_page": None}}, @@ -145,11 +139,11 @@ def json_content_function(p): candies_client = client_class_constructor(json_content_function) - result = candies_client.get_all(status="sweet") + result = candies_client._iter_pages(candies_client.get_list, status="sweet") assert result == [(1, 1, "sweet", 50), (2, 1, "sweet", 50)] - def test_get_all_ok(self, client_class_constructor): + def test_iter_pages_ok(self, client_class_constructor): def json_content_function(p): return { "candies": [10 + p, 20 + p], @@ -164,7 +158,7 @@ def json_content_function(p): candies_client = client_class_constructor(json_content_function) - result = candies_client.get_all(status="sweet") + result = candies_client._iter_pages(candies_client.get_list, status="sweet") assert result == [ (11, 1, "sweet", 50), @@ -175,19 +169,6 @@ def json_content_function(p): (23, 3, "sweet", 50), ] - def test_get_actions_no_method(self, client_class_constructor): - json_content = {"candies": [1, 2]} - - def json_content_function(p): - return json_content - - candies_client = client_class_constructor(json_content_function) - - with pytest.raises(ValueError) as exception_info: - candies_client.get_actions() - error = exception_info.value - assert str(error) == "this endpoint does not support get_actions method" - def test_get_actions_ok(self, client_class_with_actions_constructor): def json_content_function(p): return { @@ -203,7 +184,9 @@ def json_content_function(p): candies_client = client_class_with_actions_constructor(json_content_function) - result = candies_client.get_actions(status="sweet") + result = candies_client._iter_pages( + candies_client.get_actions_list, status="sweet" + ) assert result == [ (11, 1, "sweet", 50), @@ -214,26 +197,7 @@ def json_content_function(p): (23, 3, "sweet", 50), ] - -class TestGetEntityByNameMixin: - @pytest.fixture() - def client_class_constructor(self): - def constructor(json_content_function): - class CandiesPageResult(NamedTuple): - candies: list[Any] - meta: Meta - - class CandiesClient(ClientEntityBase, GetEntityByNameMixin): - def get_list(self, name, page=None, per_page=None): - json_content = json_content_function(page) - results = json_content["candies"] - return CandiesPageResult(results, Meta.parse_meta(json_content)) - - return CandiesClient(mock.MagicMock()) - - return constructor - - def test_get_by_name_result_exists(self, client_class_constructor): + def test_get_first_by_result_exists(self, client_class_constructor): json_content = {"candies": [1]} def json_content_function(p): @@ -241,11 +205,11 @@ def json_content_function(p): candies_client = client_class_constructor(json_content_function) - result = candies_client.get_by_name(name="sweet") + result = candies_client._get_first_by(status="sweet") - assert result == 1 + assert result == (1, None, "sweet", None) - def test_get_by_name_result_does_not_exist(self, client_class_constructor): + def test_get_first_by_result_does_not_exist(self, client_class_constructor): json_content = {"candies": []} def json_content_function(p): @@ -253,6 +217,6 @@ def json_content_function(p): candies_client = client_class_constructor(json_content_function) - result = candies_client.get_by_name(name="sweet") + result = candies_client._get_first_by(status="sweet") assert result is None