pyixapi 0.2.8__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.
- {pyixapi-0.2.8 → pyixapi-0.3.1}/PKG-INFO +3 -2
- {pyixapi-0.2.8 → pyixapi-0.3.1}/README.md +1 -0
- {pyixapi-0.2.8 → pyixapi-0.3.1}/pyixapi/core/api.py +62 -2
- {pyixapi-0.2.8 → pyixapi-0.3.1}/pyixapi/core/query.py +4 -17
- {pyixapi-0.2.8 → pyixapi-0.3.1}/pyixapi/core/response.py +34 -26
- {pyixapi-0.2.8 → pyixapi-0.3.1}/pyixapi/core/token.py +0 -3
- {pyixapi-0.2.8 → pyixapi-0.3.1}/pyixapi/core/util.py +8 -8
- {pyixapi-0.2.8 → pyixapi-0.3.1}/pyixapi/models.py +89 -0
- pyixapi-0.3.1/pyproject.toml +100 -0
- pyixapi-0.2.8/pyproject.toml → pyixapi-0.3.1/pyproject.toml.orig +4 -8
- {pyixapi-0.2.8 → pyixapi-0.3.1}/pyixapi/__init__.py +0 -0
- {pyixapi-0.2.8 → pyixapi-0.3.1}/pyixapi/core/__init__.py +0 -0
- {pyixapi-0.2.8 → pyixapi-0.3.1}/pyixapi/core/endpoint.py +0 -0
- {pyixapi-0.2.8 → pyixapi-0.3.1}/pyixapi/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pyixapi
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.1
|
|
4
4
|
Summary: Python API client library for IX-API
|
|
5
5
|
Author: Guillaume Mazoyer
|
|
6
6
|
Author-email: Guillaume Mazoyer <oss@mazoyer.eu>
|
|
@@ -12,7 +12,7 @@ Classifier: Programming Language :: Python :: 3.11
|
|
|
12
12
|
Classifier: Programming Language :: Python :: 3.12
|
|
13
13
|
Classifier: Programming Language :: Python :: 3.13
|
|
14
14
|
Classifier: Programming Language :: Python :: 3.14
|
|
15
|
-
Requires-Dist: pyjwt>=2.4.0,<2.
|
|
15
|
+
Requires-Dist: pyjwt>=2.4.0,<2.15
|
|
16
16
|
Requires-Dist: requests>=2.32.4,<3.0
|
|
17
17
|
Requires-Python: >=3.10
|
|
18
18
|
Description-Content-Type: text/markdown
|
|
@@ -36,6 +36,7 @@ To begin, import pyixapi and instantiate the API.
|
|
|
36
36
|
|
|
37
37
|
```python
|
|
38
38
|
import pyixapi
|
|
39
|
+
|
|
39
40
|
ixapi = pyixapi.api(
|
|
40
41
|
"https://api.de-cix.net/api/v2/",
|
|
41
42
|
"3LH3G72VH7H1SGogEsFeQOPsGjOQotMUZQRt2pK7YbH",
|
|
@@ -12,6 +12,7 @@ from pyixapi.models import (
|
|
|
12
12
|
IP,
|
|
13
13
|
MAC,
|
|
14
14
|
Account,
|
|
15
|
+
AvailabilityZone,
|
|
15
16
|
Connection,
|
|
16
17
|
Contact,
|
|
17
18
|
Demarc,
|
|
@@ -30,9 +31,10 @@ from pyixapi.models import (
|
|
|
30
31
|
ProductOffering,
|
|
31
32
|
Role,
|
|
32
33
|
RoleAssignment,
|
|
34
|
+
RoutingFunction,
|
|
33
35
|
)
|
|
34
36
|
|
|
35
|
-
__version__ = "0.
|
|
37
|
+
__version__ = "0.3.1"
|
|
36
38
|
|
|
37
39
|
|
|
38
40
|
class API(object):
|
|
@@ -61,11 +63,11 @@ class API(object):
|
|
|
61
63
|
self.http_session = requests.Session()
|
|
62
64
|
self.user_agent = user_agent
|
|
63
65
|
self.proxies = proxies
|
|
66
|
+
self._version: int | None = None
|
|
64
67
|
|
|
65
68
|
self.auth = Endpoint(self, "auth")
|
|
66
69
|
self.connections = Endpoint(self, "connections", model=Connection)
|
|
67
70
|
self.contacts = Endpoint(self, "contacts", model=Contact)
|
|
68
|
-
self.demarcs = Endpoint(self, "demarcs", model=Demarc)
|
|
69
71
|
self.devices = Endpoint(self, "devices", model=Device)
|
|
70
72
|
self.facilities = Endpoint(self, "facilities", model=Facility)
|
|
71
73
|
self.ips = Endpoint(self, "ips", model=IP)
|
|
@@ -76,6 +78,7 @@ class API(object):
|
|
|
76
78
|
self.network_services = Endpoint(self, "network-services", model=NetworkService)
|
|
77
79
|
self.pops = Endpoint(self, "pops", model=PoP)
|
|
78
80
|
# Version 2+
|
|
81
|
+
self.availability_zones = Endpoint(self, "availability-zones", model=AvailabilityZone)
|
|
79
82
|
self.member_joining_rules = Endpoint(self, "member-joining-rules", model=MemberJoiningRule)
|
|
80
83
|
self.metro_areas = Endpoint(self, "metro-areas", model=MetroArea)
|
|
81
84
|
self.metro_area_networks = Endpoint(self, "metro-area-networks", model=MetroAreaNetwork)
|
|
@@ -83,12 +86,19 @@ class API(object):
|
|
|
83
86
|
self.port_reservations = Endpoint(self, "port-reservations", model=PortReservation)
|
|
84
87
|
self.roles = Endpoint(self, "roles", model=Role)
|
|
85
88
|
self.role_assignments = Endpoint(self, "role-assignments", model=RoleAssignment)
|
|
89
|
+
self.routing_functions = Endpoint(self, "routing-functions", model=RoutingFunction)
|
|
86
90
|
|
|
87
91
|
@property
|
|
88
92
|
def version(self) -> int:
|
|
89
93
|
"""
|
|
90
94
|
Get the API version of IX-API.
|
|
95
|
+
|
|
96
|
+
The version is resolved once on first access and cached for the lifetime
|
|
97
|
+
of the API instance, as it does not change between requests.
|
|
91
98
|
"""
|
|
99
|
+
if self._version is not None:
|
|
100
|
+
return self._version
|
|
101
|
+
|
|
92
102
|
version = Request(
|
|
93
103
|
base=self.url,
|
|
94
104
|
token=self.access_token,
|
|
@@ -104,12 +114,19 @@ class API(object):
|
|
|
104
114
|
stacklevel=2,
|
|
105
115
|
)
|
|
106
116
|
|
|
117
|
+
self._version = version
|
|
107
118
|
return version
|
|
108
119
|
|
|
109
120
|
@property
|
|
110
121
|
def accounts(self) -> Endpoint:
|
|
111
122
|
return Endpoint(self, "customers" if self.version == 1 else "accounts", model=Account)
|
|
112
123
|
|
|
124
|
+
@property
|
|
125
|
+
def demarcs(self) -> Endpoint:
|
|
126
|
+
if self.version != 1:
|
|
127
|
+
raise AttributeError("demarcs endpoint is only available in IX-API v1")
|
|
128
|
+
return Endpoint(self, "demarcs", model=Demarc)
|
|
129
|
+
|
|
113
130
|
@property
|
|
114
131
|
def product_offerings(self) -> Endpoint:
|
|
115
132
|
return Endpoint(
|
|
@@ -118,6 +135,21 @@ class API(object):
|
|
|
118
135
|
model=ProductOffering,
|
|
119
136
|
)
|
|
120
137
|
|
|
138
|
+
def account(self) -> Account:
|
|
139
|
+
"""
|
|
140
|
+
Get the authenticated user's own account.
|
|
141
|
+
|
|
142
|
+
Available in IX-API 2 or newer.
|
|
143
|
+
"""
|
|
144
|
+
r = Request(
|
|
145
|
+
base=cat(self.url, "account"),
|
|
146
|
+
token=self.access_token,
|
|
147
|
+
http_session=self.http_session,
|
|
148
|
+
user_agent=self.user_agent,
|
|
149
|
+
proxies=self.proxies,
|
|
150
|
+
)
|
|
151
|
+
return Account(r._make_call(), self, self.accounts)
|
|
152
|
+
|
|
121
153
|
def authenticate(self) -> Record | None:
|
|
122
154
|
"""
|
|
123
155
|
Authenticate and generate a pair of tokens.
|
|
@@ -168,6 +200,20 @@ class API(object):
|
|
|
168
200
|
|
|
169
201
|
return Record(r, self, self.auth)
|
|
170
202
|
|
|
203
|
+
def extensions(self) -> list[dict[str, Any]]:
|
|
204
|
+
"""
|
|
205
|
+
Get the list of extensions supported by the IX-API implementation.
|
|
206
|
+
|
|
207
|
+
Available in IX-API 2 or newer.
|
|
208
|
+
"""
|
|
209
|
+
return Request(
|
|
210
|
+
base=cat(self.url, "extensions"),
|
|
211
|
+
token=self.access_token,
|
|
212
|
+
http_session=self.http_session,
|
|
213
|
+
user_agent=self.user_agent,
|
|
214
|
+
proxies=self.proxies,
|
|
215
|
+
)._make_call()
|
|
216
|
+
|
|
171
217
|
def health(self) -> dict[str, Any]:
|
|
172
218
|
"""
|
|
173
219
|
Get the health information from IX-API.
|
|
@@ -184,3 +230,17 @@ class API(object):
|
|
|
184
230
|
user_agent=self.user_agent,
|
|
185
231
|
proxies=self.proxies,
|
|
186
232
|
).get_health()
|
|
233
|
+
|
|
234
|
+
def implementation(self) -> dict[str, Any]:
|
|
235
|
+
"""
|
|
236
|
+
Get implementation details of the IX-API server.
|
|
237
|
+
|
|
238
|
+
Available in IX-API 2 or newer.
|
|
239
|
+
"""
|
|
240
|
+
return Request(
|
|
241
|
+
base=cat(self.url, "implementation"),
|
|
242
|
+
token=self.access_token,
|
|
243
|
+
http_session=self.http_session,
|
|
244
|
+
user_agent=self.user_agent,
|
|
245
|
+
proxies=self.proxies,
|
|
246
|
+
)._make_call()
|
|
@@ -59,10 +59,8 @@ class Request(object):
|
|
|
59
59
|
Responsible for building the URL and making the HTTP(S) requests to the API.
|
|
60
60
|
|
|
61
61
|
:param base: (str) Base URL passed in api() instantiation.
|
|
62
|
-
:param filters: (dict, optional)
|
|
63
|
-
|
|
64
|
-
In (e.g. /api/v1/devices?name='test') 'name': 'test' would be in the filters
|
|
65
|
-
dict.
|
|
62
|
+
:param filters: (dict, optional) key/value pairs matching the filters an
|
|
63
|
+
endpoint accepts, e.g. {"name": "test"} for /devices?name=test.
|
|
66
64
|
"""
|
|
67
65
|
|
|
68
66
|
def __init__(
|
|
@@ -84,17 +82,6 @@ class Request(object):
|
|
|
84
82
|
self.user_agent = user_agent
|
|
85
83
|
self.proxies = proxies
|
|
86
84
|
|
|
87
|
-
def get_openapi(self) -> dict[str, Any]:
|
|
88
|
-
"""
|
|
89
|
-
Get the OpenAPI Spec.
|
|
90
|
-
"""
|
|
91
|
-
headers = {"Content-Type": "application/json;"}
|
|
92
|
-
req = self.http_session.get(cat(self.base, "docs/?format=openapi"), headers=headers)
|
|
93
|
-
if req.ok:
|
|
94
|
-
return req.json()
|
|
95
|
-
else:
|
|
96
|
-
raise RequestError(req)
|
|
97
|
-
|
|
98
85
|
def get_version(self) -> int:
|
|
99
86
|
"""
|
|
100
87
|
Get the API version of IX-API.
|
|
@@ -129,7 +116,7 @@ class Request(object):
|
|
|
129
116
|
add_params: dict[str, Any] | None = None,
|
|
130
117
|
data: dict[str, Any] | None = None,
|
|
131
118
|
) -> Any:
|
|
132
|
-
if verb in ("post", "put") or verb == "delete" and data:
|
|
119
|
+
if verb in ("post", "put") or (verb == "delete" and data):
|
|
133
120
|
headers: dict[str, str] = {"Content-Type": "application/json;"}
|
|
134
121
|
else:
|
|
135
122
|
headers = {"accept": "application/json;"}
|
|
@@ -184,7 +171,7 @@ class Request(object):
|
|
|
184
171
|
for i in req:
|
|
185
172
|
yield i
|
|
186
173
|
else:
|
|
187
|
-
self.count =
|
|
174
|
+
self.count = 1
|
|
188
175
|
yield req
|
|
189
176
|
|
|
190
177
|
def put(self, data: dict[str, Any]) -> dict[str, Any]:
|
|
@@ -3,7 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
from typing import TYPE_CHECKING, Any, Iterator
|
|
4
4
|
|
|
5
5
|
from pyixapi.core.query import Request
|
|
6
|
-
from pyixapi.core.util import Hashabledict
|
|
6
|
+
from pyixapi.core.util import Hashabledict, cat
|
|
7
7
|
|
|
8
8
|
if TYPE_CHECKING:
|
|
9
9
|
from pyixapi.core.api import API
|
|
@@ -90,11 +90,11 @@ class Record(object):
|
|
|
90
90
|
Create Python objects from IX-API responses.
|
|
91
91
|
|
|
92
92
|
Nested dicts that represent other endpoints are also turned into
|
|
93
|
-
:py:class:`.Record` objects
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
93
|
+
:py:class:`.Record` objects (when the corresponding model declares them as a
|
|
94
|
+
class attribute). All fields are then assigned to the object's attributes.
|
|
95
|
+
|
|
96
|
+
Only the fields present in the response are set as attributes; accessing a
|
|
97
|
+
field that was not returned raises :py:exc:`AttributeError`.
|
|
98
98
|
|
|
99
99
|
:examples:
|
|
100
100
|
Default representation of the object is usually its ID and/or name:
|
|
@@ -149,7 +149,6 @@ class Record(object):
|
|
|
149
149
|
url: str | None = None
|
|
150
150
|
|
|
151
151
|
def __init__(self, values: dict[str, Any], api: API, endpoint: Endpoint) -> None:
|
|
152
|
-
self._full_cache: list[Any] = []
|
|
153
152
|
self._init_cache: list[tuple[str, Any]] = []
|
|
154
153
|
self.api = api
|
|
155
154
|
self.default_ret: type[Record] = Record
|
|
@@ -179,12 +178,6 @@ class Record(object):
|
|
|
179
178
|
def __repr__(self) -> str:
|
|
180
179
|
return str(dict(self))
|
|
181
180
|
|
|
182
|
-
def __getstate__(self) -> dict[str, Any]:
|
|
183
|
-
return self.__dict__
|
|
184
|
-
|
|
185
|
-
def __setstate__(self, d: dict[str, Any]) -> None:
|
|
186
|
-
self.__dict__.update(d)
|
|
187
|
-
|
|
188
181
|
def __key__(self) -> tuple[str, ...] | tuple[str]:
|
|
189
182
|
if hasattr(self, "id"):
|
|
190
183
|
return (self.endpoint.name, self.id)
|
|
@@ -212,8 +205,7 @@ class Record(object):
|
|
|
212
205
|
if isinstance(list_item, dict):
|
|
213
206
|
lookup = getattr(self.__class__, key_name, None)
|
|
214
207
|
if not isinstance(lookup, list):
|
|
215
|
-
#
|
|
216
|
-
# a list (or is not defined), just return the default model
|
|
208
|
+
# Field not declared as a list model: use the default Record.
|
|
217
209
|
return self.default_ret(list_item, self.api, self.endpoint)
|
|
218
210
|
else:
|
|
219
211
|
model = lookup[0]
|
|
@@ -259,16 +251,18 @@ class Record(object):
|
|
|
259
251
|
return r
|
|
260
252
|
|
|
261
253
|
def _diff(self) -> set[str]:
|
|
262
|
-
def
|
|
254
|
+
def make_hashable(v: Any) -> Any:
|
|
255
|
+
# Hashable, structure-preserving form for set comparison. Lists become
|
|
256
|
+
# tuples, not a joined string (which would collide).
|
|
263
257
|
if isinstance(v, dict):
|
|
264
|
-
return k,
|
|
258
|
+
return Hashabledict({k: make_hashable(val) for k, val in v.items()})
|
|
265
259
|
if isinstance(v, list):
|
|
266
|
-
return
|
|
267
|
-
return
|
|
260
|
+
return tuple(make_hashable(i) for i in v)
|
|
261
|
+
return v
|
|
268
262
|
|
|
269
|
-
current =
|
|
270
|
-
init =
|
|
271
|
-
return
|
|
263
|
+
current = {k: make_hashable(v) for k, v in self.serialize().items()}
|
|
264
|
+
init = {k: make_hashable(v) for k, v in self.serialize(init=True).items()}
|
|
265
|
+
return {key for key, _ in set(current.items()) ^ set(init.items())}
|
|
272
266
|
|
|
273
267
|
def updates(self) -> dict[str, Any]:
|
|
274
268
|
"""
|
|
@@ -284,6 +278,15 @@ class Record(object):
|
|
|
284
278
|
return {i: serialized[i] for i in diff}
|
|
285
279
|
return {}
|
|
286
280
|
|
|
281
|
+
def _make_request(self, sub_path: str) -> Request:
|
|
282
|
+
return Request(
|
|
283
|
+
base=cat(self.endpoint.url, self.id, sub_path),
|
|
284
|
+
token=self.api.access_token,
|
|
285
|
+
http_session=self.api.http_session,
|
|
286
|
+
user_agent=self.api.user_agent,
|
|
287
|
+
proxies=self.api.proxies,
|
|
288
|
+
)
|
|
289
|
+
|
|
287
290
|
def save(self) -> bool:
|
|
288
291
|
"""
|
|
289
292
|
Save changes to an existing object.
|
|
@@ -298,10 +301,15 @@ class Record(object):
|
|
|
298
301
|
base=self.endpoint.url,
|
|
299
302
|
token=self.api.access_token,
|
|
300
303
|
http_session=self.api.http_session,
|
|
301
|
-
user_agent=self.user_agent,
|
|
304
|
+
user_agent=self.api.user_agent,
|
|
302
305
|
proxies=self.api.proxies,
|
|
303
306
|
)
|
|
304
|
-
|
|
307
|
+
result = r.patch(updates)
|
|
308
|
+
if result:
|
|
309
|
+
# Refresh the record from the server response
|
|
310
|
+
if isinstance(result, dict):
|
|
311
|
+
self._init_cache = []
|
|
312
|
+
self._parse_values(result)
|
|
305
313
|
return True
|
|
306
314
|
return False
|
|
307
315
|
|
|
@@ -326,7 +334,7 @@ class Record(object):
|
|
|
326
334
|
base=self.endpoint.url,
|
|
327
335
|
token=self.api.access_token,
|
|
328
336
|
http_session=self.api.http_session,
|
|
329
|
-
user_agent=self.user_agent,
|
|
337
|
+
user_agent=self.api.user_agent,
|
|
330
338
|
proxies=self.api.proxies,
|
|
331
339
|
)
|
|
332
|
-
return
|
|
340
|
+
return r.delete()
|
|
@@ -15,16 +15,16 @@ def cat(*args: Any, separator: str = "/", trailing: str = "") -> str:
|
|
|
15
15
|
|
|
16
16
|
If an item cannot be parsed as a string, an AttributeError will be raised.
|
|
17
17
|
|
|
18
|
-
>>>
|
|
19
|
-
'a/b/c/'
|
|
20
|
-
>>> concatenate("a", "b", "/c/", separator="")
|
|
18
|
+
>>> cat("a", "/b/", "c/")
|
|
21
19
|
'a/b/c'
|
|
22
|
-
>>>
|
|
23
|
-
'a/
|
|
24
|
-
>>>
|
|
25
|
-
'
|
|
20
|
+
>>> cat("a", 1, "b")
|
|
21
|
+
'a/1/b'
|
|
22
|
+
>>> cat("a", "b", "c", separator="_")
|
|
23
|
+
'a_b_c'
|
|
24
|
+
>>> cat("a", "b", "c", trailing="/")
|
|
25
|
+
'a/b/c/'
|
|
26
26
|
"""
|
|
27
|
-
s = separator.join([str(i).
|
|
27
|
+
s = separator.join([str(i).strip(separator) for i in args if str(i)])
|
|
28
28
|
if trailing:
|
|
29
29
|
s += trailing
|
|
30
30
|
return s
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import ipaddress
|
|
2
|
+
from typing import Any
|
|
2
3
|
|
|
3
4
|
from pyixapi.core.response import Record
|
|
4
5
|
|
|
@@ -24,6 +25,21 @@ class Connection(Record):
|
|
|
24
25
|
def __str__(self) -> str:
|
|
25
26
|
return f"{self.id}: {self.name}"
|
|
26
27
|
|
|
28
|
+
def cancellation_policy(self) -> dict[str, Any]:
|
|
29
|
+
return self._make_request("cancellation-policy")._make_call()
|
|
30
|
+
|
|
31
|
+
def get_loa(self) -> dict[str, Any]:
|
|
32
|
+
return self._make_request("loa")._make_call()
|
|
33
|
+
|
|
34
|
+
def upload_loa(self, data: dict[str, Any]) -> dict[str, Any]:
|
|
35
|
+
return self._make_request("loa")._make_call(verb="post", data=data)
|
|
36
|
+
|
|
37
|
+
def statistics(self, **kwargs: Any) -> dict[str, Any]:
|
|
38
|
+
return self._make_request("statistics")._make_call(add_params=kwargs or None)
|
|
39
|
+
|
|
40
|
+
def statistics_timeseries(self, aggregate: str, **kwargs: Any) -> dict[str, Any]:
|
|
41
|
+
return self._make_request(f"statistics/{aggregate}/timeseries")._make_call(add_params=kwargs or None)
|
|
42
|
+
|
|
27
43
|
|
|
28
44
|
class Contact(Record):
|
|
29
45
|
"""
|
|
@@ -133,6 +149,21 @@ class NetworkServiceConfig(Record):
|
|
|
133
149
|
def __str__(self) -> str:
|
|
134
150
|
return self.id
|
|
135
151
|
|
|
152
|
+
def cancellation_policy(self) -> dict[str, Any]:
|
|
153
|
+
return self._make_request("cancellation-policy")._make_call()
|
|
154
|
+
|
|
155
|
+
def peer_statistics(self, **kwargs: Any) -> dict[str, Any]:
|
|
156
|
+
return self._make_request("peer-statistics")._make_call(add_params=kwargs or None)
|
|
157
|
+
|
|
158
|
+
def peer_statistics_timeseries(self, aggregate: str, **kwargs: Any) -> dict[str, Any]:
|
|
159
|
+
return self._make_request(f"peer-statistics/{aggregate}/timeseries")._make_call(add_params=kwargs or None)
|
|
160
|
+
|
|
161
|
+
def statistics(self, **kwargs: Any) -> dict[str, Any]:
|
|
162
|
+
return self._make_request("statistics")._make_call(add_params=kwargs or None)
|
|
163
|
+
|
|
164
|
+
def statistics_timeseries(self, aggregate: str, **kwargs: Any) -> dict[str, Any]:
|
|
165
|
+
return self._make_request(f"statistics/{aggregate}/timeseries")._make_call(add_params=kwargs or None)
|
|
166
|
+
|
|
136
167
|
|
|
137
168
|
class NetworkService(Record):
|
|
138
169
|
"""
|
|
@@ -143,6 +174,27 @@ class NetworkService(Record):
|
|
|
143
174
|
def __str__(self) -> str:
|
|
144
175
|
return self.id
|
|
145
176
|
|
|
177
|
+
def cancellation_policy(self) -> dict[str, Any]:
|
|
178
|
+
return self._make_request("cancellation-policy")._make_call()
|
|
179
|
+
|
|
180
|
+
def change_request(self) -> dict[str, Any]:
|
|
181
|
+
return self._make_request("change-request")._make_call()
|
|
182
|
+
|
|
183
|
+
def create_change_request(self, data: dict[str, Any]) -> dict[str, Any]:
|
|
184
|
+
return self._make_request("change-request")._make_call(verb="post", data=data)
|
|
185
|
+
|
|
186
|
+
def delete_change_request(self) -> bool:
|
|
187
|
+
return self._make_request("change-request")._make_call(verb="delete")
|
|
188
|
+
|
|
189
|
+
def rtt_statistics(self, **kwargs: Any) -> dict[str, Any]:
|
|
190
|
+
return self._make_request("rtt-statistics")._make_call(add_params=kwargs or None)
|
|
191
|
+
|
|
192
|
+
def statistics(self, **kwargs: Any) -> dict[str, Any]:
|
|
193
|
+
return self._make_request("statistics")._make_call(add_params=kwargs or None)
|
|
194
|
+
|
|
195
|
+
def statistics_timeseries(self, aggregate: str, **kwargs: Any) -> dict[str, Any]:
|
|
196
|
+
return self._make_request(f"statistics/{aggregate}/timeseries")._make_call(add_params=kwargs or None)
|
|
197
|
+
|
|
146
198
|
|
|
147
199
|
class PoP(Record):
|
|
148
200
|
"""
|
|
@@ -168,6 +220,15 @@ class ProductOffering(Record):
|
|
|
168
220
|
# Version 2 and up
|
|
169
221
|
|
|
170
222
|
|
|
223
|
+
class AvailabilityZone(Record):
|
|
224
|
+
"""
|
|
225
|
+
An AvailabilityZone describes the physical location or zone of a resource.
|
|
226
|
+
"""
|
|
227
|
+
|
|
228
|
+
def __str__(self) -> str:
|
|
229
|
+
return self.name
|
|
230
|
+
|
|
231
|
+
|
|
171
232
|
class MemberJoiningRule(Record):
|
|
172
233
|
"""
|
|
173
234
|
A MemberJoiningRule defines a rule to allow or deny access for an Account to a
|
|
@@ -212,6 +273,12 @@ class Port(Record):
|
|
|
212
273
|
def __str__(self) -> str:
|
|
213
274
|
return self.name
|
|
214
275
|
|
|
276
|
+
def statistics(self, **kwargs: Any) -> dict[str, Any]:
|
|
277
|
+
return self._make_request("statistics")._make_call(add_params=kwargs or None)
|
|
278
|
+
|
|
279
|
+
def statistics_timeseries(self, aggregate: str, **kwargs: Any) -> dict[str, Any]:
|
|
280
|
+
return self._make_request(f"statistics/{aggregate}/timeseries")._make_call(add_params=kwargs or None)
|
|
281
|
+
|
|
215
282
|
|
|
216
283
|
class PortReservation(Record):
|
|
217
284
|
"""
|
|
@@ -226,6 +293,28 @@ class PortReservation(Record):
|
|
|
226
293
|
def __str__(self) -> str:
|
|
227
294
|
return self.id
|
|
228
295
|
|
|
296
|
+
def cancellation_policy(self) -> dict[str, Any]:
|
|
297
|
+
return self._make_request("cancellation-policy")._make_call()
|
|
298
|
+
|
|
299
|
+
def get_loa(self) -> dict[str, Any]:
|
|
300
|
+
return self._make_request("loa")._make_call()
|
|
301
|
+
|
|
302
|
+
def upload_loa(self, data: dict[str, Any]) -> dict[str, Any]:
|
|
303
|
+
return self._make_request("loa")._make_call(verb="post", data=data)
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
class RoutingFunction(Record):
|
|
307
|
+
"""
|
|
308
|
+
A RoutingFunction is a function that provides routing capabilities within a
|
|
309
|
+
network service.
|
|
310
|
+
"""
|
|
311
|
+
|
|
312
|
+
def __str__(self) -> str:
|
|
313
|
+
return self.id
|
|
314
|
+
|
|
315
|
+
def cancellation_policy(self) -> dict[str, Any]:
|
|
316
|
+
return self._make_request("cancellation-policy")._make_call()
|
|
317
|
+
|
|
229
318
|
|
|
230
319
|
class Role(Record):
|
|
231
320
|
"""
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "pyixapi"
|
|
3
|
+
version = "0.3.1"
|
|
4
|
+
description = "Python API client library for IX-API"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = "Apache-2.0"
|
|
7
|
+
requires-python = ">=3.10"
|
|
8
|
+
classifiers = [
|
|
9
|
+
"Intended Audience :: Developers",
|
|
10
|
+
"Programming Language :: Python :: 3",
|
|
11
|
+
"Programming Language :: Python :: 3.10",
|
|
12
|
+
"Programming Language :: Python :: 3.11",
|
|
13
|
+
"Programming Language :: Python :: 3.12",
|
|
14
|
+
"Programming Language :: Python :: 3.13",
|
|
15
|
+
"Programming Language :: Python :: 3.14",
|
|
16
|
+
]
|
|
17
|
+
dependencies = [
|
|
18
|
+
"PyJWT>=2.4.0,<2.15",
|
|
19
|
+
"requests>=2.32.4,<3.0",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
[[project.authors]]
|
|
23
|
+
name = "Guillaume Mazoyer"
|
|
24
|
+
email = "oss@mazoyer.eu"
|
|
25
|
+
|
|
26
|
+
[dependency-groups]
|
|
27
|
+
dev = [
|
|
28
|
+
"pytest",
|
|
29
|
+
"pytest-cov",
|
|
30
|
+
"ruff",
|
|
31
|
+
"ty",
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
[tool.ruff]
|
|
35
|
+
line-length = 120
|
|
36
|
+
exclude = [
|
|
37
|
+
".git",
|
|
38
|
+
".tox",
|
|
39
|
+
".venv",
|
|
40
|
+
"env",
|
|
41
|
+
"_build",
|
|
42
|
+
"build",
|
|
43
|
+
"dist",
|
|
44
|
+
"examples",
|
|
45
|
+
"__main__.py",
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
[tool.ruff.lint]
|
|
49
|
+
select = [
|
|
50
|
+
"C90",
|
|
51
|
+
"E",
|
|
52
|
+
"W",
|
|
53
|
+
"F",
|
|
54
|
+
"I",
|
|
55
|
+
"DTZ",
|
|
56
|
+
"ICN",
|
|
57
|
+
"TCH",
|
|
58
|
+
"T10",
|
|
59
|
+
"Q",
|
|
60
|
+
"YTT",
|
|
61
|
+
]
|
|
62
|
+
task-tags = [
|
|
63
|
+
"FIXME",
|
|
64
|
+
"TODO",
|
|
65
|
+
"XXX",
|
|
66
|
+
]
|
|
67
|
+
|
|
68
|
+
[tool.ruff.lint.isort]
|
|
69
|
+
known-first-party = ["pyixapi"]
|
|
70
|
+
|
|
71
|
+
[tool.ruff.lint.mccabe]
|
|
72
|
+
max-complexity = 31
|
|
73
|
+
|
|
74
|
+
[tool.ruff.format]
|
|
75
|
+
quote-style = "double"
|
|
76
|
+
indent-style = "space"
|
|
77
|
+
skip-magic-trailing-comma = false
|
|
78
|
+
line-ending = "auto"
|
|
79
|
+
|
|
80
|
+
[tool.ty.rules]
|
|
81
|
+
unresolved-attribute = "ignore"
|
|
82
|
+
invalid-return-type = "ignore"
|
|
83
|
+
unresolved-import = "ignore"
|
|
84
|
+
|
|
85
|
+
[tool.pytest.ini_options]
|
|
86
|
+
addopts = "--cov=pyixapi --cov-branch --cov-report=term-missing --cov-report=xml"
|
|
87
|
+
|
|
88
|
+
[tool.coverage.run]
|
|
89
|
+
source = ["pyixapi"]
|
|
90
|
+
omit = ["pyixapi/__main__.py"]
|
|
91
|
+
|
|
92
|
+
[tool.coverage.report]
|
|
93
|
+
exclude_lines = ["if TYPE_CHECKING:"]
|
|
94
|
+
|
|
95
|
+
[tool.uv.build-backend]
|
|
96
|
+
module-root = "."
|
|
97
|
+
|
|
98
|
+
[build-system]
|
|
99
|
+
requires = ["uv_build>=0.9.21,<0.13.0"]
|
|
100
|
+
build-backend = "uv_build"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "pyixapi"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.3.1"
|
|
4
4
|
description = "Python API client library for IX-API"
|
|
5
5
|
authors = [{ name = "Guillaume Mazoyer", email = "oss@mazoyer.eu" }]
|
|
6
6
|
readme = "README.md"
|
|
@@ -15,15 +15,11 @@ classifiers = [
|
|
|
15
15
|
"Programming Language :: Python :: 3.13",
|
|
16
16
|
"Programming Language :: Python :: 3.14",
|
|
17
17
|
]
|
|
18
|
-
dependencies = ["PyJWT>=2.4.0,<2.
|
|
18
|
+
dependencies = ["PyJWT>=2.4.0,<2.15", "requests>=2.32.4,<3.0"]
|
|
19
19
|
|
|
20
20
|
[dependency-groups]
|
|
21
21
|
dev = ["pytest", "pytest-cov", "ruff", "ty"]
|
|
22
22
|
|
|
23
|
-
[tool.poetry]
|
|
24
|
-
requires-poetry = ">=2.0"
|
|
25
|
-
packages = [{ include = "pyixapi" }]
|
|
26
|
-
|
|
27
23
|
[tool.ruff]
|
|
28
24
|
line-length = 120
|
|
29
25
|
exclude = [".git", ".tox", ".venv", "env", "_build", "build", "dist", "examples", "__main__.py"]
|
|
@@ -63,7 +59,7 @@ invalid-return-type = "ignore"
|
|
|
63
59
|
unresolved-import = "ignore"
|
|
64
60
|
|
|
65
61
|
[tool.pytest.ini_options]
|
|
66
|
-
addopts = "--cov=pyixapi --cov-report=term-missing --cov-report=xml"
|
|
62
|
+
addopts = "--cov=pyixapi --cov-branch --cov-report=term-missing --cov-report=xml"
|
|
67
63
|
|
|
68
64
|
[tool.coverage.run]
|
|
69
65
|
source = ["pyixapi"]
|
|
@@ -73,7 +69,7 @@ omit = ["pyixapi/__main__.py"]
|
|
|
73
69
|
exclude_lines = ["if TYPE_CHECKING:"]
|
|
74
70
|
|
|
75
71
|
[build-system]
|
|
76
|
-
requires = ["uv_build>=0.9.21,<0.
|
|
72
|
+
requires = ["uv_build>=0.9.21,<0.13.0"]
|
|
77
73
|
build-backend = "uv_build"
|
|
78
74
|
|
|
79
75
|
[tool.uv.build-backend]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|