sws-api-client 0.0.2__tar.gz → 0.0.3.dev1__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: sws_api_client
3
- Version: 0.0.2
3
+ Version: 0.0.3.dev1
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,6 +16,7 @@ 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
20
 
20
21
 
21
22
  # SWS API Client
@@ -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.2"
10
+ VERSION = "0.0.3-dev1"
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","dataclasses>=0.6"],
23
23
  keywords=["python", "fao", "sws", "rest", "api", "client"],
24
24
  classifiers=[
25
25
  "Development Status :: 1 - Planning",
@@ -0,0 +1,11 @@
1
+ from .sws_api_client import SwsApiClient
2
+ from .discover import Discover
3
+ from .datasets import Datasets
4
+ from .tasks import (
5
+ TaskManager,
6
+ PluginPayload,
7
+ TaskDataset,
8
+ TaskInfo,
9
+ TaskResponse
10
+ )
11
+ from .tags import Tags
@@ -0,0 +1,15 @@
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
@@ -0,0 +1,51 @@
1
+ import requests
2
+
3
+ class Discover:
4
+
5
+ def __init__(self,access_token:str, discover:dict) -> None:
6
+ self.discover = discover
7
+ self.access_token = access_token
8
+
9
+ def call(self, method: str, endpoint: str, path: str, params: dict = None, headers: dict = None, data: dict = None) -> dict:
10
+ if not endpoint:
11
+ raise ValueError("An endpoint must be provided.")
12
+
13
+ if endpoint not in self.discover or 'path' not in self.discover[endpoint]:
14
+ raise ValueError(f"endpoint '{endpoint}' not found")
15
+
16
+ x_api_key = self.discover[endpoint].get("key", "")
17
+ full_path = f"{self.discover[endpoint]['path']}{path}"
18
+ full_headers = {"Authorization": self.access_token}
19
+ if x_api_key:
20
+ full_headers["x-api-key"] = x_api_key
21
+
22
+ if headers:
23
+ full_headers.update(headers)
24
+
25
+ request_func = getattr(requests, method.lower())
26
+ response = request_func(full_path, params=params, headers=full_headers, json=data)
27
+
28
+ try:
29
+ response.raise_for_status()
30
+ return response.json()
31
+ 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}")
36
+ except requests.exceptions.RequestException as err:
37
+ print(f"Request Exception: {err}")
38
+
39
+ return {}
40
+
41
+ def get(self, endpoint, path: str, params: dict = None, headers: dict = None) -> dict:
42
+ return self.call("GET", endpoint, path, params=params, headers=headers)
43
+
44
+ def post(self, endpoint, path: str, data: dict = None, params: dict = None, headers: dict = None) -> dict:
45
+ return self.call("POST", endpoint, path, params=params, headers=headers, data=data)
46
+
47
+ def put(self, endpoint, path: str, data: dict = None, params: dict = None, headers: dict = None) -> dict:
48
+ return self.call("PUT", endpoint, path, params=params, headers=headers, data=data)
49
+
50
+ def delete(self, endpoint, path: str, params: dict = None, headers: dict = None) -> dict:
51
+ return self.call("DELETE", endpoint, path, params=params, headers=headers)
@@ -2,15 +2,22 @@ import argparse
2
2
  import json
3
3
  import os
4
4
 
5
+ from typing import Optional
6
+
5
7
  import requests
6
8
 
9
+ from sws_api_client.discover import Discover
7
10
 
8
11
  class SwsApiClient:
9
12
 
10
- def __init__(self, sws_endpoint: str, access_token: str) -> None:
13
+ def __init__(self, sws_endpoint: str, access_token: str, current_task_id:Optional[str], current_execution_id:Optional[str] ) -> None:
11
14
  self.sws_endpoint = sws_endpoint
12
15
  self.access_token = access_token
16
+ self.current_task_id = current_task_id
17
+ self.current_execution_id = current_execution_id
13
18
  self.discover = self.__get_discover()
19
+ self.is_debug = self.check_debug()
20
+ self.discoverable = Discover(self.access_token, self.discover)
14
21
 
15
22
  def __get_discover(self) -> dict:
16
23
  discover_endpoint = f"{self.sws_endpoint}/discover?v=2"
