pyixapi 0.2.6__tar.gz → 0.2.8__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.6 → pyixapi-0.2.8}/PKG-INFO +10 -14
- {pyixapi-0.2.6 → pyixapi-0.2.8}/README.md +2 -2
- {pyixapi-0.2.6 → pyixapi-0.2.8}/pyixapi/core/api.py +38 -35
- {pyixapi-0.2.6 → pyixapi-0.2.8}/pyixapi/core/endpoint.py +14 -7
- {pyixapi-0.2.6 → pyixapi-0.2.8}/pyixapi/core/query.py +38 -28
- {pyixapi-0.2.6 → pyixapi-0.2.8}/pyixapi/core/response.py +40 -32
- {pyixapi-0.2.6 → pyixapi-0.2.8}/pyixapi/core/token.py +1 -1
- {pyixapi-0.2.6 → pyixapi-0.2.8}/pyixapi/core/util.py +2 -2
- {pyixapi-0.2.6 → pyixapi-0.2.8}/pyixapi/models.py +24 -24
- pyixapi-0.2.8/pyixapi/py.typed +0 -0
- {pyixapi-0.2.6 → pyixapi-0.2.8}/pyproject.toml +26 -8
- pyixapi-0.2.6/LICENSE +0 -178
- {pyixapi-0.2.6 → pyixapi-0.2.8}/pyixapi/__init__.py +0 -0
- {pyixapi-0.2.6 → pyixapi-0.2.8}/pyixapi/core/__init__.py +0 -0
|
@@ -1,23 +1,20 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: pyixapi
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.8
|
|
4
4
|
Summary: Python API client library for IX-API
|
|
5
|
-
License: Apache-2.0
|
|
6
5
|
Author: Guillaume Mazoyer
|
|
7
|
-
Author-email: oss@mazoyer.eu
|
|
8
|
-
|
|
6
|
+
Author-email: Guillaume Mazoyer <oss@mazoyer.eu>
|
|
7
|
+
License-Expression: Apache-2.0
|
|
9
8
|
Classifier: Intended Audience :: Developers
|
|
10
9
|
Classifier: Programming Language :: Python :: 3
|
|
11
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
12
10
|
Classifier: Programming Language :: Python :: 3.10
|
|
13
11
|
Classifier: Programming Language :: Python :: 3.11
|
|
14
12
|
Classifier: Programming Language :: Python :: 3.12
|
|
15
13
|
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
-
|
|
17
|
-
Requires-Dist:
|
|
18
|
-
Requires-Dist:
|
|
19
|
-
Requires-
|
|
20
|
-
Requires-Dist: ruff (>=0.9,<0.10) ; extra == "dev"
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
15
|
+
Requires-Dist: pyjwt>=2.4.0,<2.13
|
|
16
|
+
Requires-Dist: requests>=2.32.4,<3.0
|
|
17
|
+
Requires-Python: >=3.10
|
|
21
18
|
Description-Content-Type: text/markdown
|
|
22
19
|
|
|
23
20
|
# pyixapi
|
|
@@ -26,7 +23,7 @@ Python API client library for [IX-API](https://ix-api.net/).
|
|
|
26
23
|
|
|
27
24
|
Supported versions are:
|
|
28
25
|
|
|
29
|
-
* [version 1](https://docs.ix-api.net/v1/)
|
|
26
|
+
* [version 1](https://docs.ix-api.net/v1/) -- deprecated, providers cease date is 2026-07-01
|
|
30
27
|
* [version 2](https://docs.ix-api.net/v2/)
|
|
31
28
|
|
|
32
29
|
## Installation
|
|
@@ -40,7 +37,7 @@ To begin, import pyixapi and instantiate the API.
|
|
|
40
37
|
```python
|
|
41
38
|
import pyixapi
|
|
42
39
|
ixapi = pyixapi.api(
|
|
43
|
-
"https://api.de-cix.net/api/
|
|
40
|
+
"https://api.de-cix.net/api/v2/",
|
|
44
41
|
"3LH3G72VH7H1SGogEsFeQOPsGjOQotMUZQRt2pK7YbH",
|
|
45
42
|
"cEtrt8s0vR0CsG0vpAmcaxtnolzZj7DEG0B7izvwPlV",
|
|
46
43
|
)
|
|
@@ -78,4 +75,3 @@ DXDB:PAS:00008
|
|
|
78
75
|
```
|
|
79
76
|
|
|
80
77
|
Write queries are not implemented yet.
|
|
81
|
-
|
|
@@ -4,7 +4,7 @@ Python API client library for [IX-API](https://ix-api.net/).
|
|
|
4
4
|
|
|
5
5
|
Supported versions are:
|
|
6
6
|
|
|
7
|
-
* [version 1](https://docs.ix-api.net/v1/)
|
|
7
|
+
* [version 1](https://docs.ix-api.net/v1/) -- deprecated, providers cease date is 2026-07-01
|
|
8
8
|
* [version 2](https://docs.ix-api.net/v2/)
|
|
9
9
|
|
|
10
10
|
## Installation
|
|
@@ -18,7 +18,7 @@ To begin, import pyixapi and instantiate the API.
|
|
|
18
18
|
```python
|
|
19
19
|
import pyixapi
|
|
20
20
|
ixapi = pyixapi.api(
|
|
21
|
-
"https://api.de-cix.net/api/
|
|
21
|
+
"https://api.de-cix.net/api/v2/",
|
|
22
22
|
"3LH3G72VH7H1SGogEsFeQOPsGjOQotMUZQRt2pK7YbH",
|
|
23
23
|
"cEtrt8s0vR0CsG0vpAmcaxtnolzZj7DEG0B7izvwPlV",
|
|
24
24
|
)
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import warnings
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
1
4
|
import requests
|
|
2
5
|
|
|
3
6
|
from pyixapi.core.endpoint import Endpoint
|
|
@@ -29,7 +32,7 @@ from pyixapi.models import (
|
|
|
29
32
|
RoleAssignment,
|
|
30
33
|
)
|
|
31
34
|
|
|
32
|
-
__version__ = "0.2.
|
|
35
|
+
__version__ = "0.2.8"
|
|
33
36
|
|
|
34
37
|
|
|
35
38
|
class API(object):
|
|
@@ -42,14 +45,14 @@ class API(object):
|
|
|
42
45
|
|
|
43
46
|
def __init__(
|
|
44
47
|
self,
|
|
45
|
-
url,
|
|
46
|
-
key,
|
|
47
|
-
secret,
|
|
48
|
-
access_token="",
|
|
49
|
-
refresh_token="",
|
|
50
|
-
user_agent=f"pyixapi/{__version__}",
|
|
51
|
-
proxies=None,
|
|
52
|
-
):
|
|
48
|
+
url: str,
|
|
49
|
+
key: str,
|
|
50
|
+
secret: str,
|
|
51
|
+
access_token: str = "",
|
|
52
|
+
refresh_token: str = "",
|
|
53
|
+
user_agent: str = f"pyixapi/{__version__}",
|
|
54
|
+
proxies: dict[str, str] | None = None,
|
|
55
|
+
) -> None:
|
|
53
56
|
self.url = url.rstrip("/")
|
|
54
57
|
self.key = key
|
|
55
58
|
self.secret = secret
|
|
@@ -67,36 +70,26 @@ class API(object):
|
|
|
67
70
|
self.facilities = Endpoint(self, "facilities", model=Facility)
|
|
68
71
|
self.ips = Endpoint(self, "ips", model=IP)
|
|
69
72
|
self.macs = Endpoint(self, "macs", model=MAC)
|
|
70
|
-
self.network_feature_configs = Endpoint(
|
|
71
|
-
self, "network-feature-configs", model=NetworkFeatureConfig
|
|
72
|
-
)
|
|
73
|
+
self.network_feature_configs = Endpoint(self, "network-feature-configs", model=NetworkFeatureConfig)
|
|
73
74
|
self.network_features = Endpoint(self, "network-features", model=NetworkFeature)
|
|
74
|
-
self.network_service_configs = Endpoint(
|
|
75
|
-
self, "network-service-configs", model=NetworkServiceConfig
|
|
76
|
-
)
|
|
75
|
+
self.network_service_configs = Endpoint(self, "network-service-configs", model=NetworkServiceConfig)
|
|
77
76
|
self.network_services = Endpoint(self, "network-services", model=NetworkService)
|
|
78
77
|
self.pops = Endpoint(self, "pops", model=PoP)
|
|
79
78
|
# Version 2+
|
|
80
|
-
self.member_joining_rules = Endpoint(
|
|
81
|
-
self, "member-joining-rules", model=MemberJoiningRule
|
|
82
|
-
)
|
|
79
|
+
self.member_joining_rules = Endpoint(self, "member-joining-rules", model=MemberJoiningRule)
|
|
83
80
|
self.metro_areas = Endpoint(self, "metro-areas", model=MetroArea)
|
|
84
|
-
self.metro_area_networks = Endpoint(
|
|
85
|
-
self, "metro-area-networks", model=MetroAreaNetwork
|
|
86
|
-
)
|
|
81
|
+
self.metro_area_networks = Endpoint(self, "metro-area-networks", model=MetroAreaNetwork)
|
|
87
82
|
self.ports = Endpoint(self, "ports", model=Port)
|
|
88
|
-
self.port_reservations = Endpoint(
|
|
89
|
-
self, "port-reservations", model=PortReservation
|
|
90
|
-
)
|
|
83
|
+
self.port_reservations = Endpoint(self, "port-reservations", model=PortReservation)
|
|
91
84
|
self.roles = Endpoint(self, "roles", model=Role)
|
|
92
85
|
self.role_assignments = Endpoint(self, "role-assignments", model=RoleAssignment)
|
|
93
86
|
|
|
94
87
|
@property
|
|
95
|
-
def version(self):
|
|
88
|
+
def version(self) -> int:
|
|
96
89
|
"""
|
|
97
90
|
Get the API version of IX-API.
|
|
98
91
|
"""
|
|
99
|
-
|
|
92
|
+
version = Request(
|
|
100
93
|
base=self.url,
|
|
101
94
|
token=self.access_token,
|
|
102
95
|
http_session=self.http_session,
|
|
@@ -104,21 +97,28 @@ class API(object):
|
|
|
104
97
|
proxies=self.proxies,
|
|
105
98
|
).get_version()
|
|
106
99
|
|
|
100
|
+
if version == 1:
|
|
101
|
+
warnings.warn(
|
|
102
|
+
"IX-API version 1 is deprecated and will not be supported in future releases of pyixapi.",
|
|
103
|
+
DeprecationWarning,
|
|
104
|
+
stacklevel=2,
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
return version
|
|
108
|
+
|
|
107
109
|
@property
|
|
108
|
-
def accounts(self):
|
|
109
|
-
return Endpoint(
|
|
110
|
-
self, "customers" if self.version == 1 else "accounts", model=Account
|
|
111
|
-
)
|
|
110
|
+
def accounts(self) -> Endpoint:
|
|
111
|
+
return Endpoint(self, "customers" if self.version == 1 else "accounts", model=Account)
|
|
112
112
|
|
|
113
113
|
@property
|
|
114
|
-
def product_offerings(self):
|
|
114
|
+
def product_offerings(self) -> Endpoint:
|
|
115
115
|
return Endpoint(
|
|
116
116
|
self,
|
|
117
117
|
"products" if self.version == 1 else "product-offerings",
|
|
118
118
|
model=ProductOffering,
|
|
119
119
|
)
|
|
120
120
|
|
|
121
|
-
def authenticate(self):
|
|
121
|
+
def authenticate(self) -> Record | None:
|
|
122
122
|
"""
|
|
123
123
|
Authenticate and generate a pair of tokens.
|
|
124
124
|
|
|
@@ -148,13 +148,16 @@ class API(object):
|
|
|
148
148
|
|
|
149
149
|
return Record(r, self, self.auth)
|
|
150
150
|
|
|
151
|
-
def refresh_authentication(self):
|
|
151
|
+
def refresh_authentication(self) -> Record:
|
|
152
152
|
"""
|
|
153
153
|
Prolong authentication by refreshing the tokens pair.
|
|
154
154
|
"""
|
|
155
|
+
if not self.refresh_token:
|
|
156
|
+
raise ValueError("No refresh token available to refresh authentication")
|
|
157
|
+
|
|
155
158
|
r = Request(
|
|
156
159
|
cat(self.url, "auth", "refresh"),
|
|
157
|
-
token=self.refresh_token
|
|
160
|
+
token=self.refresh_token,
|
|
158
161
|
http_session=self.http_session,
|
|
159
162
|
user_agent=self.user_agent,
|
|
160
163
|
proxies=self.proxies,
|
|
@@ -165,7 +168,7 @@ class API(object):
|
|
|
165
168
|
|
|
166
169
|
return Record(r, self, self.auth)
|
|
167
170
|
|
|
168
|
-
def health(self):
|
|
171
|
+
def health(self) -> dict[str, Any]:
|
|
169
172
|
"""
|
|
170
173
|
Get the health information from IX-API.
|
|
171
174
|
|
|
@@ -1,7 +1,14 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, Any
|
|
4
|
+
|
|
1
5
|
from pyixapi.core.query import Request, RequestError
|
|
2
6
|
from pyixapi.core.response import Record, RecordSet
|
|
3
7
|
from pyixapi.core.util import cat
|
|
4
8
|
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from pyixapi.core.api import API
|
|
11
|
+
|
|
5
12
|
|
|
6
13
|
class Endpoint(object):
|
|
7
14
|
"""
|
|
@@ -11,16 +18,16 @@ class Endpoint(object):
|
|
|
11
18
|
object.
|
|
12
19
|
"""
|
|
13
20
|
|
|
14
|
-
def __init__(self, api, name, model=None):
|
|
15
|
-
self.return_obj = model if model else Record
|
|
21
|
+
def __init__(self, api: API, name: str, model: type[Record] | None = None) -> None:
|
|
22
|
+
self.return_obj: type[Record] = model if model else Record
|
|
16
23
|
self.api = api
|
|
17
24
|
self.url = cat(api.url, name)
|
|
18
25
|
self.name = name
|
|
19
26
|
|
|
20
|
-
def __str__(self):
|
|
27
|
+
def __str__(self) -> str:
|
|
21
28
|
return self.url
|
|
22
29
|
|
|
23
|
-
def all(self):
|
|
30
|
+
def all(self) -> RecordSet:
|
|
24
31
|
"""
|
|
25
32
|
Return all objects from an endpoint.
|
|
26
33
|
"""
|
|
@@ -33,7 +40,7 @@ class Endpoint(object):
|
|
|
33
40
|
)
|
|
34
41
|
return RecordSet(self, r)
|
|
35
42
|
|
|
36
|
-
def filter(self, *args, **kwargs):
|
|
43
|
+
def filter(self, *args: Any, **kwargs: Any) -> RecordSet:
|
|
37
44
|
"""
|
|
38
45
|
Query the list of a given endpoint. Also take named arguments that match the
|
|
39
46
|
usable filters on a given endpoint.
|
|
@@ -48,7 +55,7 @@ class Endpoint(object):
|
|
|
48
55
|
)
|
|
49
56
|
return RecordSet(self, r)
|
|
50
57
|
|
|
51
|
-
def get(self, *args, **kwargs):
|
|
58
|
+
def get(self, *args: Any, **kwargs: Any) -> Record | None:
|
|
52
59
|
"""
|
|
53
60
|
Return a single object from an endpoint.
|
|
54
61
|
"""
|
|
@@ -89,7 +96,7 @@ class Endpoint(object):
|
|
|
89
96
|
else:
|
|
90
97
|
raise e
|
|
91
98
|
|
|
92
|
-
def create(self, *args, **kwargs):
|
|
99
|
+
def create(self, *args: Any, **kwargs: Any) -> Record:
|
|
93
100
|
"""
|
|
94
101
|
Creates an object on an endpoint.
|
|
95
102
|
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import json
|
|
2
|
+
from typing import Any, Generator
|
|
2
3
|
|
|
4
|
+
import requests
|
|
5
|
+
|
|
6
|
+
from pyixapi.core.token import Token
|
|
3
7
|
from pyixapi.core.util import cat
|
|
4
8
|
|
|
5
9
|
|
|
@@ -10,7 +14,7 @@ class RequestError(Exception):
|
|
|
10
14
|
return is JSON we decode and add it to the message.
|
|
11
15
|
"""
|
|
12
16
|
|
|
13
|
-
def __init__(self, r):
|
|
17
|
+
def __init__(self, r: requests.Response) -> None:
|
|
14
18
|
if r.status_code == 404:
|
|
15
19
|
self.message = f"The requested url: {r.url} could not be found."
|
|
16
20
|
elif r.status_code == 401:
|
|
@@ -27,7 +31,7 @@ class RequestError(Exception):
|
|
|
27
31
|
self.req = r
|
|
28
32
|
self.error = r.text
|
|
29
33
|
|
|
30
|
-
def __str__(self):
|
|
34
|
+
def __str__(self) -> str:
|
|
31
35
|
return self.message
|
|
32
36
|
|
|
33
37
|
|
|
@@ -38,13 +42,13 @@ class ContentError(Exception):
|
|
|
38
42
|
those cases.
|
|
39
43
|
"""
|
|
40
44
|
|
|
41
|
-
def __init__(self, req):
|
|
45
|
+
def __init__(self, req: requests.Response) -> None:
|
|
42
46
|
super(ContentError, self).__init__(req)
|
|
43
47
|
|
|
44
48
|
self.req = req
|
|
45
49
|
self.error = "The server returned invalid (non-JSON) data. Maybe not an IX-API server?"
|
|
46
50
|
|
|
47
|
-
def __str__(self):
|
|
51
|
+
def __str__(self) -> str:
|
|
48
52
|
return self.error
|
|
49
53
|
|
|
50
54
|
|
|
@@ -63,14 +67,14 @@ class Request(object):
|
|
|
63
67
|
|
|
64
68
|
def __init__(
|
|
65
69
|
self,
|
|
66
|
-
base,
|
|
67
|
-
http_session,
|
|
68
|
-
filters=None,
|
|
69
|
-
key=None,
|
|
70
|
-
token=None,
|
|
71
|
-
user_agent=None,
|
|
72
|
-
proxies=None,
|
|
73
|
-
):
|
|
70
|
+
base: str,
|
|
71
|
+
http_session: requests.Session,
|
|
72
|
+
filters: dict[str, Any] | None = None,
|
|
73
|
+
key: str | None = None,
|
|
74
|
+
token: Token | None = None,
|
|
75
|
+
user_agent: str | None = None,
|
|
76
|
+
proxies: dict[str, str] | None = None,
|
|
77
|
+
) -> None:
|
|
74
78
|
self.base = base
|
|
75
79
|
self.filters = filters or None
|
|
76
80
|
self.key = key
|
|
@@ -80,7 +84,7 @@ class Request(object):
|
|
|
80
84
|
self.user_agent = user_agent
|
|
81
85
|
self.proxies = proxies
|
|
82
86
|
|
|
83
|
-
def get_openapi(self):
|
|
87
|
+
def get_openapi(self) -> dict[str, Any]:
|
|
84
88
|
"""
|
|
85
89
|
Get the OpenAPI Spec.
|
|
86
90
|
"""
|
|
@@ -91,13 +95,13 @@ class Request(object):
|
|
|
91
95
|
else:
|
|
92
96
|
raise RequestError(req)
|
|
93
97
|
|
|
94
|
-
def get_version(self):
|
|
98
|
+
def get_version(self) -> int:
|
|
95
99
|
"""
|
|
96
100
|
Get the API version of IX-API.
|
|
97
101
|
|
|
98
102
|
Issue a GET request to the health endpoint to read the API version.
|
|
99
103
|
|
|
100
|
-
If a RequestError is raised, it is
|
|
104
|
+
If a RequestError is raised, it is caught and the version is considered as
|
|
101
105
|
equal to 1 (IX-API v1 does not have a health endpoint).
|
|
102
106
|
"""
|
|
103
107
|
try:
|
|
@@ -105,32 +109,38 @@ class Request(object):
|
|
|
105
109
|
except RequestError:
|
|
106
110
|
return 1
|
|
107
111
|
|
|
108
|
-
def get_health(self):
|
|
112
|
+
def get_health(self) -> dict[str, Any]:
|
|
109
113
|
"""
|
|
110
114
|
Get the health from /api/health endpoint in IX-API.
|
|
111
115
|
"""
|
|
112
|
-
headers = {"Content-Type": "application/json;"}
|
|
116
|
+
headers: dict[str, str] = {"Content-Type": "application/json;"}
|
|
113
117
|
if self.token:
|
|
114
|
-
headers["Authorization"] = f"Bearer {self.token}"
|
|
118
|
+
headers["Authorization"] = f"Bearer {self.token.encoded}"
|
|
115
119
|
r = self.http_session.get(cat(self.base, "health"), headers=headers)
|
|
116
120
|
if r.ok:
|
|
117
121
|
return r.json()
|
|
118
122
|
else:
|
|
119
123
|
raise RequestError(r)
|
|
120
124
|
|
|
121
|
-
def _make_call(
|
|
125
|
+
def _make_call(
|
|
126
|
+
self,
|
|
127
|
+
verb: str = "get",
|
|
128
|
+
url_override: str | None = None,
|
|
129
|
+
add_params: dict[str, Any] | None = None,
|
|
130
|
+
data: dict[str, Any] | None = None,
|
|
131
|
+
) -> Any:
|
|
122
132
|
if verb in ("post", "put") or verb == "delete" and data:
|
|
123
|
-
headers = {"Content-Type": "application/json;"}
|
|
133
|
+
headers: dict[str, str] = {"Content-Type": "application/json;"}
|
|
124
134
|
else:
|
|
125
135
|
headers = {"accept": "application/json;"}
|
|
126
136
|
|
|
127
137
|
if self.token:
|
|
128
|
-
headers["Authorization"] = f"Bearer {self.token}"
|
|
138
|
+
headers["Authorization"] = f"Bearer {self.token.encoded}"
|
|
129
139
|
|
|
130
140
|
if self.user_agent:
|
|
131
141
|
headers["User-Agent"] = self.user_agent
|
|
132
142
|
|
|
133
|
-
params = {}
|
|
143
|
+
params: dict[str, Any] = {}
|
|
134
144
|
if not url_override:
|
|
135
145
|
if self.filters:
|
|
136
146
|
params.update(self.filters)
|
|
@@ -158,7 +168,7 @@ class Request(object):
|
|
|
158
168
|
else:
|
|
159
169
|
raise RequestError(r)
|
|
160
170
|
|
|
161
|
-
def get(self, add_params=None):
|
|
171
|
+
def get(self, add_params: dict[str, Any] | None = None) -> Generator[dict[str, Any], None, None]:
|
|
162
172
|
"""
|
|
163
173
|
Make a GET request to IX-API.
|
|
164
174
|
|
|
@@ -177,7 +187,7 @@ class Request(object):
|
|
|
177
187
|
self.count = len(req)
|
|
178
188
|
yield req
|
|
179
189
|
|
|
180
|
-
def put(self, data):
|
|
190
|
+
def put(self, data: dict[str, Any]) -> dict[str, Any]:
|
|
181
191
|
"""
|
|
182
192
|
Make a PUT request to IX-API.
|
|
183
193
|
|
|
@@ -189,7 +199,7 @@ class Request(object):
|
|
|
189
199
|
"""
|
|
190
200
|
return self._make_call(verb="put", data=data)
|
|
191
201
|
|
|
192
|
-
def post(self, data):
|
|
202
|
+
def post(self, data: dict[str, Any]) -> dict[str, Any]:
|
|
193
203
|
"""
|
|
194
204
|
Make a POST request to IX-API.
|
|
195
205
|
|
|
@@ -201,7 +211,7 @@ class Request(object):
|
|
|
201
211
|
"""
|
|
202
212
|
return self._make_call(verb="post", data=data)
|
|
203
213
|
|
|
204
|
-
def delete(self, data=None):
|
|
214
|
+
def delete(self, data: dict[str, Any] | None = None) -> bool:
|
|
205
215
|
"""
|
|
206
216
|
Make a DELETE request to IX-API.
|
|
207
217
|
|
|
@@ -215,7 +225,7 @@ class Request(object):
|
|
|
215
225
|
"""
|
|
216
226
|
return self._make_call(verb="delete", data=data)
|
|
217
227
|
|
|
218
|
-
def patch(self, data):
|
|
228
|
+
def patch(self, data: dict[str, Any]) -> dict[str, Any]:
|
|
219
229
|
"""
|
|
220
230
|
Make a PATCH request to IX-API.
|
|
221
231
|
|
|
@@ -227,7 +237,7 @@ class Request(object):
|
|
|
227
237
|
"""
|
|
228
238
|
return self._make_call(verb="patch", data=data)
|
|
229
239
|
|
|
230
|
-
def options(self):
|
|
240
|
+
def options(self) -> dict[str, Any]:
|
|
231
241
|
"""
|
|
232
242
|
Make an OPTIONS request to IX-API.
|
|
233
243
|
|
|
@@ -1,8 +1,16 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, Any, Iterator
|
|
4
|
+
|
|
1
5
|
from pyixapi.core.query import Request
|
|
2
6
|
from pyixapi.core.util import Hashabledict
|
|
3
7
|
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from pyixapi.core.api import API
|
|
10
|
+
from pyixapi.core.endpoint import Endpoint
|
|
11
|
+
|
|
4
12
|
|
|
5
|
-
def get_return(lookup):
|
|
13
|
+
def get_return(lookup: Any) -> Any:
|
|
6
14
|
"""
|
|
7
15
|
Return simple representations for items passed to lookup.
|
|
8
16
|
|
|
@@ -52,21 +60,21 @@ class RecordSet(object):
|
|
|
52
60
|
>>>
|
|
53
61
|
"""
|
|
54
62
|
|
|
55
|
-
def __init__(self, endpoint, request, **kwargs):
|
|
63
|
+
def __init__(self, endpoint: Endpoint, request: Request, **kwargs: Any) -> None:
|
|
56
64
|
self.endpoint = endpoint
|
|
57
65
|
self.request = request
|
|
58
66
|
self.response = self.request.get()
|
|
59
|
-
self._response_cache = []
|
|
67
|
+
self._response_cache: list[dict[str, Any]] = []
|
|
60
68
|
|
|
61
|
-
def __iter__(self):
|
|
69
|
+
def __iter__(self) -> Iterator[Record]:
|
|
62
70
|
return self
|
|
63
71
|
|
|
64
|
-
def __next__(self):
|
|
72
|
+
def __next__(self) -> Record:
|
|
65
73
|
if self._response_cache:
|
|
66
74
|
return self.endpoint.return_obj(self._response_cache.pop(), self.endpoint.api, self.endpoint)
|
|
67
75
|
return self.endpoint.return_obj(next(self.response), self.endpoint.api, self.endpoint)
|
|
68
76
|
|
|
69
|
-
def __len__(self):
|
|
77
|
+
def __len__(self) -> int:
|
|
70
78
|
try:
|
|
71
79
|
return self.request.count
|
|
72
80
|
except AttributeError:
|
|
@@ -138,18 +146,18 @@ class Record(object):
|
|
|
138
146
|
>>>
|
|
139
147
|
"""
|
|
140
148
|
|
|
141
|
-
url = None
|
|
149
|
+
url: str | None = None
|
|
142
150
|
|
|
143
|
-
def __init__(self, values, api, endpoint):
|
|
144
|
-
self._full_cache = []
|
|
145
|
-
self._init_cache = []
|
|
151
|
+
def __init__(self, values: dict[str, Any], api: API, endpoint: Endpoint) -> None:
|
|
152
|
+
self._full_cache: list[Any] = []
|
|
153
|
+
self._init_cache: list[tuple[str, Any]] = []
|
|
146
154
|
self.api = api
|
|
147
|
-
self.default_ret = Record
|
|
155
|
+
self.default_ret: type[Record] = Record
|
|
148
156
|
self.endpoint = endpoint
|
|
149
157
|
if values:
|
|
150
158
|
self._parse_values(values)
|
|
151
159
|
|
|
152
|
-
def __iter__(self):
|
|
160
|
+
def __iter__(self) -> Iterator[tuple[str, Any]]:
|
|
153
161
|
for i in dict(self._init_cache):
|
|
154
162
|
a = getattr(self, i)
|
|
155
163
|
if isinstance(a, Record):
|
|
@@ -159,48 +167,48 @@ class Record(object):
|
|
|
159
167
|
else:
|
|
160
168
|
yield i, a
|
|
161
169
|
|
|
162
|
-
def __getitem__(self, k):
|
|
170
|
+
def __getitem__(self, k: str) -> Any:
|
|
163
171
|
return dict(self)[k]
|
|
164
172
|
|
|
165
|
-
def __str__(self):
|
|
173
|
+
def __str__(self) -> str:
|
|
166
174
|
if hasattr(self, "id"):
|
|
167
175
|
return self.id
|
|
168
176
|
else:
|
|
169
177
|
return str(self.endpoint)
|
|
170
178
|
|
|
171
|
-
def __repr__(self):
|
|
179
|
+
def __repr__(self) -> str:
|
|
172
180
|
return str(dict(self))
|
|
173
181
|
|
|
174
|
-
def __getstate__(self):
|
|
182
|
+
def __getstate__(self) -> dict[str, Any]:
|
|
175
183
|
return self.__dict__
|
|
176
184
|
|
|
177
|
-
def __setstate__(self, d):
|
|
185
|
+
def __setstate__(self, d: dict[str, Any]) -> None:
|
|
178
186
|
self.__dict__.update(d)
|
|
179
187
|
|
|
180
|
-
def __key__(self):
|
|
188
|
+
def __key__(self) -> tuple[str, ...] | tuple[str]:
|
|
181
189
|
if hasattr(self, "id"):
|
|
182
190
|
return (self.endpoint.name, self.id)
|
|
183
191
|
else:
|
|
184
|
-
return self.endpoint.name
|
|
192
|
+
return (self.endpoint.name,)
|
|
185
193
|
|
|
186
|
-
def __hash__(self):
|
|
194
|
+
def __hash__(self) -> int:
|
|
187
195
|
return hash(self.__key__())
|
|
188
196
|
|
|
189
|
-
def __eq__(self, other):
|
|
197
|
+
def __eq__(self, other: object) -> bool:
|
|
190
198
|
if isinstance(other, Record):
|
|
191
199
|
return self.__key__() == other.__key__()
|
|
192
200
|
return NotImplemented
|
|
193
201
|
|
|
194
|
-
def _add_cache(self, item):
|
|
202
|
+
def _add_cache(self, item: tuple[str, Any]) -> None:
|
|
195
203
|
key, value = item
|
|
196
204
|
self._init_cache.append((key, get_return(value)))
|
|
197
205
|
|
|
198
|
-
def _parse_values(self, values):
|
|
206
|
+
def _parse_values(self, values: dict[str, Any]) -> None:
|
|
199
207
|
"""
|
|
200
208
|
Parse values dict at init and sets object attributes with the values within.
|
|
201
209
|
"""
|
|
202
210
|
|
|
203
|
-
def list_parser(key_name, list_item):
|
|
211
|
+
def list_parser(key_name: str, list_item: Any) -> Any:
|
|
204
212
|
if isinstance(list_item, dict):
|
|
205
213
|
lookup = getattr(self.__class__, key_name, None)
|
|
206
214
|
if not isinstance(lookup, list):
|
|
@@ -226,7 +234,7 @@ class Record(object):
|
|
|
226
234
|
self._add_cache((k, v))
|
|
227
235
|
setattr(self, k, v)
|
|
228
236
|
|
|
229
|
-
def serialize(self, nested=False, init=False):
|
|
237
|
+
def serialize(self, nested: bool = False, init: bool = False) -> Any:
|
|
230
238
|
"""
|
|
231
239
|
Pull all the attributes in an object and create a dict that can be turned into
|
|
232
240
|
the JSON that IX-API is expecting.
|
|
@@ -240,7 +248,7 @@ class Record(object):
|
|
|
240
248
|
if init:
|
|
241
249
|
init_vals = dict(self._init_cache)
|
|
242
250
|
|
|
243
|
-
r = {}
|
|
251
|
+
r: dict[str, Any] = {}
|
|
244
252
|
for i in dict(self):
|
|
245
253
|
current_val = getattr(self, i) if not init else init_vals.get(i)
|
|
246
254
|
if isinstance(current_val, Record):
|
|
@@ -250,8 +258,8 @@ class Record(object):
|
|
|
250
258
|
r[i] = current_val
|
|
251
259
|
return r
|
|
252
260
|
|
|
253
|
-
def _diff(self):
|
|
254
|
-
def fmt_dict(k, v):
|
|
261
|
+
def _diff(self) -> set[str]:
|
|
262
|
+
def fmt_dict(k: str, v: Any) -> tuple[str, Any]:
|
|
255
263
|
if isinstance(v, dict):
|
|
256
264
|
return k, Hashabledict(v)
|
|
257
265
|
if isinstance(v, list):
|
|
@@ -262,7 +270,7 @@ class Record(object):
|
|
|
262
270
|
init = Hashabledict({fmt_dict(k, v) for k, v in self.serialize(init=True).items()})
|
|
263
271
|
return set([i[0] for i in set(current.items()) ^ set(init.items())])
|
|
264
272
|
|
|
265
|
-
def updates(self):
|
|
273
|
+
def updates(self) -> dict[str, Any]:
|
|
266
274
|
"""
|
|
267
275
|
Compile changes for an existing object into a dict.
|
|
268
276
|
|
|
@@ -276,7 +284,7 @@ class Record(object):
|
|
|
276
284
|
return {i: serialized[i] for i in diff}
|
|
277
285
|
return {}
|
|
278
286
|
|
|
279
|
-
def save(self):
|
|
287
|
+
def save(self) -> bool:
|
|
280
288
|
"""
|
|
281
289
|
Save changes to an existing object.
|
|
282
290
|
|
|
@@ -297,7 +305,7 @@ class Record(object):
|
|
|
297
305
|
return True
|
|
298
306
|
return False
|
|
299
307
|
|
|
300
|
-
def update(self, data):
|
|
308
|
+
def update(self, data: dict[str, Any]) -> bool:
|
|
301
309
|
"""
|
|
302
310
|
Update an object with a dictionary.
|
|
303
311
|
|
|
@@ -309,7 +317,7 @@ class Record(object):
|
|
|
309
317
|
setattr(self, k, v)
|
|
310
318
|
return self.save()
|
|
311
319
|
|
|
312
|
-
def delete(self):
|
|
320
|
+
def delete(self) -> bool:
|
|
313
321
|
"""
|
|
314
322
|
Delete an existing object.
|
|
315
323
|
"""
|
|
@@ -49,7 +49,7 @@ class Token:
|
|
|
49
49
|
TTL is the number of seconds, from now, before the token expires.
|
|
50
50
|
"""
|
|
51
51
|
seconds = (self.expires_at - datetime.now(timezone.utc)).total_seconds()
|
|
52
|
-
return max(0, seconds)
|
|
52
|
+
return max(0, int(seconds))
|
|
53
53
|
|
|
54
54
|
@property
|
|
55
55
|
def is_expired(self) -> bool:
|
|
@@ -12,7 +12,7 @@ class Account(Record):
|
|
|
12
12
|
It is also referenced as "customer" in IX-API v1.
|
|
13
13
|
"""
|
|
14
14
|
|
|
15
|
-
def __str__(self):
|
|
15
|
+
def __str__(self) -> str:
|
|
16
16
|
return f"{self.id}: {self.name}"
|
|
17
17
|
|
|
18
18
|
|
|
@@ -21,7 +21,7 @@ class Connection(Record):
|
|
|
21
21
|
A Connection is a group of physical ports collected together into a LAG.
|
|
22
22
|
"""
|
|
23
23
|
|
|
24
|
-
def __str__(self):
|
|
24
|
+
def __str__(self) -> str:
|
|
25
25
|
return f"{self.id}: {self.name}"
|
|
26
26
|
|
|
27
27
|
|
|
@@ -30,7 +30,7 @@ class Contact(Record):
|
|
|
30
30
|
A Contact is a role undertaking a specific responsibility within an account.
|
|
31
31
|
"""
|
|
32
32
|
|
|
33
|
-
def __str__(self):
|
|
33
|
+
def __str__(self) -> str:
|
|
34
34
|
r = []
|
|
35
35
|
|
|
36
36
|
legal_company_name = getattr(self, "legal_company_name", None)
|
|
@@ -51,7 +51,7 @@ class Demarc(Record):
|
|
|
51
51
|
eg a physical port / socket, generally with a specified bandwidth.
|
|
52
52
|
"""
|
|
53
53
|
|
|
54
|
-
def __str__(self):
|
|
54
|
+
def __str__(self) -> str:
|
|
55
55
|
return self.id
|
|
56
56
|
|
|
57
57
|
|
|
@@ -61,7 +61,7 @@ class Device(Record):
|
|
|
61
61
|
specified facility and inside a PoP.
|
|
62
62
|
"""
|
|
63
63
|
|
|
64
|
-
def __str__(self):
|
|
64
|
+
def __str__(self) -> str:
|
|
65
65
|
return self.name
|
|
66
66
|
|
|
67
67
|
|
|
@@ -71,7 +71,7 @@ class Facility(Record):
|
|
|
71
71
|
defined set of PoPs can be accessed.
|
|
72
72
|
"""
|
|
73
73
|
|
|
74
|
-
def __str__(self):
|
|
74
|
+
def __str__(self) -> str:
|
|
75
75
|
return self.name
|
|
76
76
|
|
|
77
77
|
|
|
@@ -81,18 +81,18 @@ class IP(Record):
|
|
|
81
81
|
"""
|
|
82
82
|
|
|
83
83
|
@property
|
|
84
|
-
def cidr(self):
|
|
84
|
+
def cidr(self) -> ipaddress.IPv4Interface | ipaddress.IPv6Interface:
|
|
85
85
|
return ipaddress.ip_interface(f"{self.address}/{self.prefix_length}")
|
|
86
86
|
|
|
87
87
|
@property
|
|
88
|
-
def ip(self):
|
|
88
|
+
def ip(self) -> ipaddress.IPv4Address | ipaddress.IPv6Address:
|
|
89
89
|
return self.cidr.ip
|
|
90
90
|
|
|
91
91
|
@property
|
|
92
|
-
def network(self):
|
|
92
|
+
def network(self) -> ipaddress.IPv4Network | ipaddress.IPv6Network:
|
|
93
93
|
return self.cidr.network
|
|
94
94
|
|
|
95
|
-
def __str__(self):
|
|
95
|
+
def __str__(self) -> str:
|
|
96
96
|
return str(self.cidr)
|
|
97
97
|
|
|
98
98
|
|
|
@@ -101,7 +101,7 @@ class MAC(Record):
|
|
|
101
101
|
A MAC is a MAC address with a given validity period.
|
|
102
102
|
"""
|
|
103
103
|
|
|
104
|
-
def __str__(self):
|
|
104
|
+
def __str__(self) -> str:
|
|
105
105
|
return self.address.lower()
|
|
106
106
|
|
|
107
107
|
|
|
@@ -110,7 +110,7 @@ class NetworkFeatureConfig(Record):
|
|
|
110
110
|
A NetworkFeatureConfig is a customer's configuration to use a NetworkFeature.
|
|
111
111
|
"""
|
|
112
112
|
|
|
113
|
-
def __str__(self):
|
|
113
|
+
def __str__(self) -> str:
|
|
114
114
|
return self.id
|
|
115
115
|
|
|
116
116
|
|
|
@@ -120,7 +120,7 @@ class NetworkFeature(Record):
|
|
|
120
120
|
NetworkService.
|
|
121
121
|
"""
|
|
122
122
|
|
|
123
|
-
def __str__(self):
|
|
123
|
+
def __str__(self) -> str:
|
|
124
124
|
return self.id
|
|
125
125
|
|
|
126
126
|
|
|
@@ -130,7 +130,7 @@ class NetworkServiceConfig(Record):
|
|
|
130
130
|
the configuration of a (subset of a) connection for that customer's traffic.
|
|
131
131
|
"""
|
|
132
132
|
|
|
133
|
-
def __str__(self):
|
|
133
|
+
def __str__(self) -> str:
|
|
134
134
|
return self.id
|
|
135
135
|
|
|
136
136
|
|
|
@@ -140,7 +140,7 @@ class NetworkService(Record):
|
|
|
140
140
|
depending on the type of product.
|
|
141
141
|
"""
|
|
142
142
|
|
|
143
|
-
def __str__(self):
|
|
143
|
+
def __str__(self) -> str:
|
|
144
144
|
return self.id
|
|
145
145
|
|
|
146
146
|
|
|
@@ -150,7 +150,7 @@ class PoP(Record):
|
|
|
150
150
|
infrastructure and has defined reachability of other facilities.
|
|
151
151
|
"""
|
|
152
152
|
|
|
153
|
-
def __str__(self):
|
|
153
|
+
def __str__(self) -> str:
|
|
154
154
|
return self.name
|
|
155
155
|
|
|
156
156
|
|
|
@@ -161,7 +161,7 @@ class ProductOffering(Record):
|
|
|
161
161
|
It is also referenced as "product" in IX-API v1.
|
|
162
162
|
"""
|
|
163
163
|
|
|
164
|
-
def __str__(self):
|
|
164
|
+
def __str__(self) -> str:
|
|
165
165
|
return self.name
|
|
166
166
|
|
|
167
167
|
|
|
@@ -177,7 +177,7 @@ class MemberJoiningRule(Record):
|
|
|
177
177
|
listed in an allow rule.
|
|
178
178
|
"""
|
|
179
179
|
|
|
180
|
-
def __str__(self):
|
|
180
|
+
def __str__(self) -> str:
|
|
181
181
|
return self.id
|
|
182
182
|
|
|
183
183
|
|
|
@@ -186,7 +186,7 @@ class MetroArea(Record):
|
|
|
186
186
|
A MetroArea exists if a MetroAreaNetwork or Facility is present in it.
|
|
187
187
|
"""
|
|
188
188
|
|
|
189
|
-
def __str__(self):
|
|
189
|
+
def __str__(self) -> str:
|
|
190
190
|
return self.display_name
|
|
191
191
|
|
|
192
192
|
|
|
@@ -199,7 +199,7 @@ class MetroAreaNetwork(Record):
|
|
|
199
199
|
list of IDs.
|
|
200
200
|
"""
|
|
201
201
|
|
|
202
|
-
def __str__(self):
|
|
202
|
+
def __str__(self) -> str:
|
|
203
203
|
return self.name
|
|
204
204
|
|
|
205
205
|
|
|
@@ -209,7 +209,7 @@ class Port(Record):
|
|
|
209
209
|
associated with a device and pop, has a speed and a media_type.
|
|
210
210
|
"""
|
|
211
211
|
|
|
212
|
-
def __str__(self):
|
|
212
|
+
def __str__(self) -> str:
|
|
213
213
|
return self.name
|
|
214
214
|
|
|
215
215
|
|
|
@@ -223,7 +223,7 @@ class PortReservation(Record):
|
|
|
223
223
|
Please note that individual cancellation policies might apply.
|
|
224
224
|
"""
|
|
225
225
|
|
|
226
|
-
def __str__(self):
|
|
226
|
+
def __str__(self) -> str:
|
|
227
227
|
return self.id
|
|
228
228
|
|
|
229
229
|
|
|
@@ -232,7 +232,7 @@ class Role(Record):
|
|
|
232
232
|
A Role enables a Contact to act for a specific purpose.
|
|
233
233
|
"""
|
|
234
234
|
|
|
235
|
-
def __str__(self):
|
|
235
|
+
def __str__(self) -> str:
|
|
236
236
|
return self.name
|
|
237
237
|
|
|
238
238
|
|
|
@@ -241,5 +241,5 @@ class RoleAssignment(Record):
|
|
|
241
241
|
A Contact can be assigned to many Roles.
|
|
242
242
|
"""
|
|
243
243
|
|
|
244
|
-
def __str__(self):
|
|
244
|
+
def __str__(self) -> str:
|
|
245
245
|
return self.id
|
|
File without changes
|
|
@@ -1,24 +1,24 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "pyixapi"
|
|
3
|
-
version = "0.2.
|
|
3
|
+
version = "0.2.8"
|
|
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"
|
|
7
7
|
license = "Apache-2.0"
|
|
8
|
-
requires-python = ">=3.
|
|
8
|
+
requires-python = ">=3.10"
|
|
9
9
|
classifiers = [
|
|
10
10
|
"Intended Audience :: Developers",
|
|
11
11
|
"Programming Language :: Python :: 3",
|
|
12
|
-
"Programming Language :: Python :: 3.9",
|
|
13
12
|
"Programming Language :: Python :: 3.10",
|
|
14
13
|
"Programming Language :: Python :: 3.11",
|
|
15
14
|
"Programming Language :: Python :: 3.12",
|
|
16
15
|
"Programming Language :: Python :: 3.13",
|
|
16
|
+
"Programming Language :: Python :: 3.14",
|
|
17
17
|
]
|
|
18
|
-
dependencies = ["PyJWT>=2.4.0,<2.
|
|
18
|
+
dependencies = ["PyJWT>=2.4.0,<2.13", "requests>=2.32.4,<3.0"]
|
|
19
19
|
|
|
20
|
-
[
|
|
21
|
-
dev = ["pytest", "ruff
|
|
20
|
+
[dependency-groups]
|
|
21
|
+
dev = ["pytest", "pytest-cov", "ruff", "ty"]
|
|
22
22
|
|
|
23
23
|
[tool.poetry]
|
|
24
24
|
requires-poetry = ">=2.0"
|
|
@@ -57,6 +57,24 @@ known-first-party = ["pyixapi"]
|
|
|
57
57
|
[tool.ruff.lint.mccabe]
|
|
58
58
|
max-complexity = 31
|
|
59
59
|
|
|
60
|
+
[tool.ty.rules]
|
|
61
|
+
unresolved-attribute = "ignore"
|
|
62
|
+
invalid-return-type = "ignore"
|
|
63
|
+
unresolved-import = "ignore"
|
|
64
|
+
|
|
65
|
+
[tool.pytest.ini_options]
|
|
66
|
+
addopts = "--cov=pyixapi --cov-report=term-missing --cov-report=xml"
|
|
67
|
+
|
|
68
|
+
[tool.coverage.run]
|
|
69
|
+
source = ["pyixapi"]
|
|
70
|
+
omit = ["pyixapi/__main__.py"]
|
|
71
|
+
|
|
72
|
+
[tool.coverage.report]
|
|
73
|
+
exclude_lines = ["if TYPE_CHECKING:"]
|
|
74
|
+
|
|
60
75
|
[build-system]
|
|
61
|
-
requires = ["
|
|
62
|
-
build-backend = "
|
|
76
|
+
requires = ["uv_build>=0.9.21,<0.10.0"]
|
|
77
|
+
build-backend = "uv_build"
|
|
78
|
+
|
|
79
|
+
[tool.uv.build-backend]
|
|
80
|
+
module-root = "."
|
pyixapi-0.2.6/LICENSE
DELETED
|
@@ -1,178 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
Apache License
|
|
3
|
-
Version 2.0, January 2004
|
|
4
|
-
http://www.apache.org/licenses/
|
|
5
|
-
|
|
6
|
-
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
7
|
-
|
|
8
|
-
1. Definitions.
|
|
9
|
-
|
|
10
|
-
"License" shall mean the terms and conditions for use, reproduction,
|
|
11
|
-
and distribution as defined by Sections 1 through 9 of this document.
|
|
12
|
-
|
|
13
|
-
"Licensor" shall mean the copyright owner or entity authorized by
|
|
14
|
-
the copyright owner that is granting the License.
|
|
15
|
-
|
|
16
|
-
"Legal Entity" shall mean the union of the acting entity and all
|
|
17
|
-
other entities that control, are controlled by, or are under common
|
|
18
|
-
control with that entity. For the purposes of this definition,
|
|
19
|
-
"control" means (i) the power, direct or indirect, to cause the
|
|
20
|
-
direction or management of such entity, whether by contract or
|
|
21
|
-
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
22
|
-
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
23
|
-
|
|
24
|
-
"You" (or "Your") shall mean an individual or Legal Entity
|
|
25
|
-
exercising permissions granted by this License.
|
|
26
|
-
|
|
27
|
-
"Source" form shall mean the preferred form for making modifications,
|
|
28
|
-
including but not limited to software source code, documentation
|
|
29
|
-
source, and configuration files.
|
|
30
|
-
|
|
31
|
-
"Object" form shall mean any form resulting from mechanical
|
|
32
|
-
transformation or translation of a Source form, including but
|
|
33
|
-
not limited to compiled object code, generated documentation,
|
|
34
|
-
and conversions to other media types.
|
|
35
|
-
|
|
36
|
-
"Work" shall mean the work of authorship, whether in Source or
|
|
37
|
-
Object form, made available under the License, as indicated by a
|
|
38
|
-
copyright notice that is included in or attached to the work
|
|
39
|
-
(an example is provided in the Appendix below).
|
|
40
|
-
|
|
41
|
-
"Derivative Works" shall mean any work, whether in Source or Object
|
|
42
|
-
form, that is based on (or derived from) the Work and for which the
|
|
43
|
-
editorial revisions, annotations, elaborations, or other modifications
|
|
44
|
-
represent, as a whole, an original work of authorship. For the purposes
|
|
45
|
-
of this License, Derivative Works shall not include works that remain
|
|
46
|
-
separable from, or merely link (or bind by name) to the interfaces of,
|
|
47
|
-
the Work and Derivative Works thereof.
|
|
48
|
-
|
|
49
|
-
"Contribution" shall mean any work of authorship, including
|
|
50
|
-
the original version of the Work and any modifications or additions
|
|
51
|
-
to that Work or Derivative Works thereof, that is intentionally
|
|
52
|
-
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
53
|
-
or by an individual or Legal Entity authorized to submit on behalf of
|
|
54
|
-
the copyright owner. For the purposes of this definition, "submitted"
|
|
55
|
-
means any form of electronic, verbal, or written communication sent
|
|
56
|
-
to the Licensor or its representatives, including but not limited to
|
|
57
|
-
communication on electronic mailing lists, source code control systems,
|
|
58
|
-
and issue tracking systems that are managed by, or on behalf of, the
|
|
59
|
-
Licensor for the purpose of discussing and improving the Work, but
|
|
60
|
-
excluding communication that is conspicuously marked or otherwise
|
|
61
|
-
designated in writing by the copyright owner as "Not a Contribution."
|
|
62
|
-
|
|
63
|
-
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
64
|
-
on behalf of whom a Contribution has been received by Licensor and
|
|
65
|
-
subsequently incorporated within the Work.
|
|
66
|
-
|
|
67
|
-
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
68
|
-
this License, each Contributor hereby grants to You a perpetual,
|
|
69
|
-
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
70
|
-
copyright license to reproduce, prepare Derivative Works of,
|
|
71
|
-
publicly display, publicly perform, sublicense, and distribute the
|
|
72
|
-
Work and such Derivative Works in Source or Object form.
|
|
73
|
-
|
|
74
|
-
3. Grant of Patent License. Subject to the terms and conditions of
|
|
75
|
-
this License, each Contributor hereby grants to You a perpetual,
|
|
76
|
-
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
77
|
-
(except as stated in this section) patent license to make, have made,
|
|
78
|
-
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
79
|
-
where such license applies only to those patent claims licensable
|
|
80
|
-
by such Contributor that are necessarily infringed by their
|
|
81
|
-
Contribution(s) alone or by combination of their Contribution(s)
|
|
82
|
-
with the Work to which such Contribution(s) was submitted. If You
|
|
83
|
-
institute patent litigation against any entity (including a
|
|
84
|
-
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
85
|
-
or a Contribution incorporated within the Work constitutes direct
|
|
86
|
-
or contributory patent infringement, then any patent licenses
|
|
87
|
-
granted to You under this License for that Work shall terminate
|
|
88
|
-
as of the date such litigation is filed.
|
|
89
|
-
|
|
90
|
-
4. Redistribution. You may reproduce and distribute copies of the
|
|
91
|
-
Work or Derivative Works thereof in any medium, with or without
|
|
92
|
-
modifications, and in Source or Object form, provided that You
|
|
93
|
-
meet the following conditions:
|
|
94
|
-
|
|
95
|
-
(a) You must give any other recipients of the Work or
|
|
96
|
-
Derivative Works a copy of this License; and
|
|
97
|
-
|
|
98
|
-
(b) You must cause any modified files to carry prominent notices
|
|
99
|
-
stating that You changed the files; and
|
|
100
|
-
|
|
101
|
-
(c) You must retain, in the Source form of any Derivative Works
|
|
102
|
-
that You distribute, all copyright, patent, trademark, and
|
|
103
|
-
attribution notices from the Source form of the Work,
|
|
104
|
-
excluding those notices that do not pertain to any part of
|
|
105
|
-
the Derivative Works; and
|
|
106
|
-
|
|
107
|
-
(d) If the Work includes a "NOTICE" text file as part of its
|
|
108
|
-
distribution, then any Derivative Works that You distribute must
|
|
109
|
-
include a readable copy of the attribution notices contained
|
|
110
|
-
within such NOTICE file, excluding those notices that do not
|
|
111
|
-
pertain to any part of the Derivative Works, in at least one
|
|
112
|
-
of the following places: within a NOTICE text file distributed
|
|
113
|
-
as part of the Derivative Works; within the Source form or
|
|
114
|
-
documentation, if provided along with the Derivative Works; or,
|
|
115
|
-
within a display generated by the Derivative Works, if and
|
|
116
|
-
wherever such third-party notices normally appear. The contents
|
|
117
|
-
of the NOTICE file are for informational purposes only and
|
|
118
|
-
do not modify the License. You may add Your own attribution
|
|
119
|
-
notices within Derivative Works that You distribute, alongside
|
|
120
|
-
or as an addendum to the NOTICE text from the Work, provided
|
|
121
|
-
that such additional attribution notices cannot be construed
|
|
122
|
-
as modifying the License.
|
|
123
|
-
|
|
124
|
-
You may add Your own copyright statement to Your modifications and
|
|
125
|
-
may provide additional or different license terms and conditions
|
|
126
|
-
for use, reproduction, or distribution of Your modifications, or
|
|
127
|
-
for any such Derivative Works as a whole, provided Your use,
|
|
128
|
-
reproduction, and distribution of the Work otherwise complies with
|
|
129
|
-
the conditions stated in this License.
|
|
130
|
-
|
|
131
|
-
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
132
|
-
any Contribution intentionally submitted for inclusion in the Work
|
|
133
|
-
by You to the Licensor shall be under the terms and conditions of
|
|
134
|
-
this License, without any additional terms or conditions.
|
|
135
|
-
Notwithstanding the above, nothing herein shall supersede or modify
|
|
136
|
-
the terms of any separate license agreement you may have executed
|
|
137
|
-
with Licensor regarding such Contributions.
|
|
138
|
-
|
|
139
|
-
6. Trademarks. This License does not grant permission to use the trade
|
|
140
|
-
names, trademarks, service marks, or product names of the Licensor,
|
|
141
|
-
except as required for reasonable and customary use in describing the
|
|
142
|
-
origin of the Work and reproducing the content of the NOTICE file.
|
|
143
|
-
|
|
144
|
-
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
145
|
-
agreed to in writing, Licensor provides the Work (and each
|
|
146
|
-
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
147
|
-
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
148
|
-
implied, including, without limitation, any warranties or conditions
|
|
149
|
-
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
150
|
-
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
151
|
-
appropriateness of using or redistributing the Work and assume any
|
|
152
|
-
risks associated with Your exercise of permissions under this License.
|
|
153
|
-
|
|
154
|
-
8. Limitation of Liability. In no event and under no legal theory,
|
|
155
|
-
whether in tort (including negligence), contract, or otherwise,
|
|
156
|
-
unless required by applicable law (such as deliberate and grossly
|
|
157
|
-
negligent acts) or agreed to in writing, shall any Contributor be
|
|
158
|
-
liable to You for damages, including any direct, indirect, special,
|
|
159
|
-
incidental, or consequential damages of any character arising as a
|
|
160
|
-
result of this License or out of the use or inability to use the
|
|
161
|
-
Work (including but not limited to damages for loss of goodwill,
|
|
162
|
-
work stoppage, computer failure or malfunction, or any and all
|
|
163
|
-
other commercial damages or losses), even if such Contributor
|
|
164
|
-
has been advised of the possibility of such damages.
|
|
165
|
-
|
|
166
|
-
9. Accepting Warranty or Additional Liability. While redistributing
|
|
167
|
-
the Work or Derivative Works thereof, You may choose to offer,
|
|
168
|
-
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
169
|
-
or other liability obligations and/or rights consistent with this
|
|
170
|
-
License. However, in accepting such obligations, You may act only
|
|
171
|
-
on Your own behalf and on Your sole responsibility, not on behalf
|
|
172
|
-
of any other Contributor, and only if You agree to indemnify,
|
|
173
|
-
defend, and hold each Contributor harmless for any liability
|
|
174
|
-
incurred by, or claims asserted against, such Contributor by reason
|
|
175
|
-
of your accepting any such warranty or additional liability.
|
|
176
|
-
|
|
177
|
-
END OF TERMS AND CONDITIONS
|
|
178
|
-
Copyright 2017 DigitalOcean
|
|
File without changes
|
|
File without changes
|