sws-api-client 0.0.9.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.
Files changed (21) hide show
  1. {sws_api_client-0.0.9.dev0/sws_api_client.egg-info → sws_api_client-1.0.0b0}/PKG-INFO +34 -12
  2. {sws_api_client-0.0.9.dev0 → sws_api_client-1.0.0b0}/README.md +31 -10
  3. {sws_api_client-0.0.9.dev0 → sws_api_client-1.0.0b0}/setup.py +2 -2
  4. sws_api_client-1.0.0b0/sws_api_client/auth.py +7 -0
  5. sws_api_client-1.0.0b0/sws_api_client/datasets.py +127 -0
  6. {sws_api_client-0.0.9.dev0 → sws_api_client-1.0.0b0}/sws_api_client/discover.py +26 -9
  7. sws_api_client-1.0.0b0/sws_api_client/sws_api_client.py +117 -0
  8. {sws_api_client-0.0.9.dev0 → sws_api_client-1.0.0b0}/sws_api_client/tags.py +2 -2
  9. {sws_api_client-0.0.9.dev0 → sws_api_client-1.0.0b0}/sws_api_client/tasks.py +5 -2
  10. sws_api_client-1.0.0b0/sws_api_client/token.py +39 -0
  11. {sws_api_client-0.0.9.dev0 → sws_api_client-1.0.0b0/sws_api_client.egg-info}/PKG-INFO +34 -12
  12. {sws_api_client-0.0.9.dev0 → sws_api_client-1.0.0b0}/sws_api_client.egg-info/SOURCES.txt +2 -0
  13. sws_api_client-1.0.0b0/sws_api_client.egg-info/requires.txt +3 -0
  14. sws_api_client-0.0.9.dev0/sws_api_client/datasets.py +0 -15
  15. sws_api_client-0.0.9.dev0/sws_api_client/sws_api_client.py +0 -101
  16. sws_api_client-0.0.9.dev0/sws_api_client.egg-info/requires.txt +0 -2
  17. {sws_api_client-0.0.9.dev0 → sws_api_client-1.0.0b0}/LICENSE +0 -0
  18. {sws_api_client-0.0.9.dev0 → sws_api_client-1.0.0b0}/setup.cfg +0 -0
  19. {sws_api_client-0.0.9.dev0 → sws_api_client-1.0.0b0}/sws_api_client/__init__.py +0 -0
  20. {sws_api_client-0.0.9.dev0 → sws_api_client-1.0.0b0}/sws_api_client.egg-info/dependency_links.txt +0 -0
  21. {sws_api_client-0.0.9.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: 0.0.9.dev0
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: dataclasses>=0.6
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": "<sws_endpoint>",
75
- "access_token": "<access_token>"
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 `from_conf`:
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.from_conf(conf_file="<conf_file_path>")
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.from_env()
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
- dataset_details = sws_client.get_dataset_export_details(dataset_id="<dataset_id>")
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": "<sws_endpoint>",
54
- "access_token": "<access_token>"
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 `from_conf`:
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.from_conf(conf_file="<conf_file_path>")
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.from_env()
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
- dataset_details = sws_client.get_dataset_export_details(dataset_id="<dataset_id>")
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.9-dev"
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","dataclasses>=0.6"],
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,7 @@
1
+ from pydantic import BaseModel
2
+
3
+ class AuthClientCredentials(BaseModel):
4
+ clientId: str
5
+ clientSecret: str
6
+ scope: str
7
+ tokenEndpoint: str
@@ -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,access_token:str, discover:dict) -> None:
6
- self.discover = discover
7
- self.access_token = access_token
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
- full_headers = {"Authorization": self.access_token}
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
- print(f"HTTP Error: {errh}")
33
- print(f"HTTP Status Code: {errh.response.status_code}")
34
- print(f"Response Text: {errh.response.text}")
35
- print(f"Request URL: {errh.request.url}")
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
- print(f"Request Exception: {err}")
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,7 +1,7 @@
1
1
  # task_manager.py
2
+ import logging
2
3
  from sws_api_client.sws_api_client import SwsApiClient
3
- from dataclasses import dataclass
4
- from typing import List, Dict, Optional
4
+ logger = logging.getLogger(__name__)
5
5
 
6
6
  class Tags:
7
7
 
@@ -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:
@@ -127,7 +130,7 @@ class TaskManager:
127
130
  user=task.get('info').get('user'),
128
131
  outcome=task.get('info').get('outcome'),
129
132
  status=task.get('info').get('status'),
130
- group=task.get('group')
133
+ group=task.get('info').get('group')
131
134
  )
132
135
  )
133
136
 
@@ -177,7 +180,7 @@ class TaskManager:
177
180
  not_created_counter = 0
178
181
 
179
182
  task_status = task_response.info.detail_status
180
- print(f"Task {task_id} status: {task_status}")
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: 0.0.9.dev0
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: dataclasses>=0.6
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": "<sws_endpoint>",
75
- "access_token": "<access_token>"
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 `from_conf`:
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.from_conf(conf_file="<conf_file_path>")
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.from_env()
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
- dataset_details = sws_client.get_dataset_export_details(dataset_id="<dataset_id>")
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
@@ -0,0 +1,3 @@
1
+ requests>=2.31.0
2
+ pydantic>=2.7.4
3
+ python-dotenv>=0.19.0
@@ -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
@@ -1,2 +0,0 @@
1
- requests>=2.31.0
2
- dataclasses>=0.6