sws-api-client 0.0.10.dev0__tar.gz → 1.0.0b0__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.
- {sws_api_client-0.0.10.dev0/sws_api_client.egg-info → sws_api_client-1.0.0b0}/PKG-INFO +34 -12
- {sws_api_client-0.0.10.dev0 → sws_api_client-1.0.0b0}/README.md +31 -10
- {sws_api_client-0.0.10.dev0 → sws_api_client-1.0.0b0}/setup.py +2 -2
- sws_api_client-1.0.0b0/sws_api_client/auth.py +7 -0
- sws_api_client-1.0.0b0/sws_api_client/datasets.py +127 -0
- {sws_api_client-0.0.10.dev0 → sws_api_client-1.0.0b0}/sws_api_client/discover.py +26 -9
- sws_api_client-1.0.0b0/sws_api_client/sws_api_client.py +117 -0
- {sws_api_client-0.0.10.dev0 → sws_api_client-1.0.0b0}/sws_api_client/tags.py +2 -2
- {sws_api_client-0.0.10.dev0 → sws_api_client-1.0.0b0}/sws_api_client/tasks.py +4 -1
- sws_api_client-1.0.0b0/sws_api_client/token.py +39 -0
- {sws_api_client-0.0.10.dev0 → sws_api_client-1.0.0b0/sws_api_client.egg-info}/PKG-INFO +34 -12
- {sws_api_client-0.0.10.dev0 → sws_api_client-1.0.0b0}/sws_api_client.egg-info/SOURCES.txt +2 -0
- sws_api_client-1.0.0b0/sws_api_client.egg-info/requires.txt +3 -0
- sws_api_client-0.0.10.dev0/sws_api_client/datasets.py +0 -15
- sws_api_client-0.0.10.dev0/sws_api_client/sws_api_client.py +0 -101
- sws_api_client-0.0.10.dev0/sws_api_client.egg-info/requires.txt +0 -2
- {sws_api_client-0.0.10.dev0 → sws_api_client-1.0.0b0}/LICENSE +0 -0
- {sws_api_client-0.0.10.dev0 → sws_api_client-1.0.0b0}/setup.cfg +0 -0
- {sws_api_client-0.0.10.dev0 → sws_api_client-1.0.0b0}/sws_api_client/__init__.py +0 -0
- {sws_api_client-0.0.10.dev0 → sws_api_client-1.0.0b0}/sws_api_client.egg-info/dependency_links.txt +0 -0
- {sws_api_client-0.0.10.dev0 → sws_api_client-1.0.0b0}/sws_api_client.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: sws_api_client
|
|
3
|
-
Version:
|
|
3
|
+
Version: 1.0.0b0
|
|
4
4
|
Summary: A Python client to easily interact with the FAO SWS REST APIs
|
|
5
5
|
Home-page: https://bitbucket.org/cioapps/sws-it-python-api-client/src/main/
|
|
6
6
|
Author: Mansillo, Daniele (CSI)
|
|
@@ -16,7 +16,8 @@ Classifier: Operating System :: Microsoft :: Windows
|
|
|
16
16
|
Description-Content-Type: text/markdown
|
|
17
17
|
License-File: LICENSE
|
|
18
18
|
Requires-Dist: requests>=2.31.0
|
|
19
|
-
Requires-Dist:
|
|
19
|
+
Requires-Dist: pydantic>=2.7.4
|
|
20
|
+
Requires-Dist: python-dotenv>=0.19.0
|
|
20
21
|
|
|
21
22
|
|
|
22
23
|
# SWS API Client
|
|
@@ -71,23 +72,29 @@ We need to create a conf file (default name: `"conf_sws_api_client.json"`) with
|
|
|
71
72
|
|
|
72
73
|
```json
|
|
73
74
|
{
|
|
74
|
-
"sws_endpoint": "
|
|
75
|
-
"
|
|
75
|
+
"sws_endpoint": "https://sws.dev.fao.org",
|
|
76
|
+
"sws_token": "XXX",
|
|
77
|
+
"current_task_id": "XXX",
|
|
78
|
+
"current_execution_id": "XXX",
|
|
79
|
+
"authclient": {
|
|
80
|
+
"clientId": "XXX",
|
|
81
|
+
"clientSecret": "XXX",
|
|
82
|
+
"tokenEndpoint": "https://fao-dev.auth.eu-west-1.amazoncognito.com/oauth2/token",
|
|
83
|
+
"scope": "sws/user"
|
|
84
|
+
}
|
|
76
85
|
}
|
|
77
86
|
```
|
|
78
87
|
|
|
79
|
-
And instantiate the client in our script with the class method `
|
|
88
|
+
And instantiate the client in our script with the class method `auto`:
|
|
80
89
|
|
|
81
90
|
```python
|
|
82
91
|
from sws_api_client import SwsApiClient
|
|
83
92
|
|
|
84
|
-
sws_client = SwsApiClient.
|
|
85
|
-
|
|
86
|
-
# Or, if the conf_file is named "conf_sws_api_client.json" and is in the current working directory
|
|
87
|
-
|
|
88
|
-
sws_client = SwsApiClient.from_conf()
|
|
93
|
+
sws_client = SwsApiClient.auto()
|
|
89
94
|
```
|
|
90
95
|
|
|
96
|
+
Behind the scene it will automatically detect the fact that you are in debug mode and it will instanciate the client using the `from_conf` method
|
|
97
|
+
|
|
91
98
|
### Instantiate the client in a SWS plugin
|
|
92
99
|
|
|
93
100
|
When working withing a SWS plugin instantiate the client as:
|
|
@@ -95,13 +102,28 @@ When working withing a SWS plugin instantiate the client as:
|
|
|
95
102
|
```python
|
|
96
103
|
from sws_api_client import SwsApiClient
|
|
97
104
|
|
|
98
|
-
sws_client = SwsApiClient.
|
|
105
|
+
sws_client = SwsApiClient.auto()
|
|
99
106
|
```
|
|
100
107
|
|
|
108
|
+
Behind the scene it will automatically detect the fact that you are in a SWS plugin and it will instanciate the client using the `from_env` method
|
|
109
|
+
|
|
101
110
|
### Perform requests
|
|
102
111
|
|
|
103
112
|
To perform requests you just need to call the available methods using the SwsApiClient object, as an example:
|
|
104
113
|
|
|
105
114
|
```python
|
|
106
|
-
|
|
115
|
+
datasets = Datasets(sws_client)
|
|
116
|
+
dataset = datasets.get_dataset_info('aproduction')
|
|
117
|
+
logger.info(f"Dataset info: {dataset}")
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
more complete examples can be found in the `example` folder:
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
cp example/conf_sws_api_client.json.example example/conf_sws_api_client.json
|
|
124
|
+
# edit the file with your credentials
|
|
125
|
+
python3 -m venv example/.venv
|
|
126
|
+
source example/.venv/bin/activate
|
|
127
|
+
pip install -r example/requirements
|
|
128
|
+
python example/dataset_example.py
|
|
107
129
|
```
|
|
@@ -50,23 +50,29 @@ We need to create a conf file (default name: `"conf_sws_api_client.json"`) with
|
|
|
50
50
|
|
|
51
51
|
```json
|
|
52
52
|
{
|
|
53
|
-
"sws_endpoint": "
|
|
54
|
-
"
|
|
53
|
+
"sws_endpoint": "https://sws.dev.fao.org",
|
|
54
|
+
"sws_token": "XXX",
|
|
55
|
+
"current_task_id": "XXX",
|
|
56
|
+
"current_execution_id": "XXX",
|
|
57
|
+
"authclient": {
|
|
58
|
+
"clientId": "XXX",
|
|
59
|
+
"clientSecret": "XXX",
|
|
60
|
+
"tokenEndpoint": "https://fao-dev.auth.eu-west-1.amazoncognito.com/oauth2/token",
|
|
61
|
+
"scope": "sws/user"
|
|
62
|
+
}
|
|
55
63
|
}
|
|
56
64
|
```
|
|
57
65
|
|
|
58
|
-
And instantiate the client in our script with the class method `
|
|
66
|
+
And instantiate the client in our script with the class method `auto`:
|
|
59
67
|
|
|
60
68
|
```python
|
|
61
69
|
from sws_api_client import SwsApiClient
|
|
62
70
|
|
|
63
|
-
sws_client = SwsApiClient.
|
|
64
|
-
|
|
65
|
-
# Or, if the conf_file is named "conf_sws_api_client.json" and is in the current working directory
|
|
66
|
-
|
|
67
|
-
sws_client = SwsApiClient.from_conf()
|
|
71
|
+
sws_client = SwsApiClient.auto()
|
|
68
72
|
```
|
|
69
73
|
|
|
74
|
+
Behind the scene it will automatically detect the fact that you are in debug mode and it will instanciate the client using the `from_conf` method
|
|
75
|
+
|
|
70
76
|
### Instantiate the client in a SWS plugin
|
|
71
77
|
|
|
72
78
|
When working withing a SWS plugin instantiate the client as:
|
|
@@ -74,13 +80,28 @@ When working withing a SWS plugin instantiate the client as:
|
|
|
74
80
|
```python
|
|
75
81
|
from sws_api_client import SwsApiClient
|
|
76
82
|
|
|
77
|
-
sws_client = SwsApiClient.
|
|
83
|
+
sws_client = SwsApiClient.auto()
|
|
78
84
|
```
|
|
79
85
|
|
|
86
|
+
Behind the scene it will automatically detect the fact that you are in a SWS plugin and it will instanciate the client using the `from_env` method
|
|
87
|
+
|
|
80
88
|
### Perform requests
|
|
81
89
|
|
|
82
90
|
To perform requests you just need to call the available methods using the SwsApiClient object, as an example:
|
|
83
91
|
|
|
84
92
|
```python
|
|
85
|
-
|
|
93
|
+
datasets = Datasets(sws_client)
|
|
94
|
+
dataset = datasets.get_dataset_info('aproduction')
|
|
95
|
+
logger.info(f"Dataset info: {dataset}")
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
more complete examples can be found in the `example` folder:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
cp example/conf_sws_api_client.json.example example/conf_sws_api_client.json
|
|
102
|
+
# edit the file with your credentials
|
|
103
|
+
python3 -m venv example/.venv
|
|
104
|
+
source example/.venv/bin/activate
|
|
105
|
+
pip install -r example/requirements
|
|
106
|
+
python example/dataset_example.py
|
|
86
107
|
```
|
|
@@ -7,7 +7,7 @@ here = os.path.abspath(os.path.dirname(__file__))
|
|
|
7
7
|
with open(os.path.join(here, "README.md"), encoding="utf-8") as fh:
|
|
8
8
|
long_description = "\n" + fh.read()
|
|
9
9
|
|
|
10
|
-
VERSION = "0.0
|
|
10
|
+
VERSION = "1.0.0-beta"
|
|
11
11
|
|
|
12
12
|
# Setting up
|
|
13
13
|
setup(
|
|
@@ -19,7 +19,7 @@ setup(
|
|
|
19
19
|
long_description_content_type="text/markdown",
|
|
20
20
|
long_description=long_description,
|
|
21
21
|
packages=find_packages(),
|
|
22
|
-
install_requires=["requests>=2.31.0","
|
|
22
|
+
install_requires=["requests>=2.31.0", "pydantic>=2.7.4", "python-dotenv>=0.19.0"],
|
|
23
23
|
keywords=["python", "fao", "sws", "rest", "api", "client"],
|
|
24
24
|
classifiers=[
|
|
25
25
|
"Development Status :: 1 - Planning",
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from pydantic import BaseModel
|
|
3
|
+
from typing import List, Optional, Dict
|
|
4
|
+
from sws_api_client.sws_api_client import SwsApiClient
|
|
5
|
+
logger = logging.getLogger(__name__)
|
|
6
|
+
|
|
7
|
+
class Label(BaseModel):
|
|
8
|
+
en: str
|
|
9
|
+
|
|
10
|
+
class Lifecycle(BaseModel):
|
|
11
|
+
state: str
|
|
12
|
+
type: str
|
|
13
|
+
previousState: Optional[str]
|
|
14
|
+
created: int
|
|
15
|
+
lastModified: int
|
|
16
|
+
lastModifiedBy: str
|
|
17
|
+
|
|
18
|
+
class Domain(BaseModel):
|
|
19
|
+
id: str
|
|
20
|
+
label: Label
|
|
21
|
+
description: Dict
|
|
22
|
+
|
|
23
|
+
class Binding(BaseModel):
|
|
24
|
+
joinColumn: str
|
|
25
|
+
|
|
26
|
+
class Dimension(BaseModel):
|
|
27
|
+
id: str
|
|
28
|
+
label: Label
|
|
29
|
+
description: Dict
|
|
30
|
+
sdmxName: str
|
|
31
|
+
codelist: str
|
|
32
|
+
roots: List[str]
|
|
33
|
+
binding: Binding
|
|
34
|
+
checkValidityPeriod: bool
|
|
35
|
+
formulas: List
|
|
36
|
+
type: str
|
|
37
|
+
|
|
38
|
+
class Dimensions(BaseModel):
|
|
39
|
+
dimensions: List[Dimension]
|
|
40
|
+
|
|
41
|
+
class PivotingGrouped(BaseModel):
|
|
42
|
+
id: str
|
|
43
|
+
ascending: bool
|
|
44
|
+
|
|
45
|
+
class Pivoting(BaseModel):
|
|
46
|
+
grouped: List[PivotingGrouped]
|
|
47
|
+
row: PivotingGrouped
|
|
48
|
+
cols: PivotingGrouped
|
|
49
|
+
|
|
50
|
+
class DatasetBinding(BaseModel):
|
|
51
|
+
observationTable: str
|
|
52
|
+
coordinateTable: str
|
|
53
|
+
sessionObservationTable: str
|
|
54
|
+
metadataTable: str
|
|
55
|
+
metadataElementTable: str
|
|
56
|
+
sessionMetadataTable: str
|
|
57
|
+
sessionMetadataElementTable: str
|
|
58
|
+
validationTable: str
|
|
59
|
+
sessionValidationTable: str
|
|
60
|
+
tagObservationTable: str
|
|
61
|
+
tags: List
|
|
62
|
+
|
|
63
|
+
class Dataset(BaseModel):
|
|
64
|
+
id: str
|
|
65
|
+
label: Label
|
|
66
|
+
description: Dict
|
|
67
|
+
sdmxName: str
|
|
68
|
+
lifecycle: Lifecycle
|
|
69
|
+
domain: Domain
|
|
70
|
+
dimensions: Dimensions
|
|
71
|
+
flags: Dict
|
|
72
|
+
rules: Dict
|
|
73
|
+
pivoting: Pivoting
|
|
74
|
+
pluginbar: Dict
|
|
75
|
+
showEmptyRows: bool
|
|
76
|
+
showRealCalc: bool
|
|
77
|
+
useApproveCycle: bool
|
|
78
|
+
binding: DatasetBinding
|
|
79
|
+
|
|
80
|
+
class Fingerprint(BaseModel):
|
|
81
|
+
empty: bool
|
|
82
|
+
sessions: int
|
|
83
|
+
queries: int
|
|
84
|
+
tags: int
|
|
85
|
+
computationTags: int
|
|
86
|
+
modules: int
|
|
87
|
+
|
|
88
|
+
class DataModel(BaseModel):
|
|
89
|
+
dataset: Dataset
|
|
90
|
+
fingerprint: Fingerprint
|
|
91
|
+
|
|
92
|
+
class Datasets:
|
|
93
|
+
|
|
94
|
+
def __init__(self, sws_client: SwsApiClient) -> None:
|
|
95
|
+
self.sws_client = sws_client
|
|
96
|
+
|
|
97
|
+
def get_dataset_export_details(self, dataset_id: str) -> dict:
|
|
98
|
+
|
|
99
|
+
url = f"/dataset/{dataset_id}/info"
|
|
100
|
+
params = {"extended": "true"}
|
|
101
|
+
|
|
102
|
+
response = self.sws_client.discoverable.get('session_api', url, params=params)
|
|
103
|
+
|
|
104
|
+
return response
|
|
105
|
+
|
|
106
|
+
def get_dataset_info(self, dataset_id: str) -> DataModel:
|
|
107
|
+
|
|
108
|
+
url = f"/admin/dataset/{dataset_id}"
|
|
109
|
+
|
|
110
|
+
response = self.sws_client.discoverable.get('is_api', url)
|
|
111
|
+
|
|
112
|
+
return response
|
|
113
|
+
|
|
114
|
+
def create_dataset(self, dataset: Dataset) -> DataModel:
|
|
115
|
+
|
|
116
|
+
url = "/admin/dataset"
|
|
117
|
+
|
|
118
|
+
response = self.sws_client.discoverable.post('is_api', url, data=dataset.dict())
|
|
119
|
+
|
|
120
|
+
return response
|
|
121
|
+
|
|
122
|
+
def clone_dataset(self, dataset_id: str, new_id: str) -> DataModel:
|
|
123
|
+
|
|
124
|
+
dataset = self.get_dataset_info(dataset_id)
|
|
125
|
+
dataset.dataset.id = new_id
|
|
126
|
+
new_dataset = self.create_dataset(dataset.dataset)
|
|
127
|
+
return new_dataset
|
|
@@ -1,11 +1,27 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import os
|
|
1
3
|
import requests
|
|
2
4
|
|
|
5
|
+
from sws_api_client.token import Token
|
|
6
|
+
|
|
7
|
+
logger = logging.getLogger(__name__)
|
|
8
|
+
|
|
3
9
|
class Discover:
|
|
4
10
|
|
|
5
|
-
def __init__(self,
|
|
6
|
-
self.
|
|
7
|
-
self.
|
|
11
|
+
def __init__(self,sws_endpoint:str,sws_token:str, token:Token) -> None:
|
|
12
|
+
self.sws_endpoint = sws_endpoint
|
|
13
|
+
self.sws_token = sws_token
|
|
14
|
+
self.token = token
|
|
15
|
+
self.discover = self.__get_discover()
|
|
16
|
+
logger.debug(f"Discover initialized with endpoint {sws_endpoint} and token {sws_token}")
|
|
8
17
|
|
|
18
|
+
def __get_discover(self) -> dict:
|
|
19
|
+
token = self.token.get_token()
|
|
20
|
+
discover_endpoint = f"{self.sws_endpoint}/discover"
|
|
21
|
+
headers = {"Authorization": token.access_token, 'sws-token': self.sws_token}
|
|
22
|
+
|
|
23
|
+
return requests.get(url=discover_endpoint, headers=headers).json()
|
|
24
|
+
|
|
9
25
|
def call(self, method: str, endpoint: str, path: str, params: dict = None, headers: dict = None, data: dict = None) -> dict:
|
|
10
26
|
if not endpoint:
|
|
11
27
|
raise ValueError("An endpoint must be provided.")
|
|
@@ -15,7 +31,8 @@ class Discover:
|
|
|
15
31
|
|
|
16
32
|
x_api_key = self.discover[endpoint].get("key", "")
|
|
17
33
|
full_path = f"{self.discover[endpoint]['path']}{path}"
|
|
18
|
-
|
|
34
|
+
token = self.token.get_token()
|
|
35
|
+
full_headers = {"Authorization": token.access_token, "sws-token": self.sws_token}
|
|
19
36
|
if x_api_key:
|
|
20
37
|
full_headers["x-api-key"] = x_api_key
|
|
21
38
|
|
|
@@ -29,12 +46,12 @@ class Discover:
|
|
|
29
46
|
response.raise_for_status()
|
|
30
47
|
return response.json()
|
|
31
48
|
except requests.exceptions.HTTPError as errh:
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
49
|
+
logger.error(f"HTTP Error: {errh}")
|
|
50
|
+
logger.error(f"HTTP Status Code: {errh.response.status_code}")
|
|
51
|
+
logger.error(f"Response Text: {errh.response.text}")
|
|
52
|
+
logger.error(f"Request URL: {errh.request.url}")
|
|
36
53
|
except requests.exceptions.RequestException as err:
|
|
37
|
-
|
|
54
|
+
logger.error(f"Request Exception: {err}")
|
|
38
55
|
|
|
39
56
|
return {}
|
|
40
57
|
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import json
|
|
3
|
+
import os
|
|
4
|
+
import boto3
|
|
5
|
+
from typing import Optional
|
|
6
|
+
from sws_api_client.auth import AuthClientCredentials
|
|
7
|
+
from sws_api_client.discover import Discover
|
|
8
|
+
from sws_api_client.token import Token
|
|
9
|
+
import logging
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
DEFAULT_CREDENTIALS_SECRET_NAME = "SWS_USER_CREDENTIALS_SECRET_NAME"
|
|
14
|
+
|
|
15
|
+
class SwsApiClient:
|
|
16
|
+
|
|
17
|
+
def __init__(self,
|
|
18
|
+
sws_endpoint: str,
|
|
19
|
+
sws_token: str,
|
|
20
|
+
authclient: AuthClientCredentials,
|
|
21
|
+
current_task_id:Optional[str],
|
|
22
|
+
current_execution_id:Optional[str]
|
|
23
|
+
) -> None:
|
|
24
|
+
|
|
25
|
+
logger.info("Initializing SwsApiClient")
|
|
26
|
+
|
|
27
|
+
self.sws_endpoint = sws_endpoint
|
|
28
|
+
self.sws_token = sws_token
|
|
29
|
+
self.authclient = authclient
|
|
30
|
+
self.current_task_id = current_task_id
|
|
31
|
+
self.current_execution_id = current_execution_id
|
|
32
|
+
self.token = Token(authclient)
|
|
33
|
+
self.is_debug = self.check_debug()
|
|
34
|
+
self.discoverable = Discover(sws_endpoint=sws_endpoint, sws_token=sws_token, token=self.token)
|
|
35
|
+
logger.debug(f"SwsApiClient initialized with endpoint {sws_endpoint} and token {sws_token}")
|
|
36
|
+
|
|
37
|
+
def __get_authclient_credentials_from_secret(self, env_name="SWS_USER_CREDENTIALS_SECRET_NAME") -> AuthClientCredentials:
|
|
38
|
+
logger.debug(f"Fetching auth client credentials from secret: {env_name}")
|
|
39
|
+
secret_name = os.getenv(env_name)
|
|
40
|
+
self.session = boto3.Session() if self.session is None else self.session
|
|
41
|
+
self.secret_manager = self.session.client('secretsmanager') if self.secret_manager is None else self.secret_manager
|
|
42
|
+
response = self.secret_manager.get_secret_value(SecretId=secret_name)
|
|
43
|
+
secretContent = json.loads(response['SecretString'])
|
|
44
|
+
logger.debug(f"Secret fetched successfully: {secretContent}")
|
|
45
|
+
return AuthClientCredentials(
|
|
46
|
+
clientId=secretContent["ID"],
|
|
47
|
+
clientSecret=secretContent["SECRET"],
|
|
48
|
+
scope=secretContent["SCOPE"],
|
|
49
|
+
tokenEndpoint=secretContent["TOKEN_ENDPOINT"]
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
@classmethod
|
|
53
|
+
def from_env(cls, sws_endpoint_env="SWS_ENDPOINT", authclient_secret_name=DEFAULT_CREDENTIALS_SECRET_NAME):
|
|
54
|
+
logger.debug(f"Creating SwsApiClient from environment variables: {sws_endpoint_env}, {authclient_secret_name}")
|
|
55
|
+
authclient:AuthClientCredentials = cls.__get_authclient_credentials_from_secret(env_name=authclient_secret_name)
|
|
56
|
+
return cls(
|
|
57
|
+
sws_endpoint=os.getenv(sws_endpoint_env),
|
|
58
|
+
current_task_id=os.getenv("TASK_ID"),
|
|
59
|
+
current_execution_id=os.getenv("EXECUTION_ID"),
|
|
60
|
+
authclient=authclient
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
@classmethod
|
|
64
|
+
def from_conf(cls, conf_file="conf_sws_api_client.json"):
|
|
65
|
+
logger.debug(f"Creating SwsApiClient from config file: {conf_file}")
|
|
66
|
+
with open(conf_file) as f:
|
|
67
|
+
kwargs = json.load(f)
|
|
68
|
+
logger.debug(f"Config loaded: {kwargs}")
|
|
69
|
+
return cls(
|
|
70
|
+
sws_endpoint=kwargs["sws_endpoint"],
|
|
71
|
+
sws_token=kwargs["sws_token"],
|
|
72
|
+
current_task_id=kwargs.get("current_task_id"),
|
|
73
|
+
current_execution_id=kwargs.get("current_execution_id"),
|
|
74
|
+
authclient=AuthClientCredentials(**kwargs["authclient"])
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
@classmethod
|
|
78
|
+
def from_args(cls):
|
|
79
|
+
logger.debug("Creating SwsApiClient from command line arguments")
|
|
80
|
+
parser = argparse.ArgumentParser(description="Instantiate SwsApiClient from args")
|
|
81
|
+
parser.add_argument("--sws_endpoint", type=str, required=True, help="The sws endpoint")
|
|
82
|
+
parser.add_argument("--sws_token", type=str, required=True, help="The SWS access token")
|
|
83
|
+
parser.add_argument("--authclient_id", type=str, required=True, help="The authclient ID")
|
|
84
|
+
parser.add_argument("--authclient_secret", type=str, required=True, help="The authclient secret")
|
|
85
|
+
parser.add_argument("--authclient_scope", type=str, required=True, help="The authclient scope")
|
|
86
|
+
parser.add_argument("--authclient_endpoint", type=str, required=True, help="The authclient endpoint URI")
|
|
87
|
+
parser.add_argument("--current_task_id", type=str, required=False, help="The current task ID")
|
|
88
|
+
parser.add_argument("--current_execution_id", type=str, required=False, help="The current execution ID")
|
|
89
|
+
args, _ = parser.parse_known_args()
|
|
90
|
+
props = vars(args)
|
|
91
|
+
logger.debug(f"Arguments parsed: {props}")
|
|
92
|
+
return cls(
|
|
93
|
+
sws_endpoint=props.get("sws_endpoint"),
|
|
94
|
+
current_task_id=props.get("current_task_id"),
|
|
95
|
+
current_execution_id=props.get("current_execution_id"),
|
|
96
|
+
authclient={
|
|
97
|
+
"client_id": props.get("authclient_id"),
|
|
98
|
+
"client_secret": props.get("authclient_secret"),
|
|
99
|
+
"scope": props.get("authclient_scope"),
|
|
100
|
+
"endpoint": props.get("authclient_endpoint")
|
|
101
|
+
}
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
@classmethod
|
|
105
|
+
def check_debug(cls):
|
|
106
|
+
debug = os.getenv("DEBUG_MODE") == "TRUE" or os.getenv("DEBUG_MODE") is None
|
|
107
|
+
logger.debug(f"Debug mode is {'on' if debug else 'off'}")
|
|
108
|
+
return debug
|
|
109
|
+
|
|
110
|
+
@classmethod
|
|
111
|
+
def auto(cls):
|
|
112
|
+
debug = cls.check_debug()
|
|
113
|
+
logger.debug(f"Auto-detecting client creation method, debug mode: {debug}")
|
|
114
|
+
if debug:
|
|
115
|
+
return cls.from_conf()
|
|
116
|
+
else:
|
|
117
|
+
return cls.from_env()
|
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
import logging
|
|
1
2
|
from sws_api_client.sws_api_client import SwsApiClient
|
|
2
3
|
from dataclasses import (dataclass, asdict)
|
|
3
4
|
from typing import List, Dict, Optional, Literal
|
|
4
5
|
import json
|
|
5
6
|
import time
|
|
7
|
+
logger = logging.getLogger(__name__)
|
|
8
|
+
|
|
6
9
|
|
|
7
10
|
@dataclass
|
|
8
11
|
class TaskDataset:
|
|
@@ -177,7 +180,7 @@ class TaskManager:
|
|
|
177
180
|
not_created_counter = 0
|
|
178
181
|
|
|
179
182
|
task_status = task_response.info.detail_status
|
|
180
|
-
|
|
183
|
+
logger.info(f"Task {task_id} status: {task_status}")
|
|
181
184
|
if task_status == 'ENDED':
|
|
182
185
|
return task_response
|
|
183
186
|
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import datetime
|
|
2
|
+
import logging
|
|
3
|
+
import os
|
|
4
|
+
from pydantic import BaseModel
|
|
5
|
+
import requests
|
|
6
|
+
|
|
7
|
+
logger = logging.getLogger(__name__)
|
|
8
|
+
from sws_api_client.auth import AuthClientCredentials
|
|
9
|
+
|
|
10
|
+
class TokenModel(BaseModel):
|
|
11
|
+
access_token: str
|
|
12
|
+
expires_on: datetime.datetime
|
|
13
|
+
|
|
14
|
+
class Token:
|
|
15
|
+
|
|
16
|
+
def __init__(self,authclient:AuthClientCredentials) -> None:
|
|
17
|
+
self.authclient = authclient
|
|
18
|
+
self.token = self.get_token()
|
|
19
|
+
|
|
20
|
+
def get_token(self) -> TokenModel:
|
|
21
|
+
if not hasattr(self, 'token') or self.token.expires_on is None or self.token.expires_on < datetime.datetime.now():
|
|
22
|
+
url = f"{self.authclient.tokenEndpoint}"
|
|
23
|
+
data = {
|
|
24
|
+
"grant_type": "client_credentials",
|
|
25
|
+
"client_id": self.authclient.clientId,
|
|
26
|
+
"client_secret": self.authclient.clientSecret,
|
|
27
|
+
"scope": self.authclient.scope,
|
|
28
|
+
}
|
|
29
|
+
response = requests.post(url, data=data)
|
|
30
|
+
response.raise_for_status()
|
|
31
|
+
result = response.json()
|
|
32
|
+
self.token = TokenModel(
|
|
33
|
+
access_token=result["access_token"],
|
|
34
|
+
expires_on=datetime.datetime.now() + datetime.timedelta(seconds=result["expires_in"])
|
|
35
|
+
)
|
|
36
|
+
logger.debug(f"Token: {self.token}")
|
|
37
|
+
return self.token
|
|
38
|
+
else:
|
|
39
|
+
return self.token
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: sws_api_client
|
|
3
|
-
Version:
|
|
3
|
+
Version: 1.0.0b0
|
|
4
4
|
Summary: A Python client to easily interact with the FAO SWS REST APIs
|
|
5
5
|
Home-page: https://bitbucket.org/cioapps/sws-it-python-api-client/src/main/
|
|
6
6
|
Author: Mansillo, Daniele (CSI)
|
|
@@ -16,7 +16,8 @@ Classifier: Operating System :: Microsoft :: Windows
|
|
|
16
16
|
Description-Content-Type: text/markdown
|
|
17
17
|
License-File: LICENSE
|
|
18
18
|
Requires-Dist: requests>=2.31.0
|
|
19
|
-
Requires-Dist:
|
|
19
|
+
Requires-Dist: pydantic>=2.7.4
|
|
20
|
+
Requires-Dist: python-dotenv>=0.19.0
|
|
20
21
|
|
|
21
22
|
|
|
22
23
|
# SWS API Client
|
|
@@ -71,23 +72,29 @@ We need to create a conf file (default name: `"conf_sws_api_client.json"`) with
|
|
|
71
72
|
|
|
72
73
|
```json
|
|
73
74
|
{
|
|
74
|
-
"sws_endpoint": "
|
|
75
|
-
"
|
|
75
|
+
"sws_endpoint": "https://sws.dev.fao.org",
|
|
76
|
+
"sws_token": "XXX",
|
|
77
|
+
"current_task_id": "XXX",
|
|
78
|
+
"current_execution_id": "XXX",
|
|
79
|
+
"authclient": {
|
|
80
|
+
"clientId": "XXX",
|
|
81
|
+
"clientSecret": "XXX",
|
|
82
|
+
"tokenEndpoint": "https://fao-dev.auth.eu-west-1.amazoncognito.com/oauth2/token",
|
|
83
|
+
"scope": "sws/user"
|
|
84
|
+
}
|
|
76
85
|
}
|
|
77
86
|
```
|
|
78
87
|
|
|
79
|
-
And instantiate the client in our script with the class method `
|
|
88
|
+
And instantiate the client in our script with the class method `auto`:
|
|
80
89
|
|
|
81
90
|
```python
|
|
82
91
|
from sws_api_client import SwsApiClient
|
|
83
92
|
|
|
84
|
-
sws_client = SwsApiClient.
|
|
85
|
-
|
|
86
|
-
# Or, if the conf_file is named "conf_sws_api_client.json" and is in the current working directory
|
|
87
|
-
|
|
88
|
-
sws_client = SwsApiClient.from_conf()
|
|
93
|
+
sws_client = SwsApiClient.auto()
|
|
89
94
|
```
|
|
90
95
|
|
|
96
|
+
Behind the scene it will automatically detect the fact that you are in debug mode and it will instanciate the client using the `from_conf` method
|
|
97
|
+
|
|
91
98
|
### Instantiate the client in a SWS plugin
|
|
92
99
|
|
|
93
100
|
When working withing a SWS plugin instantiate the client as:
|
|
@@ -95,13 +102,28 @@ When working withing a SWS plugin instantiate the client as:
|
|
|
95
102
|
```python
|
|
96
103
|
from sws_api_client import SwsApiClient
|
|
97
104
|
|
|
98
|
-
sws_client = SwsApiClient.
|
|
105
|
+
sws_client = SwsApiClient.auto()
|
|
99
106
|
```
|
|
100
107
|
|
|
108
|
+
Behind the scene it will automatically detect the fact that you are in a SWS plugin and it will instanciate the client using the `from_env` method
|
|
109
|
+
|
|
101
110
|
### Perform requests
|
|
102
111
|
|
|
103
112
|
To perform requests you just need to call the available methods using the SwsApiClient object, as an example:
|
|
104
113
|
|
|
105
114
|
```python
|
|
106
|
-
|
|
115
|
+
datasets = Datasets(sws_client)
|
|
116
|
+
dataset = datasets.get_dataset_info('aproduction')
|
|
117
|
+
logger.info(f"Dataset info: {dataset}")
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
more complete examples can be found in the `example` folder:
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
cp example/conf_sws_api_client.json.example example/conf_sws_api_client.json
|
|
124
|
+
# edit the file with your credentials
|
|
125
|
+
python3 -m venv example/.venv
|
|
126
|
+
source example/.venv/bin/activate
|
|
127
|
+
pip install -r example/requirements
|
|
128
|
+
python example/dataset_example.py
|
|
107
129
|
```
|
|
@@ -2,11 +2,13 @@ LICENSE
|
|
|
2
2
|
README.md
|
|
3
3
|
setup.py
|
|
4
4
|
sws_api_client/__init__.py
|
|
5
|
+
sws_api_client/auth.py
|
|
5
6
|
sws_api_client/datasets.py
|
|
6
7
|
sws_api_client/discover.py
|
|
7
8
|
sws_api_client/sws_api_client.py
|
|
8
9
|
sws_api_client/tags.py
|
|
9
10
|
sws_api_client/tasks.py
|
|
11
|
+
sws_api_client/token.py
|
|
10
12
|
sws_api_client.egg-info/PKG-INFO
|
|
11
13
|
sws_api_client.egg-info/SOURCES.txt
|
|
12
14
|
sws_api_client.egg-info/dependency_links.txt
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
from sws_api_client.sws_api_client import SwsApiClient
|
|
2
|
-
|
|
3
|
-
class Datasets:
|
|
4
|
-
|
|
5
|
-
def __init__(self, sws_client: SwsApiClient) -> None:
|
|
6
|
-
self.sws_client = sws_client
|
|
7
|
-
|
|
8
|
-
def get_dataset_export_details(self, dataset_id: str) -> dict:
|
|
9
|
-
|
|
10
|
-
url = f"/dataset/{dataset_id}/info"
|
|
11
|
-
params = {"extended": "true"}
|
|
12
|
-
|
|
13
|
-
response = self.sws_client.discoverable.get('session_api', url, params=params)
|
|
14
|
-
|
|
15
|
-
return response
|
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
import argparse
|
|
2
|
-
import json
|
|
3
|
-
import os
|
|
4
|
-
|
|
5
|
-
from typing import Optional
|
|
6
|
-
|
|
7
|
-
import requests
|
|
8
|
-
|
|
9
|
-
from sws_api_client.discover import Discover
|
|
10
|
-
|
|
11
|
-
class SwsApiClient:
|
|
12
|
-
|
|
13
|
-
def __init__(self, sws_endpoint: str, access_token: str, current_task_id:Optional[str], current_execution_id:Optional[str] ) -> None:
|
|
14
|
-
self.sws_endpoint = sws_endpoint
|
|
15
|
-
self.access_token = access_token
|
|
16
|
-
self.current_task_id = current_task_id
|
|
17
|
-
self.current_execution_id = current_execution_id
|
|
18
|
-
self.discover = self.__get_discover()
|
|
19
|
-
self.is_debug = self.check_debug()
|
|
20
|
-
self.discoverable = Discover(self.access_token, self.discover)
|
|
21
|
-
|
|
22
|
-
def __get_discover(self) -> dict:
|
|
23
|
-
discover_endpoint = f"{self.sws_endpoint}/discover?v=2"
|
|
24
|
-
headers = {"Authorization": self.access_token}
|
|
25
|
-
return requests.get(url=discover_endpoint, headers=headers).json()
|
|
26
|
-
|
|
27
|
-
@classmethod
|
|
28
|
-
def from_env(cls, sws_endpoint_env="SWS_ENDPOINT", access_token_env="ACCESS_TOKEN"):
|
|
29
|
-
return cls(
|
|
30
|
-
sws_endpoint=os.getenv(sws_endpoint_env),
|
|
31
|
-
access_token=os.getenv(access_token_env),
|
|
32
|
-
current_task_id = os.getenv("TASK_ID"),
|
|
33
|
-
current_execution_id = os.getenv("EXECUTION_ID")
|
|
34
|
-
)
|
|
35
|
-
|
|
36
|
-
@classmethod
|
|
37
|
-
def from_conf(cls, conf_file="conf_sws_api_client.json"):
|
|
38
|
-
with open(conf_file) as f:
|
|
39
|
-
kwargs = json.load(f)
|
|
40
|
-
return cls(**kwargs)
|
|
41
|
-
|
|
42
|
-
@classmethod
|
|
43
|
-
def from_args(cls):
|
|
44
|
-
parser = argparse.ArgumentParser(
|
|
45
|
-
description="Instantiate SwsApiClient from args"
|
|
46
|
-
)
|
|
47
|
-
parser.add_argument(
|
|
48
|
-
"--sws_endpoint", type=str, required=True, help="The sws endpoint"
|
|
49
|
-
)
|
|
50
|
-
parser.add_argument(
|
|
51
|
-
"--access_token", type=str, required=True, help="The access token"
|
|
52
|
-
)
|
|
53
|
-
parser.add_argument(
|
|
54
|
-
"--current_task_id", type=str, required=False, help="The current task ID"
|
|
55
|
-
)
|
|
56
|
-
parser.add_argument(
|
|
57
|
-
"--current_execution_id", type=str, required=False, help="The current execution ID"
|
|
58
|
-
)
|
|
59
|
-
|
|
60
|
-
args, _ = parser.parse_known_args()
|
|
61
|
-
|
|
62
|
-
return cls(**vars(args))
|
|
63
|
-
|
|
64
|
-
@classmethod
|
|
65
|
-
def check_debug(self):
|
|
66
|
-
return os.getenv("DEBUG_MODE") == "TRUE" or os.getenv("DEBUG_MODE") is None
|
|
67
|
-
|
|
68
|
-
@classmethod
|
|
69
|
-
def auto(cls):
|
|
70
|
-
debug = cls.check_debug()
|
|
71
|
-
if debug:
|
|
72
|
-
return cls.from_conf()
|
|
73
|
-
else:
|
|
74
|
-
return cls.from_env()
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
def get_dataset_export_details(self, dataset_id: str) -> dict:
|
|
78
|
-
|
|
79
|
-
session_api_key = self.discover["session_api"]["key"]
|
|
80
|
-
session_api_path = self.discover["session_api"]["path"]
|
|
81
|
-
|
|
82
|
-
url = f"{session_api_path}/dataset/{dataset_id}/info"
|
|
83
|
-
params = {"extended": "true"}
|
|
84
|
-
headers = {"Authorization": self.access_token, "x-api-key": session_api_key}
|
|
85
|
-
|
|
86
|
-
response = requests.get(url, params=params, headers=headers).json()
|
|
87
|
-
|
|
88
|
-
return response
|
|
89
|
-
|
|
90
|
-
def get_read_access_url(self, path: str, expiration: int) -> dict:
|
|
91
|
-
|
|
92
|
-
tag_api_key = self.discover["tag_api"]["key"]
|
|
93
|
-
tag_api_path = self.discover["tag_api"]["path"]
|
|
94
|
-
|
|
95
|
-
url = f"{tag_api_path}/tags/dissemination/getReadAccessUrl"
|
|
96
|
-
body = {"path": path, "expiration": expiration}
|
|
97
|
-
headers = {"Authorization": self.access_token, "x-api-key": tag_api_key}
|
|
98
|
-
|
|
99
|
-
response = requests.post(url, json=body, headers=headers).json()
|
|
100
|
-
|
|
101
|
-
return response
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sws_api_client-0.0.10.dev0 → sws_api_client-1.0.0b0}/sws_api_client.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|