diff --git a/hcloud/primary_ips/client.py b/hcloud/primary_ips/client.py index f7f36a80..cc26e55e 100644 --- a/hcloud/primary_ips/client.py +++ b/hcloud/primary_ips/client.py @@ -1,5 +1,6 @@ from __future__ import annotations +import warnings from typing import TYPE_CHECKING, Any, NamedTuple from ..actions import ActionsPageResult, BoundAction, ResourceActionsClient @@ -9,6 +10,7 @@ if TYPE_CHECKING: from .._client import Client from ..datacenters import BoundDatacenter, Datacenter + from ..locations import BoundLocation, Location class BoundPrimaryIP(BoundModelBase[PrimaryIP], PrimaryIP): @@ -24,10 +26,15 @@ def __init__( ): # pylint: disable=import-outside-toplevel from ..datacenters import BoundDatacenter + from ..locations import BoundLocation - datacenter = data.get("datacenter", {}) - if datacenter: - data["datacenter"] = BoundDatacenter(client._parent.datacenters, datacenter) + raw = data.get("datacenter", {}) + if raw: + data["datacenter"] = BoundDatacenter(client._parent.datacenters, raw) + + raw = data.get("location", {}) + if raw: + data["location"] = BoundLocation(client._parent.locations, raw) super().__init__(client, data, complete) @@ -309,6 +316,7 @@ def create( type: str, name: str, datacenter: Datacenter | BoundDatacenter | None = None, + location: Location | BoundLocation | None = None, assignee_type: str | None = "server", assignee_id: int | None = None, auto_delete: bool | None = False, @@ -319,6 +327,7 @@ def create( :param type: str Primary IP type Choices: ipv4, ipv6 :param name: str :param datacenter: Datacenter (optional) + :param location: Location (optional) :param assignee_type: str (optional) :param assignee_id: int (optional) :param auto_delete: bool (optional) @@ -333,7 +342,16 @@ def create( "auto_delete": auto_delete, } if datacenter is not None: + warnings.warn( + "The 'datacenter' argument is deprecated and will be removed after 1 July 2026. " + "Please use the 'location' argument instead. " + "See https://docs.hetzner.cloud/changelog#2025-12-16-phasing-out-datacenters", + DeprecationWarning, + stacklevel=2, + ) data["datacenter"] = datacenter.id_or_name + if location is not None: + data["location"] = location.id_or_name if assignee_id is not None: data["assignee_id"] = assignee_id if labels is not None: diff --git a/hcloud/primary_ips/domain.py b/hcloud/primary_ips/domain.py index 9a7e3d2b..58cd596f 100644 --- a/hcloud/primary_ips/domain.py +++ b/hcloud/primary_ips/domain.py @@ -1,5 +1,6 @@ from __future__ import annotations +import warnings from typing import TYPE_CHECKING, TypedDict from ..core import BaseDomain, DomainIdentityMixin @@ -7,6 +8,7 @@ if TYPE_CHECKING: from ..actions import BoundAction from ..datacenters import BoundDatacenter + from ..locations import BoundLocation from ..rdns import DNSPtr from .client import BoundPrimaryIP @@ -23,7 +25,15 @@ class PrimaryIP(BaseDomain, DomainIdentityMixin): :param dns_ptr: List[Dict] Array of reverse DNS entries :param datacenter: :class:`Datacenter ` - Datacenter the Primary IP was created in. + Datacenter the Primary IP was created in. + + This property is deprecated and will be removed after 1 July 2026. + Please use the ``location`` property instead. + + See https://docs.hetzner.cloud/changelog#2025-12-16-phasing-out-datacenters. + + :param location: :class:`Location ` + Location the Primary IP was created in. :param blocked: boolean Whether the IP is blocked :param protection: dict @@ -42,12 +52,12 @@ class PrimaryIP(BaseDomain, DomainIdentityMixin): Delete the Primary IP when the Assignee it is assigned to is deleted. """ - __api_properties__ = ( + __properties__ = ( "id", "ip", "type", "dns_ptr", - "datacenter", + "location", "blocked", "protection", "labels", @@ -57,7 +67,14 @@ class PrimaryIP(BaseDomain, DomainIdentityMixin): "assignee_type", "auto_delete", ) - __slots__ = __api_properties__ + __api_properties__ = ( + *__properties__, + "datacenter", + ) + __slots__ = ( + *__properties__, + "_datacenter", + ) def __init__( self, @@ -66,6 +83,7 @@ def __init__( ip: str | None = None, dns_ptr: list[DNSPtr] | None = None, datacenter: BoundDatacenter | None = None, + location: BoundLocation | None = None, blocked: bool | None = None, protection: PrimaryIPProtection | None = None, labels: dict[str, str] | None = None, @@ -80,6 +98,7 @@ def __init__( self.ip = ip self.dns_ptr = dns_ptr self.datacenter = datacenter + self.location = location self.blocked = blocked self.protection = protection self.labels = labels @@ -89,6 +108,24 @@ def __init__( self.assignee_type = assignee_type self.auto_delete = auto_delete + @property + def datacenter(self) -> BoundDatacenter | None: + """ + :meta private: + """ + warnings.warn( + "The 'datacenter' property is deprecated and will be removed after 1 July 2026. " + "Please use the 'location' property instead. " + "See https://docs.hetzner.cloud/changelog#2025-12-16-phasing-out-datacenters.", + DeprecationWarning, + stacklevel=2, + ) + return self._datacenter + + @datacenter.setter + def datacenter(self, value: BoundDatacenter | None) -> None: + self._datacenter = value + class PrimaryIPProtection(TypedDict): delete: bool diff --git a/hcloud/servers/client.py b/hcloud/servers/client.py index d5abb28d..e65e4e08 100644 --- a/hcloud/servers/client.py +++ b/hcloud/servers/client.py @@ -1,5 +1,6 @@ from __future__ import annotations +import warnings from datetime import datetime from typing import TYPE_CHECKING, Any, NamedTuple @@ -12,6 +13,7 @@ from ..floating_ips import BoundFloatingIP from ..images import BoundImage, CreateImageResponse from ..isos import BoundIso +from ..locations import BoundLocation, Location from ..metrics import Metrics from ..placement_groups import BoundPlacementGroup from ..primary_ips import BoundPrimaryIP @@ -39,7 +41,6 @@ from ..firewalls import Firewall from ..images import Image from ..isos import Iso - from ..locations import BoundLocation, Location from ..networks import BoundNetwork, Network from ..placement_groups import PlacementGroup from ..server_types import ServerType @@ -60,9 +61,13 @@ def __init__( data: dict[str, Any], complete: bool = True, ): - datacenter = data.get("datacenter") - if datacenter is not None: - data["datacenter"] = BoundDatacenter(client._parent.datacenters, datacenter) + raw = data.get("datacenter") + if raw: + data["datacenter"] = BoundDatacenter(client._parent.datacenters, raw) + + raw = data.get("location") + if raw: + data["location"] = BoundLocation(client._parent.locations, raw) volumes = data.get("volumes", []) if volumes: @@ -662,6 +667,13 @@ def create( if location is not None: data["location"] = location.id_or_name if datacenter is not None: + warnings.warn( + "The 'datacenter' argument is deprecated and will be removed after 1 July 2026. " + "Please use the 'location' argument instead. " + "See https://docs.hetzner.cloud/changelog#2025-12-16-phasing-out-datacenters", + DeprecationWarning, + stacklevel=2, + ) data["datacenter"] = datacenter.id_or_name if ssh_keys is not None: data["ssh_keys"] = [ssh_key.id_or_name for ssh_key in ssh_keys] diff --git a/hcloud/servers/domain.py b/hcloud/servers/domain.py index 215fa9de..1a052886 100644 --- a/hcloud/servers/domain.py +++ b/hcloud/servers/domain.py @@ -1,5 +1,6 @@ from __future__ import annotations +import warnings from typing import TYPE_CHECKING, Literal, TypedDict from ..core import BaseDomain, DomainIdentityMixin @@ -11,6 +12,7 @@ from ..floating_ips import BoundFloatingIP from ..images import BoundImage from ..isos import BoundIso + from ..locations import BoundLocation from ..metrics import Metrics from ..networks import BoundNetwork, Network from ..placement_groups import BoundPlacementGroup @@ -36,6 +38,12 @@ class Server(BaseDomain, DomainIdentityMixin): Public network information. :param server_type: :class:`BoundServerType ` :param datacenter: :class:`BoundDatacenter ` + + This property is deprecated and will be removed after 1 July 2026. + Please use the ``location`` property instead. + + See https://docs.hetzner.cloud/changelog#2025-12-16-phasing-out-datacenters. + :param location: :class:`BoundLocation ` :param image: :class:`BoundImage `, None :param iso: :class:`BoundIso `, None :param rescue_enabled: bool @@ -81,13 +89,13 @@ class Server(BaseDomain, DomainIdentityMixin): STATUS_UNKNOWN = "unknown" """Server Status unknown""" - __api_properties__ = ( + __properties__ = ( "id", "name", "status", "public_net", "server_type", - "datacenter", + "location", "image", "iso", "rescue_enabled", @@ -104,7 +112,14 @@ class Server(BaseDomain, DomainIdentityMixin): "primary_disk_size", "placement_group", ) - __slots__ = __api_properties__ + __api_properties__ = ( + *__properties__, + "datacenter", + ) + __slots__ = ( + *__properties__, + "_datacenter", + ) # pylint: disable=too-many-locals def __init__( @@ -116,6 +131,7 @@ def __init__( public_net: PublicNetwork | None = None, server_type: BoundServerType | None = None, datacenter: BoundDatacenter | None = None, + location: BoundLocation | None = None, image: BoundImage | None = None, iso: BoundIso | None = None, rescue_enabled: bool | None = None, @@ -138,6 +154,7 @@ def __init__( self.public_net = public_net self.server_type = server_type self.datacenter = datacenter + self.location = location self.image = image self.iso = iso self.rescue_enabled = rescue_enabled @@ -163,6 +180,24 @@ def private_net_for(self, network: BoundNetwork | Network) -> PrivateNet | None: return o return None + @property + def datacenter(self) -> BoundDatacenter | None: + """ + :meta private: + """ + warnings.warn( + "The 'datacenter' property is deprecated and will be removed after 1 July 2026. " + "Please use the 'location' property instead. " + "See https://docs.hetzner.cloud/changelog#2025-12-16-phasing-out-datacenters.", + DeprecationWarning, + stacklevel=2, + ) + return self._datacenter + + @datacenter.setter + def datacenter(self, value: BoundDatacenter | None) -> None: + self._datacenter = value + class ServerProtection(TypedDict): rebuild: bool diff --git a/tests/unit/conftest.py b/tests/unit/conftest.py index 0d2a8ecb..65446f4a 100644 --- a/tests/unit/conftest.py +++ b/tests/unit/conftest.py @@ -3,6 +3,7 @@ from __future__ import annotations import inspect +import warnings from collections.abc import Callable from typing import ClassVar, TypedDict from unittest import mock @@ -185,11 +186,17 @@ def test_method_list(self, bound_model): members_count = 0 members_missing = [] - for name, member in inspect.getmembers( - bound_model, - lambda m: inspect.ismethod(m) - and m.__func__ in bound_model.__class__.__dict__.values(), - ): + + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", category=DeprecationWarning) + + members = inspect.getmembers( + bound_model, + lambda m: inspect.ismethod(m) + and m.__func__ in bound_model.__class__.__dict__.values(), + ) + + for name, member in members: # Ignore private methods if name.startswith("_"): continue diff --git a/tests/unit/primary_ips/test_client.py b/tests/unit/primary_ips/test_client.py index 8886a01e..a4ffcc3a 100644 --- a/tests/unit/primary_ips/test_client.py +++ b/tests/unit/primary_ips/test_client.py @@ -45,14 +45,17 @@ def test_init(self, primary_ip_response): assert bound_primary_ip.assignee_id == 17 assert bound_primary_ip.assignee_type == "server" - assert isinstance(bound_primary_ip.datacenter, BoundDatacenter) - assert bound_primary_ip.datacenter.id == 42 - assert bound_primary_ip.datacenter.name == "fsn1-dc8" - assert bound_primary_ip.datacenter.description == "Falkenstein DC Park 8" - assert bound_primary_ip.datacenter.location.country == "DE" - assert bound_primary_ip.datacenter.location.city == "Falkenstein" - assert bound_primary_ip.datacenter.location.latitude == 50.47612 - assert bound_primary_ip.datacenter.location.longitude == 12.370071 + with pytest.deprecated_call(): + datacenter = bound_primary_ip.datacenter + + assert isinstance(datacenter, BoundDatacenter) + assert datacenter.id == 42 + assert datacenter.name == "fsn1-dc8" + assert datacenter.description == "Falkenstein DC Park 8" + assert datacenter.location.country == "DE" + assert datacenter.location.city == "Falkenstein" + assert datacenter.location.latitude == 50.47612 + assert datacenter.location.longitude == 12.370071 class TestPrimaryIPsClient: @@ -132,9 +135,12 @@ def test_create_with_datacenter( ): request_mock.return_value = primary_ip_response - response = primary_ips_client.create( - type="ipv6", name="my-resource", datacenter=Datacenter(name="datacenter") - ) + with pytest.deprecated_call(): + response = primary_ips_client.create( + type="ipv6", + name="my-resource", + datacenter=Datacenter(name="datacenter"), + ) request_mock.assert_called_with( method="POST", diff --git a/tests/unit/servers/test_client.py b/tests/unit/servers/test_client.py index ebf1da24..0a09544d 100644 --- a/tests/unit/servers/test_client.py +++ b/tests/unit/servers/test_client.py @@ -100,12 +100,13 @@ def test_init(self, response_full_server): assert bound_server.public_net.floating_ips[0].id == 478 assert bound_server.public_net.floating_ips[0].complete is False - assert isinstance(bound_server.datacenter, BoundDatacenter) - assert ( - bound_server.datacenter._client == bound_server._client._parent.datacenters - ) - assert bound_server.datacenter.id == 1 - assert bound_server.datacenter.complete is True + with pytest.deprecated_call(): + datacenter = bound_server.datacenter + + assert isinstance(datacenter, BoundDatacenter) + assert datacenter._client == bound_server._client._parent.datacenters + assert datacenter.id == 1 + assert datacenter.complete is True assert isinstance(bound_server.server_type, BoundServerType) assert ( @@ -288,12 +289,13 @@ def test_create_with_datacenter( ): request_mock.return_value = response_create_simple_server - response = servers_client.create( - "my-server", - server_type=ServerType(name="cx11"), - image=Image(id=4711), - datacenter=Datacenter(id=1), - ) + with pytest.deprecated_call(): + response = servers_client.create( + "my-server", + server_type=ServerType(name="cx11"), + image=Image(id=4711), + datacenter=Datacenter(id=1), + ) request_mock.assert_called_with( method="POST",