@@ -22,6 +29,8 @@ class SwsApiClient:
22
29
  return cls(
23
30
  sws_endpoint=os.getenv(sws_endpoint_env),
24
31
  access_token=os.getenv(access_token_env),
32
+ currentTaskId = os.getenv("TASK_ID"),
33
+ currentExecutionId = os.getenv("EXECUTION_ID")
25
34
  )
26
35
 
27
36
  @classmethod
@@ -41,10 +50,29 @@ class SwsApiClient:
41
50
  parser.add_argument(
42
51
  "--access_token", type=str, required=True, help="The access token"
43
52
  )
53
+ parser.add_argument(
54
+ "--currentTaskId", type=str, required=False, help="The current task ID"
55
+ )
56
+ parser.add_argument(
57
+ "--currentExecutionId", type=str, required=False, help="The current execution ID"
58
+ )
44
59
 
45
60
  args, _ = parser.parse_known_args()
46
61
 
47
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
+
48
76
 
49
77
  def get_dataset_export_details(self, dataset_id: str) -> dict:
50
78
 
@@ -0,0 +1,19 @@
1
+ # task_manager.py
2
+ from sws_api_client.sws_api_client import SwsApiClient
3
+ from dataclasses import dataclass
4
+ from typing import List, Dict, Optional
5
+
6
+ class Tags:
7
+
8
+ def __init__(self, sws_client: SwsApiClient, endpoint: str = 'tag_api') -> None:
9
+ self.sws_client = sws_client
10
+ self.endpoint = endpoint
11
+
12
+ def get_read_access_url(self, path: str, expiration: int) -> dict:
13
+
14
+ url = f"/tags/dissemination/getReadAccessUrl"
15
+ body = {"path": path, "expiration": expiration}
16
+
17
+ response = self.sws_client.discoverable.post(self.endpoint, url, data=body)
18
+
19
+ return response
@@ -0,0 +1,160 @@
1
+ from sws_api_client.sws_api_client import SwsApiClient
2
+ from dataclasses import (dataclass, asdict)
3
+ from typing import List, Dict, Optional, Literal
4
+ import json
5
+ import time
6
+
7
+ @dataclass
8
+ class TaskDataset:
9
+ domain: str
10
+ dataset: str
11
+
12
+ @dataclass
13
+ class PluginPayload:
14
+ datasets: List[TaskDataset]
15
+ parameters: Dict
16
+
17
+ def to_dict(self) -> Dict:
18
+ return asdict(self)
19
+
20
+ # Define Status and DetailStatus as Literal types
21
+ Status = Literal['ACTIVE', 'ARCHIVED']
22
+ DetailStatus = Literal['CREATED', 'EXECUTION_PREPARED', 'EXECUTION_PROCESSING', 'EXECUTION_PROCESSED', 'STOP_REQUESTED', 'RETRIED', 'ENDED', 'ARCHIVED']
23
+ Outcome = Literal['SUCCESS', 'FAILURE']
24
+ @dataclass
25
+ class TaskInfo:
26
+ detail_status: DetailStatus
27
+ ended_on: Optional[str]
28
+ description: str
29
+ updated_on: str
30
+ created_on: str
31
+ service_user: str
32
+ tags: Dict[str, str]
33
+ output: Dict
34
+ input: Dict
35
+ task_type: str
36
+ context: str
37
+ progress: int
38
+ user: str
39
+ outcome: Outcome
40
+ group: Optional[str]
41
+ status: Status
42
+
43
+ @dataclass
44
+ class TaskResponse:
45
+ task_id: str
46
+ info: TaskInfo
47
+
48
+ class TaskManager:
49
+
50
+ def __init__(self, sws_client: SwsApiClient, endpoint: str = 'task_manager_api') -> None:
51
+ self.sws_client = sws_client
52
+ self.endpoint = endpoint
53
+
54
+ def update_current(self, progress: Optional[int] = None):
55
+ if not self.sws_client.current_task_id:
56
+ raise ValueError("A current task ID must be provided.")
57
+ if not self.sws_client.current_execution_id:
58
+ raise ValueError("A current task ID must be provided.")
59
+ taskId = self.sws_client.current_task_id
60
+ executionId = self.sws_client.current_execution_id
61
+
62
+ path = f'/task/{taskId}/execution/{executionId}/status'
63
+ data = {
64
+ 'progress': progress
65
+ }
66
+ self.sws_client.discoverable.put(self.endpoint, path, data=data)
67
+
68
+ def create_plugin_task(self,
69
+ pluginId:str,
70
+ slow:bool,
71
+ payload: PluginPayload,
72
+ description: Optional[str],
73
+ group: Optional[str] = None,
74
+ parentTaskId: Optional[str] = None,
75
+ user:Optional[str] = None
76
+ ) -> TaskResponse:
77
+
78
+ path = 'task/create'
79
+
80
+ data = {
81
+ "user": user,
82
+ "context": "IS",
83
+ "type": "RUN_PLUGIN",
84
+ "description": f"Run plugin {pluginId}" if description is None else description,
85
+ "input":{
86
+ "slow": slow,
87
+ "pluginId": pluginId,
88
+ "payload": payload.to_dict()
89
+ },
90
+ "config": {
91
+ "repeatable":True
92
+ },
93
+ "parentTaskId": parentTaskId
94
+ }
95
+
96
+ if group:
97
+ data['group'] = group
98
+
99
+ response = self.sws_client.discoverable.post(self.endpoint, path, data=data)
100
+ if response:
101
+ return self.get_task_response(response)
102
+ else:
103
+ return None
104
+
105
+ def get_task_response(self, task: Dict) -> TaskResponse:
106
+ return TaskResponse(
107
+ task_id=task['taskId'],
108
+ info=TaskInfo(
109
+ detail_status=task['info'].get('detailStatus'),
110
+ ended_on=task['info'].get('endedOn'),
111
+ description=task['info'].get('description'),
112
+ updated_on=task['info'].get('updatedOn'),
113
+ created_on=task['info'].get('createdOn'),
114
+ service_user=task['info'].get('serviceUser'),
115
+ tags=task['info'].get('tags'),
116
+ output=task['info'].get('output'),
117
+ input=json.loads(task['info'].get('input', '{}')), # Default to an empty dictionary if 'input' is missing
118
+ task_type=task['info'].get('taskType'),
119
+ context=task['info'].get('context'),
120
+ progress=task['info'].get('progress'),
121
+ user=task['info'].get('user'),
122
+ outcome=task['info'].get('outcome'),
123
+ status=task['info'].get('status'),
124
+ group=task['info'].get('group')
125
+ )
126
+ )
127
+
128
+
129
+ def get_task(self, task_id: str) -> Optional[TaskResponse]:
130
+ path = f'/task/{task_id}'
131
+ response = self.sws_client.discoverable.get(self.endpoint, path)
132
+
133
+ if response:
134
+ return self.get_task_response(response)
135
+ else:
136
+ return None
137
+
138
+ def wait_completion(self, task_id: str, poll_interval: int = 10) -> TaskResponse:
139
+ """
140
+ Wait for a task to reach the 'ENDED' status and return the task outcome.
141
+
142
+ Args:
143
+ task_id (str): The ID of the task.
144
+ poll_interval (int, optional): The interval (in seconds) between status checks. Defaults to 10.
145
+
146
+ Returns:
147
+ str: The final outcome of the task.
148
+ """
149
+ while True:
150
+ task_response = self.get_task(task_id)
151
+
152
+ if not task_response:
153
+ raise ValueError(f"Task with ID {task_id} not found.")
154
+
155
+ task_status = task_response.info.detail_status
156
+ print(f"Task {task_id} status: {task_status}")
157
+ if task_status == 'ENDED':
158
+ return task_response
159
+
160
+ time.sleep(poll_interval)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: sws_api_client
3
- Version: 0.0.2
3
+ Version: 0.0.3.dev1
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,6 +16,7 @@ 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
20
 
20
21
 
21
22
  # SWS API Client
@@ -2,7 +2,11 @@ LICENSE
2
2
  README.md
3
3
  setup.py
4
4
  sws_api_client/__init__.py
5
+ sws_api_client/datasets.py
6
+ sws_api_client/discover.py
5
7
  sws_api_client/sws_api_client.py
8
+ sws_api_client/tags.py
9
+ sws_api_client/tasks.py
6
10
  sws_api_client.egg-info/PKG-INFO
7
11
  sws_api_client.egg-info/SOURCES.txt
8
12
  sws_api_client.egg-info/dependency_links.txt
@@ -1 +1,2 @@
1
1
  requests>=2.31.0
2
+ dataclasses>=0.6
@@ -1 +0,0 @@
1
- from .sws_api_client import SwsApiClient