Skip to content

Commit d21d66c

Browse files
authored
feat: deprecate datacenter in primary ips and servers (#609)
See https://docs.hetzner.cloud/changelog#2025-12-16-phasing-out-datacenters
1 parent c314c8a commit d21d66c

File tree

7 files changed

+159
-42
lines changed

7 files changed

+159
-42
lines changed

hcloud/primary_ips/client.py

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from __future__ import annotations
22

3+
import warnings
34
from typing import TYPE_CHECKING, Any, NamedTuple
45

56
from ..actions import ActionsPageResult, BoundAction, ResourceActionsClient
@@ -9,6 +10,7 @@
910
if TYPE_CHECKING:
1011
from .._client import Client
1112
from ..datacenters import BoundDatacenter, Datacenter
13+
from ..locations import BoundLocation, Location
1214

1315

1416
class BoundPrimaryIP(BoundModelBase[PrimaryIP], PrimaryIP):
@@ -24,10 +26,15 @@ def __init__(
2426
):
2527
# pylint: disable=import-outside-toplevel
2628
from ..datacenters import BoundDatacenter
29+
from ..locations import BoundLocation
2730

28-
datacenter = data.get("datacenter", {})
29-
if datacenter:
30-
data["datacenter"] = BoundDatacenter(client._parent.datacenters, datacenter)
31+
raw = data.get("datacenter", {})
32+
if raw:
33+
data["datacenter"] = BoundDatacenter(client._parent.datacenters, raw)
34+
35+
raw = data.get("location", {})
36+
if raw:
37+
data["location"] = BoundLocation(client._parent.locations, raw)
3138

3239
super().__init__(client, data, complete)
3340

@@ -309,6 +316,7 @@ def create(
309316
type: str,
310317
name: str,
311318
datacenter: Datacenter | BoundDatacenter | None = None,
319+
location: Location | BoundLocation | None = None,
312320
assignee_type: str | None = "server",
313321
assignee_id: int | None = None,
314322
auto_delete: bool | None = False,
@@ -319,6 +327,7 @@ def create(
319327
:param type: str Primary IP type Choices: ipv4, ipv6
320328
:param name: str
321329
:param datacenter: Datacenter (optional)
330+
:param location: Location (optional)
322331
:param assignee_type: str (optional)
323332
:param assignee_id: int (optional)
324333
:param auto_delete: bool (optional)
@@ -333,7 +342,16 @@ def create(
333342
"auto_delete": auto_delete,
334343
}
335344
if datacenter is not None:
345+
warnings.warn(
346+
"The 'datacenter' argument is deprecated and will be removed after 1 July 2026. "
347+
"Please use the 'location' argument instead. "
348+
"See https://docs.hetzner.cloud/changelog#2025-12-16-phasing-out-datacenters",
349+
DeprecationWarning,
350+
stacklevel=2,
351+
)
336352
data["datacenter"] = datacenter.id_or_name
353+
if location is not None:
354+
data["location"] = location.id_or_name
337355
if assignee_id is not None:
338356
data["assignee_id"] = assignee_id
339357
if labels is not None:

hcloud/primary_ips/domain.py

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
from __future__ import annotations
22

3+
import warnings
34
from typing import TYPE_CHECKING, TypedDict
45

56
from ..core import BaseDomain, DomainIdentityMixin
67

78
if TYPE_CHECKING:
89
from ..actions import BoundAction
910
from ..datacenters import BoundDatacenter
11+
from ..locations import BoundLocation
1012
from ..rdns import DNSPtr
1113
from .client import BoundPrimaryIP
1214

@@ -23,7 +25,15 @@ class PrimaryIP(BaseDomain, DomainIdentityMixin):
2325
:param dns_ptr: List[Dict]
2426
Array of reverse DNS entries
2527
:param datacenter: :class:`Datacenter <hcloud.datacenters.client.BoundDatacenter>`
26-
Datacenter the Primary IP was created in.
28+
Datacenter the Primary IP was created in.
29+
30+
This property is deprecated and will be removed after 1 July 2026.
31+
Please use the ``location`` property instead.
32+
33+
See https://docs.hetzner.cloud/changelog#2025-12-16-phasing-out-datacenters.
34+
35+
:param location: :class:`Location <hcloud.locations.client.BoundLocation>`
36+
Location the Primary IP was created in.
2737
:param blocked: boolean
2838
Whether the IP is blocked
2939
:param protection: dict
@@ -42,12 +52,12 @@ class PrimaryIP(BaseDomain, DomainIdentityMixin):
4252
Delete the Primary IP when the Assignee it is assigned to is deleted.
4353
"""
4454

45-
__api_properties__ = (
55+
__properties__ = (
4656
"id",
4757
"ip",
4858
"type",
4959
"dns_ptr",
50-
"datacenter",
60+
"location",
5161
"blocked",
5262
"protection",
5363
"labels",
@@ -57,7 +67,14 @@ class PrimaryIP(BaseDomain, DomainIdentityMixin):
5767
"assignee_type",
5868
"auto_delete",
5969
)
60-
__slots__ = __api_properties__
70+
__api_properties__ = (
71+
*__properties__,
72+
"datacenter",
73+
)
74+
__slots__ = (
75+
*__properties__,
76+
"_datacenter",
77+
)
6178

6279
def __init__(
6380
self,
@@ -66,6 +83,7 @@ def __init__(
6683
ip: str | None = None,
6784
dns_ptr: list[DNSPtr] | None = None,
6885
datacenter: BoundDatacenter | None = None,
86+
location: BoundLocation | None = None,
6987
blocked: bool | None = None,
7088
protection: PrimaryIPProtection | None = None,
7189
labels: dict[str, str] | None = None,
@@ -80,6 +98,7 @@ def __init__(
8098
self.ip = ip
8199
self.dns_ptr = dns_ptr
82100
self.datacenter = datacenter
101+
self.location = location
83102
self.blocked = blocked
84103
self.protection = protection
85104
self.labels = labels
@@ -89,6 +108,24 @@ def __init__(
89108
self.assignee_type = assignee_type
90109
self.auto_delete = auto_delete
91110

111+
@property
112+
def datacenter(self) -> BoundDatacenter | None:
113+
"""
114+
:meta private:
115+
"""
116+
warnings.warn(
117+
"The 'datacenter' property is deprecated and will be removed after 1 July 2026. "
118+
"Please use the 'location' property instead. "
119+
"See https://docs.hetzner.cloud/changelog#2025-12-16-phasing-out-datacenters.",
120+
DeprecationWarning,
121+
stacklevel=2,
122+
)
123+
return self._datacenter
124+
125+
@datacenter.setter
126+
def datacenter(self, value: BoundDatacenter | None) -> None:
127+
self._datacenter = value
128+
92129

93130
class PrimaryIPProtection(TypedDict):
94131
delete: bool

hcloud/servers/client.py

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from __future__ import annotations
22

3+
import warnings
34
from datetime import datetime
45
from typing import TYPE_CHECKING, Any, NamedTuple
56

@@ -12,6 +13,7 @@
1213
from ..floating_ips import BoundFloatingIP
1314
from ..images import BoundImage, CreateImageResponse
1415
from ..isos import BoundIso
16+
from ..locations import BoundLocation, Location
1517
from ..metrics import Metrics
1618
from ..placement_groups import BoundPlacementGroup
1719
from ..primary_ips import BoundPrimaryIP
@@ -39,7 +41,6 @@
3941
from ..firewalls import Firewall
4042
from ..images import Image
4143
from ..isos import Iso
42-
from ..locations import BoundLocation, Location
4344
from ..networks import BoundNetwork, Network
4445
from ..placement_groups import PlacementGroup
4546
from ..server_types import ServerType
@@ -60,9 +61,13 @@ def __init__(
6061
data: dict[str, Any],
6162
complete: bool = True,
6263
):
63-
datacenter = data.get("datacenter")
64-
if datacenter is not None:
65-
data["datacenter"] = BoundDatacenter(client._parent.datacenters, datacenter)
64+
raw = data.get("datacenter")
65+
if raw:
66+
data["datacenter"] = BoundDatacenter(client._parent.datacenters, raw)
67+
68+
raw = data.get("location")
69+
if raw:
70+
data["location"] = BoundLocation(client._parent.locations, raw)
6671

6772
volumes = data.get("volumes", [])
6873
if volumes:
@@ -662,6 +667,13 @@ def create(
662667
if location is not None:
663668
data["location"] = location.id_or_name
664669
if datacenter is not None:
670+
warnings.warn(
671+
"The 'datacenter' argument is deprecated and will be removed after 1 July 2026. "
672+
"Please use the 'location' argument instead. "
673+
"See https://docs.hetzner.cloud/changelog#2025-12-16-phasing-out-datacenters",
674+
DeprecationWarning,
675+
stacklevel=2,
676+
)
665677
data["datacenter"] = datacenter.id_or_name
666678
if ssh_keys is not None:
667679
data["ssh_keys"] = [ssh_key.id_or_name for ssh_key in ssh_keys]

hcloud/servers/domain.py

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from __future__ import annotations
22

3+
import warnings
34
from typing import TYPE_CHECKING, Literal, TypedDict
45

56
from ..core import BaseDomain, DomainIdentityMixin
@@ -11,6 +12,7 @@
1112
from ..floating_ips import BoundFloatingIP
1213
from ..images import BoundImage
1314
from ..isos import BoundIso
15+
from ..locations import BoundLocation
1416
from ..metrics import Metrics
1517
from ..networks import BoundNetwork, Network
1618
from ..placement_groups import BoundPlacementGroup
@@ -36,6 +38,12 @@ class Server(BaseDomain, DomainIdentityMixin):
3638
Public network information.
3739
:param server_type: :class:`BoundServerType <hcloud.server_types.client.BoundServerType>`
3840
:param datacenter: :class:`BoundDatacenter <hcloud.datacenters.client.BoundDatacenter>`
41+
42+
This property is deprecated and will be removed after 1 July 2026.
43+
Please use the ``location`` property instead.
44+
45+
See https://docs.hetzner.cloud/changelog#2025-12-16-phasing-out-datacenters.
46+
:param location: :class:`BoundLocation <hcloud.locations.client.BoundLocation>`
3947
:param image: :class:`BoundImage <hcloud.images.client.BoundImage>`, None
4048
:param iso: :class:`BoundIso <hcloud.isos.client.BoundIso>`, None
4149
:param rescue_enabled: bool
@@ -81,13 +89,13 @@ class Server(BaseDomain, DomainIdentityMixin):
8189
STATUS_UNKNOWN = "unknown"
8290
"""Server Status unknown"""
8391

84-
__api_properties__ = (
92+
__properties__ = (
8593
"id",
8694
"name",
8795
"status",
8896
"public_net",
8997
"server_type",
90-
"datacenter",
98+
"location",
9199
"image",
92100
"iso",
93101
"rescue_enabled",
@@ -104,7 +112,14 @@ class Server(BaseDomain, DomainIdentityMixin):
104112
"primary_disk_size",
105113
"placement_group",
106114
)
107-
__slots__ = __api_properties__
115+
__api_properties__ = (
116+
*__properties__,
117+
"datacenter",
118+
)
119+
__slots__ = (
120+
*__properties__,
121+
"_datacenter",
122+
)
108123

109124
# pylint: disable=too-many-locals
110125
def __init__(
@@ -116,6 +131,7 @@ def __init__(
116131
public_net: PublicNetwork | None = None,
117132
server_type: BoundServerType | None = None,
118133
datacenter: BoundDatacenter | None = None,
134+
location: BoundLocation | None = None,
119135
image: BoundImage | None = None,
120136
iso: BoundIso | None = None,
121137
rescue_enabled: bool | None = None,
@@ -138,6 +154,7 @@ def __init__(
138154
self.public_net = public_net
139155
self.server_type = server_type
140156
self.datacenter = datacenter
157+
self.location = location
141158
self.image = image
142159
self.iso = iso
143160
self.rescue_enabled = rescue_enabled
@@ -163,6 +180,24 @@ def private_net_for(self, network: BoundNetwork | Network) -> PrivateNet | None:
163180
return o
164181
return None
165182

183+
@property
184+
def datacenter(self) -> BoundDatacenter | None:
185+
"""
186+
:meta private:
187+
"""
188+
warnings.warn(
189+
"The 'datacenter' property is deprecated and will be removed after 1 July 2026. "
190+
"Please use the 'location' property instead. "
191+
"See https://docs.hetzner.cloud/changelog#2025-12-16-phasing-out-datacenters.",
192+
DeprecationWarning,
193+
stacklevel=2,
194+
)
195+
return self._datacenter
196+
197+
@datacenter.setter
198+
def datacenter(self, value: BoundDatacenter | None) -> None:
199+
self._datacenter = value
200+
166201

167202
class ServerProtection(TypedDict):
168203
rebuild: bool

tests/unit/conftest.py

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from __future__ import annotations
44

55
import inspect
6+
import warnings
67
from collections.abc import Callable
78
from typing import ClassVar, TypedDict
89
from unittest import mock
@@ -185,11 +186,17 @@ def test_method_list(self, bound_model):
185186

186187
members_count = 0
187188
members_missing = []
188-
for name, member in inspect.getmembers(
189-
bound_model,
190-
lambda m: inspect.ismethod(m)
191-
and m.__func__ in bound_model.__class__.__dict__.values(),
192-
):
189+
190+
with warnings.catch_warnings():
191+
warnings.filterwarnings("ignore", category=DeprecationWarning)
192+
193+
members = inspect.getmembers(
194+
bound_model,
195+
lambda m: inspect.ismethod(m)
196+
and m.__func__ in bound_model.__class__.__dict__.values(),
197+
)
198+
199+
for name, member in members:
193200
# Ignore private methods
194201
if name.startswith("_"):
195202
continue

tests/unit/primary_ips/test_client.py

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -45,14 +45,17 @@ def test_init(self, primary_ip_response):
4545
assert bound_primary_ip.assignee_id == 17
4646
assert bound_primary_ip.assignee_type == "server"
4747

48-
assert isinstance(bound_primary_ip.datacenter, BoundDatacenter)
49-
assert bound_primary_ip.datacenter.id == 42
50-
assert bound_primary_ip.datacenter.name == "fsn1-dc8"
51-
assert bound_primary_ip.datacenter.description == "Falkenstein DC Park 8"
52-
assert bound_primary_ip.datacenter.location.country == "DE"
53-
assert bound_primary_ip.datacenter.location.city == "Falkenstein"
54-
assert bound_primary_ip.datacenter.location.latitude == 50.47612
55-
assert bound_primary_ip.datacenter.location.longitude == 12.370071
48+
with pytest.deprecated_call():
49+
datacenter = bound_primary_ip.datacenter
50+
51+
assert isinstance(datacenter, BoundDatacenter)
52+
assert datacenter.id == 42
53+
assert datacenter.name == "fsn1-dc8"
54+
assert datacenter.description == "Falkenstein DC Park 8"
55+
assert datacenter.location.country == "DE"
56+
assert datacenter.location.city == "Falkenstein"
57+
assert datacenter.location.latitude == 50.47612
58+
assert datacenter.location.longitude == 12.370071
5659

5760

5861
class TestPrimaryIPsClient:
@@ -132,9 +135,12 @@ def test_create_with_datacenter(
132135
):
133136
request_mock.return_value = primary_ip_response
134137

135-
response = primary_ips_client.create(
136-
type="ipv6", name="my-resource", datacenter=Datacenter(name="datacenter")
137-
)
138+
with pytest.deprecated_call():
139+
response = primary_ips_client.create(
140+
type="ipv6",
141+
name="my-resource",
142+
datacenter=Datacenter(name="datacenter"),
143+
)
138144

139145
request_mock.assert_called_with(
140146
method="POST",

0 commit comments

Comments
 (0)