mtn-cloud 0.2.19__tar.gz → 0.3.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/PKG-INFO +16 -12
  2. {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/README.md +15 -11
  3. {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/docs/advanced-cookbook.md +15 -5
  4. {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/docs/api-reference/networks.md +5 -2
  5. {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/docs/index.md +0 -1
  6. {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/docs/networking.md +7 -3
  7. {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/docs/quickstart.md +45 -16
  8. {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/src/mtn_cloud/_version.py +1 -1
  9. {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/src/mtn_cloud/models/base.py +7 -1
  10. {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/src/mtn_cloud/models/network.py +3 -0
  11. {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/src/mtn_cloud/resources/networks.py +7 -1
  12. {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/tests/test_networks.py +8 -0
  13. mtn_cloud-0.2.19/docs/docstring-style.md +0 -50
  14. {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/.gitignore +0 -0
  15. {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/LICENSE +0 -0
  16. {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/docs/api-overview.md +0 -0
  17. {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/docs/api-reference/archive-buckets.md +0 -0
  18. {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/docs/api-reference/backups.md +0 -0
  19. {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/docs/api-reference/client.md +0 -0
  20. {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/docs/api-reference/clouds.md +0 -0
  21. {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/docs/api-reference/groups.md +0 -0
  22. {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/docs/api-reference/index.md +0 -0
  23. {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/docs/api-reference/instance-types.md +0 -0
  24. {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/docs/api-reference/instances.md +0 -0
  25. {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/docs/api-reference/plans.md +0 -0
  26. {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/docs/api-reference/security-groups.md +0 -0
  27. {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/docs/api-reference/storage-buckets.md +0 -0
  28. {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/docs/api-reference/virtual-images.md +0 -0
  29. {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/docs/backups.md +0 -0
  30. {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/docs/instances.md +0 -0
  31. {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/docs/security-groups.md +0 -0
  32. {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/docs/storage.md +0 -0
  33. {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/docs/stylesheets/extra.css +0 -0
  34. {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/docs/virtual-images.md +0 -0
  35. {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/pyproject.toml +0 -0
  36. {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/src/mtn_cloud/__init__.py +0 -0
  37. {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/src/mtn_cloud/client.py +0 -0
  38. {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/src/mtn_cloud/config.py +0 -0
  39. {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/src/mtn_cloud/exceptions.py +0 -0
  40. {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/src/mtn_cloud/http.py +0 -0
  41. {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/src/mtn_cloud/models/__init__.py +0 -0
  42. {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/src/mtn_cloud/models/archive.py +0 -0
  43. {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/src/mtn_cloud/models/backup.py +0 -0
  44. {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/src/mtn_cloud/models/cloud.py +0 -0
  45. {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/src/mtn_cloud/models/group.py +0 -0
  46. {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/src/mtn_cloud/models/instance.py +0 -0
  47. {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/src/mtn_cloud/models/instance_type.py +0 -0
  48. {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/src/mtn_cloud/models/plan.py +0 -0
  49. {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/src/mtn_cloud/models/resource_pool.py +0 -0
  50. {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/src/mtn_cloud/models/security_group.py +0 -0
  51. {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/src/mtn_cloud/models/snapshot.py +0 -0
  52. {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/src/mtn_cloud/models/storage_bucket.py +0 -0
  53. {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/src/mtn_cloud/models/user.py +0 -0
  54. {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/src/mtn_cloud/models/virtual_image.py +0 -0
  55. {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/src/mtn_cloud/models/volume.py +0 -0
  56. {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/src/mtn_cloud/resources/__init__.py +0 -0
  57. {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/src/mtn_cloud/resources/archive_buckets.py +0 -0
  58. {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/src/mtn_cloud/resources/backups.py +0 -0
  59. {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/src/mtn_cloud/resources/base.py +0 -0
  60. {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/src/mtn_cloud/resources/clouds.py +0 -0
  61. {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/src/mtn_cloud/resources/groups.py +0 -0
  62. {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/src/mtn_cloud/resources/instance_types.py +0 -0
  63. {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/src/mtn_cloud/resources/instances.py +0 -0
  64. {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/src/mtn_cloud/resources/plans.py +0 -0
  65. {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/src/mtn_cloud/resources/security_groups.py +0 -0
  66. {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/src/mtn_cloud/resources/storage_buckets.py +0 -0
  67. {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/src/mtn_cloud/resources/virtual_images.py +0 -0
  68. {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/tests/__init__.py +0 -0
  69. {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/tests/conftest.py +0 -0
  70. {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/tests/test_archive_buckets.py +0 -0
  71. {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/tests/test_backups.py +0 -0
  72. {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/tests/test_client.py +0 -0
  73. {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/tests/test_clouds.py +0 -0
  74. {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/tests/test_config.py +0 -0
  75. {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/tests/test_exceptions.py +0 -0
  76. {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/tests/test_groups.py +0 -0
  77. {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/tests/test_instance_types.py +0 -0
  78. {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/tests/test_instances.py +0 -0
  79. {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/tests/test_plans.py +0 -0
  80. {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/tests/test_security_groups.py +0 -0
  81. {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/tests/test_storage_buckets.py +0 -0
  82. {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/tests/test_virtual_images.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mtn-cloud
3
- Version: 0.2.19
3
+ Version: 0.3.1
4
4
  Summary: Community Python SDK for MTN Cloud - Deploy and manage cloud resources with ease
5
5
  Project-URL: Homepage, https://github.com/mahveotm/mtn-cloud-python
6
6
  Project-URL: Documentation, https://github.com/mahveotm/mtn-cloud-python#readme
@@ -84,7 +84,6 @@ pip install mtn-cloud
84
84
  - [Advanced Cookbook](https://mahveotm.github.io/mtn-cloud-python/advanced-cookbook/)
85
85
  - [API Overview](https://mahveotm.github.io/mtn-cloud-python/api-overview/)
86
86
  - [API Reference](https://mahveotm.github.io/mtn-cloud-python/api-reference/)
87
- - [Docstring Style Standard](https://mahveotm.github.io/mtn-cloud-python/docstring-style/)
88
87
 
89
88
  ## Quick Start
90
89
 
@@ -200,27 +199,32 @@ cloud.instances.delete(123, force=True, preserve_volumes=True)
200
199
  ### 4. Work with Networks
201
200
 
202
201
  ```python
202
+ group = cloud.groups.get_by_name("MTNNG_CLOUD_AZ_1")
203
+
203
204
  # List networks
204
- networks = cloud.networks.list(cloud_id=1)
205
+ networks = cloud.networks.list(cloud_id=group.cloud_ids[0])
205
206
  for n in networks[:5]:
206
207
  print(n.id, n.name, n.cidr)
207
208
 
208
- # Create an OpenStack-focused network
209
- network_types = cloud.networks.list_types(openstack_only=True)
209
+ # Create an OpenStack network (needs an OpenStack type + a resource pool)
210
+ net_type = next(
211
+ t for t in cloud.networks.list_types(openstack_only=True)
212
+ if t.code == "openstackPrivate"
213
+ )
214
+ pool = cloud.instances.get_resource_pool("my-project", group=group.name)
215
+
210
216
  new_network = cloud.networks.create(
211
217
  name="mtn-prod-net",
212
- cloud_id=1,
213
- group_id=621,
214
- type_id=network_types[0].id,
218
+ cloud_id=group.cloud_ids[0],
219
+ group_id=group.id,
220
+ type_id=net_type.id,
221
+ resource_pool_id=pool.id,
215
222
  cidr="10.42.10.0/24",
216
223
  gateway="10.42.10.1",
217
224
  dns_primary="8.8.8.8",
218
- visibility="private",
219
- dhcp_server=True,
220
225
  )
221
226
 
222
- # Update and list subnets
223
- cloud.networks.update(new_network.id, description="Production network")
227
+ # Inspect subnets
224
228
  subnets = cloud.networks.list_subnets(new_network.id)
225
229
  print(f"Subnets: {len(subnets)}")
226
230
  ```
@@ -40,7 +40,6 @@ pip install mtn-cloud
40
40
  - [Advanced Cookbook](https://mahveotm.github.io/mtn-cloud-python/advanced-cookbook/)
41
41
  - [API Overview](https://mahveotm.github.io/mtn-cloud-python/api-overview/)
42
42
  - [API Reference](https://mahveotm.github.io/mtn-cloud-python/api-reference/)
43
- - [Docstring Style Standard](https://mahveotm.github.io/mtn-cloud-python/docstring-style/)
44
43
 
45
44
  ## Quick Start
46
45
 
@@ -156,27 +155,32 @@ cloud.instances.delete(123, force=True, preserve_volumes=True)
156
155
  ### 4. Work with Networks
157
156
 
158
157
  ```python
158
+ group = cloud.groups.get_by_name("MTNNG_CLOUD_AZ_1")
159
+
159
160
  # List networks
160
- networks = cloud.networks.list(cloud_id=1)
161
+ networks = cloud.networks.list(cloud_id=group.cloud_ids[0])
161
162
  for n in networks[:5]:
162
163
  print(n.id, n.name, n.cidr)
163
164
 
164
- # Create an OpenStack-focused network
165
- network_types = cloud.networks.list_types(openstack_only=True)
165
+ # Create an OpenStack network (needs an OpenStack type + a resource pool)
166
+ net_type = next(
167
+ t for t in cloud.networks.list_types(openstack_only=True)
168
+ if t.code == "openstackPrivate"
169
+ )
170
+ pool = cloud.instances.get_resource_pool("my-project", group=group.name)
171
+
166
172
  new_network = cloud.networks.create(
167
173
  name="mtn-prod-net",
168
- cloud_id=1,
169
- group_id=621,
170
- type_id=network_types[0].id,
174
+ cloud_id=group.cloud_ids[0],
175
+ group_id=group.id,
176
+ type_id=net_type.id,
177
+ resource_pool_id=pool.id,
171
178
  cidr="10.42.10.0/24",
172
179
  gateway="10.42.10.1",
173
180
  dns_primary="8.8.8.8",
174
- visibility="private",
175
- dhcp_server=True,
176
181
  )
177
182
 
178
- # Update and list subnets
179
- cloud.networks.update(new_network.id, description="Production network")
183
+ # Inspect subnets
180
184
  subnets = cloud.networks.list_subnets(new_network.id)
181
185
  print(f"Subnets: {len(subnets)}")
182
186
  ```
@@ -103,9 +103,10 @@ from mtn_cloud import (
103
103
  try:
104
104
  cloud.networks.create(
105
105
  name="prod-net",
106
- cloud_id=1,
106
+ cloud_id=4,
107
107
  group_id=621,
108
- type_id=8,
108
+ type_id=12, # an OpenStack type from list_types(openstack_only=True)
109
+ resource_pool_id=230, # numeric pool ID from get_resource_pool(...).id
109
110
  cidr="10.42.10.0/24",
110
111
  )
111
112
  except AuthenticationError:
@@ -149,8 +150,17 @@ with MTNCloud(token="your-api-token") as cloud:
149
150
  Pick explicit templates and plans instead of "first result" in production.
150
151
 
151
152
  ```python
153
+ group = cloud.groups.get_by_name("MTNNG_CLOUD_AZ_1")
152
154
  target_type = cloud.instance_types.get_by_code("MTN-U24.04LTS")
153
- target_plan = cloud.plans.get_by_name("G2S4")
155
+
156
+ plans = cloud.instances.list_service_plans(
157
+ zone_id=group.cloud_ids[0],
158
+ layout_id=target_type.default_layout_id,
159
+ group_id=group.id,
160
+ )
161
+ target_plan = next(p for p in plans if p["name"] == "G2S4")
162
+
163
+ pool = cloud.instances.get_resource_pool("my-project", group=group.name)
154
164
 
155
165
  instance = cloud.instances.create(
156
166
  name="api-worker-01",
@@ -158,8 +168,8 @@ instance = cloud.instances.create(
158
168
  type=target_type.code,
159
169
  group="MTNNG_CLOUD_AZ_1",
160
170
  layout=target_type.default_layout_id,
161
- plan=target_plan.id,
162
- resource_pool_id="pool-214",
171
+ plan=target_plan["id"],
172
+ resource_pool_id=pool.code,
163
173
  )
164
174
  ```
165
175
 
@@ -32,7 +32,7 @@
32
32
  - Returns: `list[Network]`
33
33
  - Raises: common API exceptions
34
34
 
35
- ### `create(name: str, *, cloud_id: int, group_id: int, type_id=None, display_name=None, labels=None, description=None, cidr=None, gateway=None, dns_primary=None, dns_secondary=None, vlan_id=None, switch_id=None, pool_id=None, allow_static_override=None, assign_public_ip=None, active=None, dhcp_server=None, network_domain_id=None, search_domains=None, network_proxy_id=None, appliance_url_proxy_bypass=None, no_proxy=None, visibility=None, tenant_ids=None, resource_permission_all=None, resource_permission_site_ids=None) -> Network`
35
+ ### `create(name: str, *, cloud_id: int, group_id: int, type_id=None, resource_pool_id=None, display_name=None, labels=None, description=None, cidr=None, gateway=None, dns_primary=None, dns_secondary=None, vlan_id=None, switch_id=None, pool_id=None, allow_static_override=None, assign_public_ip=None, active=None, dhcp_server=None, network_domain_id=None, search_domains=None, network_proxy_id=None, appliance_url_proxy_bypass=None, no_proxy=None, visibility=None, tenant_ids=None, resource_permission_all=None, resource_permission_site_ids=None) -> Network`
36
36
 
37
37
  - Endpoint: `POST /api/networks`
38
38
  - Parameters:
@@ -40,8 +40,11 @@
40
40
  - `name`: network name
41
41
  - `cloud_id`: cloud/zone ID
42
42
  - `group_id`: group/site ID
43
+ - Required for OpenStack networks (e.g. `openstackPrivate`):
44
+ - `type_id`: an OpenStack network type ID from `list_types(openstack_only=True)`
45
+ - `resource_pool_id`: numeric resource pool ID (sent as `zonePool`); get it from `instances.get_resource_pool(...).id`
43
46
  - Common optional network config:
44
- - `type_id`, `display_name`, `labels`, `description`, `cidr`, `gateway`
47
+ - `display_name`, `labels`, `description`, `cidr`, `gateway`
45
48
  - `dns_primary`, `dns_secondary`, `vlan_id`, `switch_id`, `pool_id`
46
49
  - `allow_static_override`, `assign_public_ip`, `active`, `dhcp_server`
47
50
  - `network_domain_id`, `search_domains`, `network_proxy_id`
@@ -18,7 +18,6 @@ Use this guide in two ways:
18
18
  8. [Advanced Cookbook](./advanced-cookbook.md)
19
19
  9. [API Overview](./api-overview.md)
20
20
  10. [API Reference](./api-reference/index.md)
21
- 11. [Docstring Style Standard](./docstring-style.md)
22
21
 
23
22
  ## Who This Is For
24
23
 
@@ -33,21 +33,25 @@ print([(t.id, t.name) for t in network_types[:5]])
33
33
 
34
34
  ## Create a Network
35
35
 
36
+ An OpenStack network requires an OpenStack `type_id` and a `resource_pool_id` (the numeric pool ID — the network is created inside that resource pool). Pick a private network type and a pool:
37
+
36
38
  ```python
37
39
  networks = cloud.networks.list(cloud_id=cloud_id)
38
40
  for network in networks[:5]:
39
41
  print(network.id, network.name, network.cidr)
40
42
 
43
+ net_type = next(t for t in network_types if t.code == "openstackPrivate")
44
+ pool = cloud.instances.get_resource_pool("my-project", group=group.name)
45
+
41
46
  new_network = cloud.networks.create(
42
47
  name="mtn-prod-net",
43
48
  cloud_id=cloud_id, # from group.cloud_ids[0]
44
49
  group_id=group.id, # from groups.get_by_name(...)
45
- type_id=network_types[0].id, # from list_types(openstack_only=True)
50
+ type_id=net_type.id, # an OpenStack type from list_types(openstack_only=True)
51
+ resource_pool_id=pool.id, # numeric pool ID from get_resource_pool(...)
46
52
  cidr="10.42.10.0/24",
47
53
  gateway="10.42.10.1",
48
54
  dns_primary="8.8.8.8",
49
- visibility="private",
50
- dhcp_server=True, # enables automatic IP assignment
51
55
  )
52
56
 
53
57
  print(new_network.id, new_network.name)
@@ -57,11 +57,28 @@ One tenancy can have multiple resource pools — useful for separating dev, stag
57
57
 
58
58
  Security groups are the firewall layer for your VMs. Without one, you will not be able to SSH or RDP in after provisioning.
59
59
 
60
- 1. Go to **Infrastructure → Network → Security Groups** and create a group.
61
- 2. Add an inbound rule: SSH port 22 (TCP) for Linux VMs, or RDP port 3389 (TCP) for Windows VMs.
62
- 3. Restrict the source to your IP range, e.g., `200.200.113.15/32` for a single machine.
60
+ You can create one either in the console (**Infrastructure → Network → Security Groups**) or directly with the SDK:
63
61
 
64
- The group name (e.g., `"default"`) is passed as the `security_group` parameter when creating an instance.
62
+ ```python
63
+ # Create the group
64
+ sg = cloud.security_groups.create(
65
+ name="web-servers",
66
+ description="SSH + HTTPS access",
67
+ )
68
+
69
+ # Allow SSH from a single machine (use 3389/tcp instead for Windows RDP)
70
+ cloud.security_groups.create_rule(
71
+ sg.id,
72
+ name="allow-ssh",
73
+ direction="ingress",
74
+ protocol="tcp",
75
+ port_range="22",
76
+ source_type="cidr",
77
+ source="200.200.113.15/32", # restrict to your IP range
78
+ )
79
+ ```
80
+
81
+ The group name (e.g., `"web-servers"` or `"default"`) is passed as the `security_group` parameter when creating an instance. See the [Security Groups guide](./security-groups.md) for the full workflow.
65
82
 
66
83
  Optionally, add an SSH key pair under **User Settings → SSH Keys** for key-based access.
67
84
 
@@ -77,18 +94,30 @@ Keep the credentials from that email — you will pass them directly into the SD
77
94
 
78
95
  ## 5. Discover Core Reference Data
79
96
 
80
- Before creating resources, look up the valid IDs and codes for your account. These are tenant-specific and cannot be copied from documentation or other accounts.
97
+ Before creating resources, look up the valid IDs and codes for your account. These are tenant-specific and cannot be copied from documentation or other accounts. Everything below uses permission-safe endpoints — the admin-level `clouds.list()` and `plans.list()` are restricted on most tenant accounts and aren't needed.
81
98
 
82
99
  ```python
83
- groups = cloud.groups.list()
84
- clouds = cloud.clouds.list_openstack()
85
- instance_types = cloud.instance_types.list_os()
86
- plans = cloud.plans.list()
87
-
88
- print("Groups:", [(g.id, g.name) for g in groups[:5]])
89
- print("Clouds:", [(c.id, c.name) for c in clouds[:5]])
90
- print("Instance types:", [(t.code, t.default_layout_id) for t in instance_types[:5]])
91
- print("Plans:", [(p.id, p.name, p.cores, p.memory_gb) for p in plans[:5]])
100
+ # Group — also carries the cloud/zone IDs you need
101
+ group = cloud.groups.get_by_name("MTNNG_CLOUD_AZ_1")
102
+ cloud_id = group.cloud_ids[0]
103
+
104
+ # Instance type — carries its own default_layout_id
105
+ itype = cloud.instance_types.get_by_code("MTN-CS10")
106
+
107
+ # Resource pool — the resource_pool_id, where the instance is hosted
108
+ pools = cloud.instances.list_resource_pools(group=group.name)
109
+
110
+ # Service plans — CPU/memory/storage tiers for this zone + layout
111
+ plans = cloud.instances.list_service_plans(
112
+ zone_id=cloud_id,
113
+ layout_id=itype.default_layout_id,
114
+ group_id=group.id,
115
+ )
116
+
117
+ print("group:", group.id, "cloud_id:", cloud_id)
118
+ print("layout:", itype.default_layout_id)
119
+ print("pools:", [(p.code, p.name) for p in pools[:5]])
120
+ print("plans:", [(pl["id"], pl["name"]) for pl in plans[:5]])
92
121
  ```
93
122
 
94
123
  MTN Cloud currently has two live zones: **Lagos AZ1** (`MTNNG_CLOUD_AZ_1`) and **Lagos AZ2** (`MTNNG_CLOUD_AZ_2`).
@@ -105,9 +134,9 @@ instance = cloud.instances.create(
105
134
  group="MTNNG_CLOUD_AZ_1",
106
135
  layout=327,
107
136
  plan=6923,
108
- resource_pool_id="pool-214", # from your project order
137
+ resource_pool_id=pools[0].code, # from list_resource_pools() above
109
138
  availability_zone="Lagos-AZ-1-fd1",
110
- security_group="default", # security group name from the console
139
+ security_group="web-servers", # the security group you created above
111
140
  )
112
141
 
113
142
  print(instance.id, instance.name, instance.status, instance.primary_ip)
@@ -1,3 +1,3 @@
1
1
  """Package version metadata."""
2
2
 
3
- __version__ = "0.2.19"
3
+ __version__ = "0.3.1"
@@ -7,7 +7,7 @@ from datetime import datetime
7
7
  from typing import Any, Generic, TypeVar
8
8
 
9
9
  from pydantic import BaseModel as PydanticBaseModel
10
- from pydantic import ConfigDict, Field
10
+ from pydantic import ConfigDict, Field, field_validator
11
11
 
12
12
 
13
13
  class BaseModel(PydanticBaseModel):
@@ -28,6 +28,12 @@ class BaseModel(PydanticBaseModel):
28
28
  from_attributes=True,
29
29
  )
30
30
 
31
+ @field_validator("labels", mode="before", check_fields=False)
32
+ @classmethod
33
+ def _coerce_null_labels(cls, value: Any) -> Any:
34
+ """Treat a null ``labels`` field from the API as an empty list."""
35
+ return [] if value is None else value
36
+
31
37
 
32
38
  class Resource(BaseModel):
33
39
  """
@@ -238,6 +238,7 @@ class NetworkCreate(BaseModel):
238
238
  cloud_id: int
239
239
  group_id: int
240
240
  type_id: int | None = None
241
+ resource_pool_id: int | None = None
241
242
  display_name: str | None = Field(default=None, alias="displayName")
242
243
  labels: list[str] | None = None
243
244
  description: str | None = None
@@ -272,6 +273,8 @@ class NetworkCreate(BaseModel):
272
273
 
273
274
  if self.type_id is not None:
274
275
  network["type"] = {"id": self.type_id}
276
+ if self.resource_pool_id is not None:
277
+ network["zonePool"] = {"id": self.resource_pool_id}
275
278
  if self.display_name is not None:
276
279
  network["displayName"] = self.display_name
277
280
  if self.labels is not None:
@@ -135,6 +135,7 @@ class NetworksResource(BaseResource[Network]):
135
135
  cloud_id: int,
136
136
  group_id: int,
137
137
  type_id: int | None = None,
138
+ resource_pool_id: int | None = None,
138
139
  display_name: str | None = None,
139
140
  labels: List[str] | None = None,
140
141
  description: str | None = None,
@@ -162,6 +163,10 @@ class NetworksResource(BaseResource[Network]):
162
163
  """
163
164
  Create a network using MTN OpenStack-compatible fields.
164
165
 
166
+ OpenStack network types (e.g. ``openstackPrivate``) require both an
167
+ OpenStack ``type_id`` from ``list_types(openstack_only=True)`` and a
168
+ ``resource_pool_id`` (the numeric pool ID from ``get_resource_pool``).
169
+
165
170
  Notes:
166
171
  - Non-OpenStack provider-specific `config` variants are intentionally excluded.
167
172
  - Use integer IDs for cloud/group/type to keep payload typing uniform.
@@ -171,6 +176,7 @@ class NetworksResource(BaseResource[Network]):
171
176
  cloud_id=cloud_id,
172
177
  group_id=group_id,
173
178
  type_id=type_id,
179
+ resource_pool_id=resource_pool_id,
174
180
  display_name=display_name,
175
181
  labels=labels,
176
182
  description=description,
@@ -295,7 +301,7 @@ class NetworksResource(BaseResource[Network]):
295
301
  phrase: Optional phrase filter
296
302
  openstack_only: Return only network types tagged for OpenStack
297
303
  """
298
- params: dict[str, Any] = {}
304
+ params: dict[str, Any] = {"max": 500}
299
305
  if name:
300
306
  params["name"] = name
301
307
  if code:
@@ -115,6 +115,7 @@ class TestNetworkPayloadModels:
115
115
  ("network.cidr", "192.168.10.0/24"),
116
116
  ("network.dnsPrimary", "8.8.8.8"),
117
117
  ("network.visibility", "private"),
118
+ ("network.zonePool.id", 230),
118
119
  ("network.tenants", [{"id": 1}, {"id": 2}]),
119
120
  ("network.resourcePermission.all", True),
120
121
  ],
@@ -126,6 +127,7 @@ class TestNetworkPayloadModels:
126
127
  cloud_id=1,
127
128
  group_id=10,
128
129
  type_id=7,
130
+ resource_pool_id=230,
129
131
  cidr="192.168.10.0/24",
130
132
  gateway="192.168.10.1",
131
133
  dns_primary="8.8.8.8",
@@ -136,6 +138,12 @@ class TestNetworkPayloadModels:
136
138
 
137
139
  assert nested_value(payload, path) == expected
138
140
 
141
+ def test_network_accepts_null_labels(self) -> None:
142
+ """Coerce a null labels field from the API into an empty list."""
143
+ network = Network.model_validate({"id": 1, "name": "n", "labels": None})
144
+
145
+ assert network.labels == []
146
+
139
147
  @pytest.mark.parametrize(
140
148
  ("path", "expected"),
141
149
  [
@@ -1,50 +0,0 @@
1
- # Docstring Style Standard
2
-
3
- This SDK uses a uniform docstring style for public classes and methods.
4
-
5
- ## Goals
6
-
7
- - Fast scanning for beginners.
8
- - Predictable structure for advanced users.
9
- - Consistent IDE help and generated documentation output.
10
-
11
- ## Method Template
12
-
13
- ```python
14
- def example(arg1: str, arg2: int | None = None) -> bool:
15
- """One-line summary.
16
-
17
- Optional short context paragraph.
18
-
19
- Args:
20
- arg1: Required value description.
21
- arg2: Optional value description.
22
-
23
- Returns:
24
- Description of return value.
25
-
26
- Raises:
27
- ValueError: If input validation fails.
28
- MTNCloudError: If the API request fails.
29
- """
30
- ```
31
-
32
- ## Class Template
33
-
34
- ```python
35
- class Example:
36
- """One-line summary.
37
-
38
- Explain what the class represents and when it should be used.
39
- """
40
- ```
41
-
42
- ## Rules
43
-
44
- 1. Start with an imperative one-line summary.
45
- 2. Use `Args`, `Returns`, and `Raises` on public methods.
46
- 3. Include examples only when they add clarity.
47
- 4. Avoid Markdown code fences inside docstrings; prefer plain text blocks.
48
- 5. Keep docstrings accurate to the current method signature.
49
- 6. Prefer exact resource names used by the SDK (`group`, `cloud`, `archive bucket`).
50
-
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes