maxar-platform 1.0.0__py3-none-any.whl
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.
- maxar_platform/__init__.py +32 -0
- maxar_platform/api_keys.py +179 -0
- maxar_platform/discovery.py +163 -0
- maxar_platform/exceptions.py +124 -0
- maxar_platform/monitoring.py +171 -0
- maxar_platform/ogc/__init__.py +323 -0
- maxar_platform/ogc/footprints.py +55 -0
- maxar_platform/ogc/imagery.py +60 -0
- maxar_platform/ogc/seamlines.py +51 -0
- maxar_platform/ordering.py +189 -0
- maxar_platform/raster_analytics.py +179 -0
- maxar_platform/session.py +397 -0
- maxar_platform/tasking.py +158 -0
- maxar_platform/usage.py +53 -0
- maxar_platform-1.0.0.dist-info/LICENSE.txt +21 -0
- maxar_platform-1.0.0.dist-info/METADATA +70 -0
- maxar_platform-1.0.0.dist-info/RECORD +18 -0
- maxar_platform-1.0.0.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Provides
|
|
3
|
+
--------
|
|
4
|
+
|
|
5
|
+
Streamlined access to these Maxar Geospatial Platform APIs:
|
|
6
|
+
|
|
7
|
+
- Authentication
|
|
8
|
+
- Discovery Catalog
|
|
9
|
+
- OGC Image streaming, vectors, and metadata
|
|
10
|
+
- Ordering
|
|
11
|
+
- Monitoring
|
|
12
|
+
- Tasking
|
|
13
|
+
- Raster Analytics
|
|
14
|
+
|
|
15
|
+
Coming soon:
|
|
16
|
+
|
|
17
|
+
- 3D Analytics
|
|
18
|
+
- Change Monitoring
|
|
19
|
+
|
|
20
|
+
Reference
|
|
21
|
+
---------
|
|
22
|
+
|
|
23
|
+
https://developers.maxar.com/
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
__version__ = "1.0.0"
|
|
27
|
+
|
|
28
|
+
import os
|
|
29
|
+
|
|
30
|
+
BASE_URL = os.environ.get("MGP_BASE_URL") or "https://api.maxar.com/"
|
|
31
|
+
CLIENT_ID = os.environ.get("MAXAR_CLIENT_ID") or "mgp"
|
|
32
|
+
PACKAGE = "maxar_platform"
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
"""
|
|
2
|
+
API keys are an alternative to OAuth2 authentication.
|
|
3
|
+
|
|
4
|
+
They are more compact than OAuth2 bearer tokens which makes them easier to send as URL parameters.
|
|
5
|
+
|
|
6
|
+
Unlike bearer tokens which expire in two hours, API keys have a default 6 month lifespan. It is also possible to
|
|
7
|
+
set the expiration date as short as needed. API keys can not be renewed or extended, once they expire a new one must
|
|
8
|
+
be created.
|
|
9
|
+
|
|
10
|
+
API keys have the same roles and scopes as your account but can not be used on any of the administrative endpoints:
|
|
11
|
+
|
|
12
|
+
- Admin API
|
|
13
|
+
- API Key Service
|
|
14
|
+
- Oauth2 Authentication
|
|
15
|
+
|
|
16
|
+
API Key dictionaries use a custom representations that mask out the API key string when they are displayed/printed.
|
|
17
|
+
|
|
18
|
+
>>> key = generate_key("my key")
|
|
19
|
+
>>> print(key)
|
|
20
|
+
|
|
21
|
+
{'id': '5431dd15-3efe-418a-b04d-acdca3a4d281',
|
|
22
|
+
'name': 'my key',
|
|
23
|
+
'description': '',
|
|
24
|
+
'api_key': '*******',
|
|
25
|
+
'last_modified_date': '2023-07-20 19:44:07',
|
|
26
|
+
'created_date': '2023-07-20 19:44:07',
|
|
27
|
+
'expiration_date': '2024-03-12 18:33:23'}
|
|
28
|
+
|
|
29
|
+
The `api_key` value is not otherwise masked, be careful with:
|
|
30
|
+
|
|
31
|
+
>>> print(key['api_key'])
|
|
32
|
+
<your key will print out!>
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
Reference
|
|
36
|
+
---------
|
|
37
|
+
|
|
38
|
+
https://developers.maxar.com/docs/authentication/guides/api-key
|
|
39
|
+
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
import pprint
|
|
43
|
+
from posixpath import join
|
|
44
|
+
|
|
45
|
+
import pendulum
|
|
46
|
+
|
|
47
|
+
from maxar_platform import BASE_URL
|
|
48
|
+
from maxar_platform.exceptions import MissingAPIResource
|
|
49
|
+
from maxar_platform.session import session
|
|
50
|
+
|
|
51
|
+
__all__ = ["generate_key", "list_keys", "delete_key", "get_key"]
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
KEYS_URL = join(BASE_URL, "api-key/api/v2/api-key")
|
|
55
|
+
KEYS_KEY = join(KEYS_URL, "id/:id")
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def generate_key(name="", description="", expiration=None, lifespan=None):
|
|
59
|
+
"""Generate a new API key
|
|
60
|
+
|
|
61
|
+
Keywords
|
|
62
|
+
----------
|
|
63
|
+
name: str, optional
|
|
64
|
+
Name of key, while optional highly recommended.
|
|
65
|
+
description: str, optional
|
|
66
|
+
Description of key.
|
|
67
|
+
expiration: datelike object
|
|
68
|
+
Expiration date. Can be Date or Datetime objects, or strings of dates in common formats. Maximum lifespan is 6 months.
|
|
69
|
+
lifespan: int
|
|
70
|
+
Lifespan of keys in days if you don't want to compute an expiration date.
|
|
71
|
+
|
|
72
|
+
Returns
|
|
73
|
+
-------
|
|
74
|
+
KeyDict
|
|
75
|
+
Key as Python dictionary with special `str` and `repr` methods to mask keys from accidental display.
|
|
76
|
+
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
expires_format = "YYYY-MM-DD HH:mm:ss"
|
|
80
|
+
|
|
81
|
+
payload = {"name": name, "description": description}
|
|
82
|
+
|
|
83
|
+
if expiration and lifespan:
|
|
84
|
+
raise ValueError("Tokens can be given an `expiration` OR a `lifespan`, not both")
|
|
85
|
+
|
|
86
|
+
if expiration:
|
|
87
|
+
expires = pendulum.parse(expiration)
|
|
88
|
+
payload["expiration_date"] = expires.format(expires_format)
|
|
89
|
+
|
|
90
|
+
if lifespan:
|
|
91
|
+
expires = pendulum.now().add(days=lifespan)
|
|
92
|
+
payload["expiration_date"] = expires.format(expires_format)
|
|
93
|
+
|
|
94
|
+
return KeyDict(session.oauth_session.post(KEYS_URL, json=payload).json())
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def list_keys():
|
|
98
|
+
"""List keys associated with your user
|
|
99
|
+
|
|
100
|
+
Returns
|
|
101
|
+
-------
|
|
102
|
+
list of KeyDict
|
|
103
|
+
All of user's keys, including expired ones
|
|
104
|
+
|
|
105
|
+
"""
|
|
106
|
+
return [KeyDict(k) for k in session.oauth_session.get(KEYS_URL).json()]
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def delete_key(key_or_id):
|
|
110
|
+
"""Delete an API key
|
|
111
|
+
|
|
112
|
+
Parameters
|
|
113
|
+
----------
|
|
114
|
+
key: KeyDict or str
|
|
115
|
+
Either a key dictionary, or a key's ID string
|
|
116
|
+
|
|
117
|
+
Returns
|
|
118
|
+
-------
|
|
119
|
+
None
|
|
120
|
+
|
|
121
|
+
Note
|
|
122
|
+
----
|
|
123
|
+
A key's ID is the 'id' value, not the 'api_key' value that is used for authentication.
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
"""
|
|
127
|
+
if isinstance(key_or_id, dict):
|
|
128
|
+
key_id = key_or_id["id"]
|
|
129
|
+
else:
|
|
130
|
+
key_id = key_or_id
|
|
131
|
+
url = KEYS_KEY.replace(":id", key_id)
|
|
132
|
+
try:
|
|
133
|
+
session.oauth_session.delete(url)
|
|
134
|
+
except MissingAPIResource:
|
|
135
|
+
raise MissingAPIResource(f"API Key with ID {key_id} was not found")
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def get_key(**kwargs):
|
|
139
|
+
"""Get a key
|
|
140
|
+
|
|
141
|
+
Keywords
|
|
142
|
+
----------
|
|
143
|
+
id: str, optional
|
|
144
|
+
name: str, optional
|
|
145
|
+
description: str, optional
|
|
146
|
+
api_key: str, optional
|
|
147
|
+
|
|
148
|
+
Returns
|
|
149
|
+
-------
|
|
150
|
+
KeyDict
|
|
151
|
+
The first key found matching the supplied keyword arguments.
|
|
152
|
+
|
|
153
|
+
Note
|
|
154
|
+
----
|
|
155
|
+
You can match to any ONE of the KeyDict fields, however in most cases you would use ID or name.
|
|
156
|
+
Matching is exact, there is no support for wildcards or other regex magic at this time.
|
|
157
|
+
|
|
158
|
+
"""
|
|
159
|
+
if len(kwargs) > 1:
|
|
160
|
+
raise ValueError("API keys can only be retrieved on a single property")
|
|
161
|
+
for k, v in kwargs.items():
|
|
162
|
+
for key in list_keys():
|
|
163
|
+
if key[k] == v:
|
|
164
|
+
return KeyDict(key)
|
|
165
|
+
raise MissingAPIResource(f"API Key with {k} `{v}` was not found")
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
class KeyDict(dict):
|
|
169
|
+
"""A dictionary class for keys that masks the key value when printed or displayed"""
|
|
170
|
+
|
|
171
|
+
def __str__(self):
|
|
172
|
+
masked = self.copy()
|
|
173
|
+
masked["api_key"] = "**************"
|
|
174
|
+
return pprint.pformat(masked, sort_dicts=False)
|
|
175
|
+
|
|
176
|
+
def __repr__(self):
|
|
177
|
+
masked = self.copy()
|
|
178
|
+
masked["api_key"] = "*******"
|
|
179
|
+
return str(masked)
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
""" Discovery API
|
|
2
|
+
|
|
3
|
+
Discovery is Maxar's data catalog. Since it implements STAC API, this SDK uses `pystac_client` for access.
|
|
4
|
+
|
|
5
|
+
For search, open a catalog or subcatalog to get a STAC client.
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
from maxar_platform.discovery import open_catalog
|
|
9
|
+
open_catalog() # returns the root catalog, or pass a subcatalog name like "imagery"
|
|
10
|
+
results = stac_api_client.search(...)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
Reference
|
|
14
|
+
---------
|
|
15
|
+
https://developers.maxar.com/docs/discovery/
|
|
16
|
+
|
|
17
|
+
https://pystac-client.readthedocs.io/en/latest/usage.html#itemsearch
|
|
18
|
+
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
import json
|
|
22
|
+
import logging
|
|
23
|
+
from copy import deepcopy
|
|
24
|
+
from posixpath import join
|
|
25
|
+
|
|
26
|
+
from pygeofilter.backends.cql2_json import to_cql2
|
|
27
|
+
from pygeofilter.parsers.cql2_text import parse as cql2_parse
|
|
28
|
+
from pystac_client import Client
|
|
29
|
+
from pystac_client.exceptions import APIError
|
|
30
|
+
from pystac_client.stac_api_io import StacApiIO
|
|
31
|
+
|
|
32
|
+
from maxar_platform import BASE_URL
|
|
33
|
+
from maxar_platform.session import session
|
|
34
|
+
|
|
35
|
+
logger = logging.getLogger(__name__)
|
|
36
|
+
|
|
37
|
+
from typing import Any, Dict, Optional
|
|
38
|
+
|
|
39
|
+
DISCOVERY_URL = join(BASE_URL, "discovery/v1/")
|
|
40
|
+
DISCOVERY_CATALOG = join(DISCOVERY_URL, "catalogs/:subCatalogId")
|
|
41
|
+
|
|
42
|
+
__all__ = ["stac_api_client", "open_catalog"]
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class MaxarStacApiIO(StacApiIO):
|
|
46
|
+
|
|
47
|
+
area_based_calc = False
|
|
48
|
+
|
|
49
|
+
def request(
|
|
50
|
+
self,
|
|
51
|
+
href: str,
|
|
52
|
+
method: Optional[str] = None,
|
|
53
|
+
headers: Optional[Dict[str, str]] = None,
|
|
54
|
+
parameters: Optional[Dict[str, Any]] = None,
|
|
55
|
+
) -> str:
|
|
56
|
+
"""Makes a request to an http endpoint
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
href (str): The request URL
|
|
60
|
+
method (Optional[str], optional): The http method to use, 'GET' or 'POST'.
|
|
61
|
+
Defaults to None, which will result in 'GET' being used.
|
|
62
|
+
headers (Optional[Dict[str, str]], optional): Additional headers to include
|
|
63
|
+
in request. Defaults to None.
|
|
64
|
+
parameters (Optional[Dict[str, Any]], optional): parameters to send with
|
|
65
|
+
request. Defaults to None.
|
|
66
|
+
|
|
67
|
+
Raises:
|
|
68
|
+
APIError: raised if the server returns an error response
|
|
69
|
+
|
|
70
|
+
Return:
|
|
71
|
+
str: The decoded response from the endpoint
|
|
72
|
+
|
|
73
|
+
Note:
|
|
74
|
+
This follows the default StacApiIO call but uses the SDK's session object. It also converts
|
|
75
|
+
cql2 text queries to json since the Discovery API currently doesn't accept text queries in
|
|
76
|
+
POST queries.
|
|
77
|
+
"""
|
|
78
|
+
if method == "POST":
|
|
79
|
+
if parameters.get("filter-lang") == "cql2-text":
|
|
80
|
+
ast = cql2_parse(parameters["filter"])
|
|
81
|
+
parameters["filter"] = json.loads(to_cql2(ast))
|
|
82
|
+
parameters["filter-lang"] = "cql2-json"
|
|
83
|
+
if self.area_based_calc and parameters:
|
|
84
|
+
if "bbox" in parameters or "intersects" in parameters:
|
|
85
|
+
parameters.update({"area-based-calc": True})
|
|
86
|
+
resp = session.post(url=href, headers=headers, json=parameters)
|
|
87
|
+
else:
|
|
88
|
+
params = deepcopy(parameters) or {}
|
|
89
|
+
if self.area_based_calc and parameters:
|
|
90
|
+
if "bbox" in parameters or "intersects" in parameters:
|
|
91
|
+
params.update({"area-based-calc": 1})
|
|
92
|
+
resp = session.get(url=href, headers=headers, params=params)
|
|
93
|
+
try:
|
|
94
|
+
return resp.content.decode("utf-8")
|
|
95
|
+
except Exception as err:
|
|
96
|
+
raise APIError(str(err))
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def open_catalog(catalog=None):
|
|
100
|
+
"""Open a pystac_client connection to a Discovery catalog
|
|
101
|
+
|
|
102
|
+
Parameters
|
|
103
|
+
----------
|
|
104
|
+
catalog : str, default None
|
|
105
|
+
Catalog or subcatalog name to open, i.e. "imagery". If omitted the root catalog is used.
|
|
106
|
+
|
|
107
|
+
Returns
|
|
108
|
+
-------
|
|
109
|
+
pystac_client.Client
|
|
110
|
+
A Client object, configured to use Maxar authentication and area-based calculations
|
|
111
|
+
|
|
112
|
+
Reference
|
|
113
|
+
---------
|
|
114
|
+
https://pystac-client.readthedocs.io/en/latest/api.html#collection-client"""
|
|
115
|
+
|
|
116
|
+
if not catalog:
|
|
117
|
+
url = DISCOVERY_URL
|
|
118
|
+
else:
|
|
119
|
+
url = DISCOVERY_CATALOG.replace(":subCatalogId", catalog)
|
|
120
|
+
|
|
121
|
+
client = Client.open(url, stac_io=MaxarStacApiIO())
|
|
122
|
+
|
|
123
|
+
def abc_getter(self):
|
|
124
|
+
return self._stac_io.area_based_calc
|
|
125
|
+
|
|
126
|
+
def abc_setter(self, val):
|
|
127
|
+
if type(val) is not bool:
|
|
128
|
+
raise ValueError("Value must be a boolean")
|
|
129
|
+
self._stac_io.area_based_calc = val
|
|
130
|
+
|
|
131
|
+
setattr(
|
|
132
|
+
client.__class__,
|
|
133
|
+
"area_based_calc",
|
|
134
|
+
property(abc_getter, abc_setter, doc="Return metadata with area-based calculations"),
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
return client
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
stac_api_client = open_catalog()
|
|
141
|
+
""" A pre-configured `pystac-client` instance ready to search the root catalog.
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
Attributes
|
|
145
|
+
----------
|
|
146
|
+
area_based_calc : Bool
|
|
147
|
+
Toggles area-based calculations, default is False
|
|
148
|
+
|
|
149
|
+
Reference
|
|
150
|
+
---------
|
|
151
|
+
https://pystac-client.readthedocs.io/en/latest/api.html#collection-client """
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
# This is handy for certain searches
|
|
155
|
+
CATID_TO_COLLECTION = {
|
|
156
|
+
"1010": "qb01",
|
|
157
|
+
"1020": "wv01",
|
|
158
|
+
"1030": "wv02",
|
|
159
|
+
"1040": "wv03-vnir",
|
|
160
|
+
"104A": "wv03-swir",
|
|
161
|
+
"1050": "ge01",
|
|
162
|
+
"1060": "ik02",
|
|
163
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
""" SDK Exceptions """
|
|
2
|
+
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"MissingDependency",
|
|
7
|
+
"APIServerException",
|
|
8
|
+
"BadAPIRequest",
|
|
9
|
+
"MissingAPIResource",
|
|
10
|
+
"OversizeRequestException",
|
|
11
|
+
"UnAuthorizedException",
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
#########
|
|
15
|
+
# General Errors
|
|
16
|
+
#########
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class MissingDependency(Exception):
|
|
20
|
+
"""A dependency required for this functionality has not been installed"""
|
|
21
|
+
|
|
22
|
+
pass
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
#########
|
|
26
|
+
# API Errors
|
|
27
|
+
#########
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class APIServerException(Exception):
|
|
31
|
+
"""The API service encountered an error"""
|
|
32
|
+
|
|
33
|
+
def __init__(self, msg, *args, **kwargs):
|
|
34
|
+
msg = f"{msg} ({datetime.now()})"
|
|
35
|
+
super().__init__(msg, *args, **kwargs)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class BadAPIRequest(Exception):
|
|
39
|
+
"""The API request sent was incorrectly formatted"""
|
|
40
|
+
|
|
41
|
+
pass
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class MissingAPIResource(Exception):
|
|
45
|
+
"""An API request returned a 404"""
|
|
46
|
+
|
|
47
|
+
pass
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class OversizeRequestException(Exception):
|
|
51
|
+
"""The API request sent was incorrectly formatted"""
|
|
52
|
+
|
|
53
|
+
pass
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class UnAuthorizedException(Exception):
|
|
57
|
+
"""The API request's credentials were rejected"""
|
|
58
|
+
|
|
59
|
+
pass
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
########
|
|
63
|
+
# Hook functions for Request.Session.hooks
|
|
64
|
+
########
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def messenger(request):
|
|
68
|
+
try:
|
|
69
|
+
payload = request.json()
|
|
70
|
+
message = payload.get("message")
|
|
71
|
+
messages = payload.get("error").get("error_messages")
|
|
72
|
+
if messages:
|
|
73
|
+
messages = "; ".join(messages)
|
|
74
|
+
return message or messages or request.text
|
|
75
|
+
except:
|
|
76
|
+
return request.text
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def oversize_request_hook(r, *args, **kwargs):
|
|
80
|
+
"""
|
|
81
|
+
Hook for handling oversize request errors from API.
|
|
82
|
+
"""
|
|
83
|
+
if r.status_code == 413 and "Request Entity Too Large" in r.json()["message"]:
|
|
84
|
+
raise OversizeRequestException(f"{messenger(r)}, Reduce size of attributes and try again.")
|
|
85
|
+
|
|
86
|
+
return r
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def bad_api_request_hook(r, *args, **kwargs):
|
|
90
|
+
"""
|
|
91
|
+
Hook for handling 400 errors from API.
|
|
92
|
+
"""
|
|
93
|
+
if r.status_code == 400:
|
|
94
|
+
raise BadAPIRequest(messenger(r))
|
|
95
|
+
return r
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def unauth_request_hook(r, *args, **kwargs):
|
|
99
|
+
"""
|
|
100
|
+
Hook for handling 401 errors from API.
|
|
101
|
+
"""
|
|
102
|
+
if r.status_code in [401, 403]:
|
|
103
|
+
raise UnAuthorizedException(
|
|
104
|
+
f"Server responded with {r.status_code} '{messenger(r)}', check your credentials and try again."
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
return r
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def missing_resource_hook(r, *args, **kwargs):
|
|
111
|
+
if r.status_code == 404:
|
|
112
|
+
raise MissingAPIResource(messenger(r))
|
|
113
|
+
|
|
114
|
+
return r
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def api_server_request_hook(r, *args, **kwargs):
|
|
118
|
+
"""
|
|
119
|
+
Hook for handling non-20X errors from API.
|
|
120
|
+
"""
|
|
121
|
+
if not r.ok:
|
|
122
|
+
raise APIServerException(messenger(r))
|
|
123
|
+
|
|
124
|
+
return r
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Provides
|
|
3
|
+
--------
|
|
4
|
+
|
|
5
|
+
Functions to maintain Monitors:
|
|
6
|
+
|
|
7
|
+
- create and update a monitor
|
|
8
|
+
- enable/disable a monitor
|
|
9
|
+
- get one or list many monitors
|
|
10
|
+
- see events that the monitor has matched
|
|
11
|
+
|
|
12
|
+
Reference
|
|
13
|
+
---------
|
|
14
|
+
https://developers.maxar.com/docs/monitoring/
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from posixpath import join
|
|
18
|
+
from typing import List, Union
|
|
19
|
+
|
|
20
|
+
from maxar_platform import BASE_URL
|
|
21
|
+
from maxar_platform.session import paginated_request, session
|
|
22
|
+
|
|
23
|
+
__all__ = (
|
|
24
|
+
"create_monitor",
|
|
25
|
+
"get_monitor",
|
|
26
|
+
"disable_monitor",
|
|
27
|
+
"enable_monitor",
|
|
28
|
+
"update_monitor",
|
|
29
|
+
"list_monitors",
|
|
30
|
+
"list_monitor_events",
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
MONITORING_BASE = BASE_URL + "monitoring/v1/monitors"
|
|
34
|
+
MONITORING_MONITOR = join(MONITORING_BASE, ":id")
|
|
35
|
+
MONITORING_EVENTS = join(MONITORING_MONITOR, "events")
|
|
36
|
+
MONITORING_ENABLE = join(MONITORING_MONITOR, "enable")
|
|
37
|
+
MONITORING_DISABLE = join(MONITORING_MONITOR, "disable")
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def list_monitors(
|
|
41
|
+
limit=100,
|
|
42
|
+
sort="desc",
|
|
43
|
+
filter=None,
|
|
44
|
+
):
|
|
45
|
+
"""Fetch user's monitors
|
|
46
|
+
|
|
47
|
+
Parameters
|
|
48
|
+
----------
|
|
49
|
+
limit: int or None, default None
|
|
50
|
+
Maximum number of orders to fetch, None means unlimited, default is 100.
|
|
51
|
+
sort: str
|
|
52
|
+
Sort order for order IDs, either 'asc' (ascending) or 'desc' (descending). Default is 'desc'.
|
|
53
|
+
filter: str or iterable of strings
|
|
54
|
+
Filter results using `field:value` syntax.
|
|
55
|
+
Matches are case sensitive and will match if the value is found anywhere in the field: filtering on a value of `description:NV` will return `Las Vegas, NV` along with `DENVER CO` descriptions.
|
|
56
|
+
Multiple filters can be passed but keys must be unique since filters will be logically ANDed.
|
|
57
|
+
Example: 'description:denver' or ['name:denver', 'description:parking']
|
|
58
|
+
|
|
59
|
+
Returns
|
|
60
|
+
-------
|
|
61
|
+
list
|
|
62
|
+
monitor objects matching parameters"""
|
|
63
|
+
|
|
64
|
+
params = {}
|
|
65
|
+
|
|
66
|
+
if filter is not None:
|
|
67
|
+
params["filter"] = filter
|
|
68
|
+
|
|
69
|
+
return paginated_request("GET", MONITORING_BASE, "monitors", sort, limit, **params)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def get_monitor(monitor_id):
|
|
73
|
+
"""Fetch raw data about a monitor from an ID
|
|
74
|
+
|
|
75
|
+
Parameters
|
|
76
|
+
----------
|
|
77
|
+
monitor_id : str
|
|
78
|
+
Monitor ID to fetch metadata for
|
|
79
|
+
|
|
80
|
+
Returns
|
|
81
|
+
-------
|
|
82
|
+
dict
|
|
83
|
+
API data for the given monitor"""
|
|
84
|
+
|
|
85
|
+
url = MONITORING_MONITOR.replace(":id", monitor_id)
|
|
86
|
+
r = session.get(url)
|
|
87
|
+
return r.json()["data"]
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def create_monitor(monitor_payload):
|
|
91
|
+
"""Create a new monitor
|
|
92
|
+
|
|
93
|
+
Parameters
|
|
94
|
+
----------
|
|
95
|
+
monitor_payload : dict
|
|
96
|
+
Dictionary of monitor settings
|
|
97
|
+
|
|
98
|
+
Returns
|
|
99
|
+
-------
|
|
100
|
+
dict
|
|
101
|
+
Data for the monitor"""
|
|
102
|
+
|
|
103
|
+
r = session.post(MONITORING_BASE, json=monitor_payload)
|
|
104
|
+
return r.json()["data"]
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def update_monitor(monitor_id, monitor_payload):
|
|
108
|
+
"""Update an existing monitor with new settings
|
|
109
|
+
|
|
110
|
+
Parameters
|
|
111
|
+
----------
|
|
112
|
+
monitor_id : str
|
|
113
|
+
Monitor ID to fetch metadata for
|
|
114
|
+
monitor_payload : dict
|
|
115
|
+
Dictionary of updated fields for the existing monitor
|
|
116
|
+
|
|
117
|
+
Returns
|
|
118
|
+
-------
|
|
119
|
+
dict
|
|
120
|
+
Data for the updated monitor"""
|
|
121
|
+
|
|
122
|
+
url = MONITORING_MONITOR.replace(":id", monitor_id)
|
|
123
|
+
r = session.patch(url, json=monitor_payload)
|
|
124
|
+
return r.json()["data"]
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def list_monitor_events(monitor_id):
|
|
128
|
+
"""Fetch the events for a monitor from an ID
|
|
129
|
+
|
|
130
|
+
Parameters
|
|
131
|
+
----------
|
|
132
|
+
monitor_id : str
|
|
133
|
+
monitor ID to fetch events for
|
|
134
|
+
|
|
135
|
+
Returns
|
|
136
|
+
-------
|
|
137
|
+
list
|
|
138
|
+
Matched events for the given monitor"""
|
|
139
|
+
|
|
140
|
+
url = MONITORING_EVENTS.replace(":id", monitor_id)
|
|
141
|
+
return paginated_request("GET", url, "events", sort="desc")
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def disable_monitor(monitor_id):
|
|
145
|
+
"""Disable a monitor
|
|
146
|
+
|
|
147
|
+
Parameters
|
|
148
|
+
----------
|
|
149
|
+
monitor_id : str
|
|
150
|
+
monitor ID to disable
|
|
151
|
+
|
|
152
|
+
Returns
|
|
153
|
+
-------"""
|
|
154
|
+
|
|
155
|
+
url = MONITORING_DISABLE.replace(":id", monitor_id)
|
|
156
|
+
r = session.post(url)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def enable_monitor(monitor_id):
|
|
160
|
+
"""Enable a monitor
|
|
161
|
+
|
|
162
|
+
Parameters
|
|
163
|
+
----------
|
|
164
|
+
monitor_id : str
|
|
165
|
+
monitor ID to enable
|
|
166
|
+
|
|
167
|
+
Returns
|
|
168
|
+
-------"""
|
|
169
|
+
|
|
170
|
+
url = MONITORING_ENABLE.replace(":id", monitor_id)
|
|
171
|
+
r = session.post(url)
|