atlasai-dstoolkit-client 0.0.12__py3-none-any.whl → 0.0.14__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.
@@ -33,5 +33,5 @@ del osp
33
33
  del os
34
34
 
35
35
 
36
- from . import fabric, feature, dataset, model
36
+ from . import fabric, feature, dataset, model, workflows
37
37
  from .login import login, logout
@@ -15,6 +15,7 @@
15
15
  import logging
16
16
  import uuid
17
17
 
18
+ import geopandas as gpd
18
19
  import pandas as pd
19
20
  from shapely import wkb
20
21
 
@@ -60,7 +61,7 @@ class FeatureExport:
60
61
  self._export = self._details()
61
62
  return self.export
62
63
 
63
- def results(self, limit=None) -> pd.DataFrame:
64
+ def results(self, limit=None, as_gdf=False) -> pd.DataFrame:
64
65
  if not self.export:
65
66
  return pd.DataFrame([])
66
67
 
@@ -84,6 +85,8 @@ class FeatureExport:
84
85
 
85
86
  try:
86
87
  df['shape'] = wkb.loads(df['shape'])
88
+ if as_gdf:
89
+ df = gpd.GeoDataFrame(df, geometry="shape")
87
90
  except Exception:
88
91
  logger.warning('Shape field is not in WKB format. Skipping conversion.')
89
92
 
@@ -0,0 +1,18 @@
1
+ from rich.console import Console
2
+ from rich.text import Text
3
+
4
+ console = Console()
5
+
6
+ def print_title(text):
7
+ text = Text(text, style="bold underline", justify="center")
8
+ console.print(text)
9
+
10
+ def print_subtitle(text):
11
+ text = Text(text, style="italic cyan", justify="center")
12
+ console.print(text)
13
+
14
+ def print_body(text):
15
+ if isinstance(text, bytes):
16
+ text = text.decode("utf-8")
17
+ text = Text(text, style="white", justify="left")
18
+ console.print(text)
@@ -0,0 +1,229 @@
1
+ # Copyright 2025 AtlasAI PBC. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ import logging
15
+ import os
16
+ import time
17
+
18
+ import pandas as pd
19
+ from tqdm import tqdm
20
+
21
+ from concurrent.futures import ThreadPoolExecutor, as_completed
22
+ from dataclasses import dataclass
23
+
24
+ import requests
25
+
26
+ from . import api, output as o
27
+
28
+ logger = logging.getLogger('atlasai.toolkit')
29
+
30
+ @dataclass()
31
+ class WorkflowResponse:
32
+ id: str
33
+ uid: str
34
+ name: str
35
+ create_date: str
36
+ update_date: str
37
+ status: str
38
+ message: str
39
+
40
+
41
+ class Workflow:
42
+ type = None
43
+ id = None
44
+
45
+ def __init__(self, id=None):
46
+ # Allow the selection of an existing workflow and check the logs.
47
+ self.id = id
48
+
49
+ @property
50
+ def status(self):
51
+ _result = self.get()
52
+ return _result['data']['status']
53
+
54
+ def results(self, *args, **kwargs):
55
+ return self._results(*args, **kwargs)
56
+
57
+ def _set(self, result):
58
+ self.id = result['data']['id']
59
+
60
+ def _config(self):
61
+ raise NotImplementedError()
62
+
63
+ def _validate(self):
64
+ raise NotImplementedError()
65
+
66
+ def _results(self, *args, **kwargs):
67
+ raise NotImplementedError
68
+
69
+ def get(self):
70
+ if not self.id:
71
+ raise Exception('No valid execution of the workflow. Please use the `run` method first.')
72
+
73
+ _, result = api._get(resource=f'workflow/{self.id}')
74
+ return result
75
+
76
+ def logs(self, only_errors=True):
77
+ if not self.id:
78
+ raise Exception('No valid execution of the workflow. Please use the `run` method first.')
79
+
80
+ workflow = self.get()
81
+
82
+ _, result = api._get(resource=f'workflow/{self.id}/logs', params={'only_errors': only_errors})
83
+
84
+ o.print_title(f'Workflow: {workflow["data"]["name"]}')
85
+ o.print_subtitle(f'Status: {workflow["data"]["status"]}')
86
+ o.print_subtitle(f'Message: {workflow["data"]["message"] or "-"}')
87
+
88
+ o.print_body('---------------------------------------------------')
89
+
90
+ if not result['data']:
91
+ if only_errors:
92
+ o.print_title('No errors detected.')
93
+ else:
94
+ o.print_title('No pod found to pull logs from.')
95
+
96
+ for pod, data in result['data'].items():
97
+ o.print_title(f'Pod: {pod}')
98
+ o.print_subtitle(f'Message: {data["message"] or "-"}')
99
+ o.print_subtitle(f'Logs: {data["logs"] or "-"}')
100
+ o.print_body('---------------------------------------------------')
101
+
102
+ def run(self, wait_until_complete=False):
103
+ self._validate()
104
+ status = 'Running'
105
+ message = ''
106
+ data = dict(
107
+ type=self.type,
108
+ config=self._config()
109
+ )
110
+ _, result = api._post(resource='workflow', data=data)
111
+ self._set(result)
112
+ if wait_until_complete:
113
+ while True:
114
+ _result = self.get()
115
+ if _result['data']['status'].lower() != 'running':
116
+ status = _result['data']['status']
117
+ message = _result['data']['message']
118
+ break
119
+
120
+ time.sleep(5)
121
+ return WorkflowResponse(**result['data'], status=status, message=message)
122
+
123
+ class Test(Workflow):
124
+ type = 'test'
125
+
126
+ def __repr__(self):
127
+ return 'Test'
128
+
129
+ def __str__(self):
130
+ return 'Test'
131
+
132
+ def configure(self):
133
+ pass
134
+
135
+ def _config(self):
136
+ return dict()
137
+
138
+ def _validate(self):
139
+ pass
140
+
141
+
142
+ class Electrification(Workflow):
143
+ type = 'electrification'
144
+
145
+ def __init__(self, id=None):
146
+ self.aoi_name = None
147
+ self.aoi_path = None
148
+ self.aoi = None
149
+ self.output_path = None
150
+
151
+ super().__init__(id)
152
+
153
+ def __repr__(self):
154
+ return f'Electrification({self.aoi_name})'
155
+
156
+ def __str__(self):
157
+ return f'Electrification({self.aoi_name})'
158
+
159
+ def configure(self, aoi_name=None, aoi_path=None, aoi=None, output_path=None):
160
+ self.aoi_name = aoi_name or self.aoi_name
161
+ self.aoi_path = aoi_path or self.aoi_path
162
+ self.aoi = aoi or self.aoi
163
+ self.output_path = output_path or self.output_path
164
+
165
+ def _config(self):
166
+ cfg = {}
167
+ for name, value in (
168
+ ('aoi_name', self.aoi_name),
169
+ ('aoi_geojson', self.aoi),
170
+ ('aoi_geojson_uri', self.aoi_path),
171
+ ('output_bucket', self.output_path)
172
+ ):
173
+ if value:
174
+ cfg[name] = value
175
+ return cfg
176
+
177
+ def _validate(self):
178
+ if not self.aoi_name:
179
+ raise Exception('`aoi_name` must be specified.')
180
+
181
+ if not self.aoi_path and not self.aoi:
182
+ raise Exception('`aoi_path` or `aoi` must be specified.')
183
+
184
+ def _results(self, save_to=None):
185
+ results = []
186
+ if not self.id:
187
+ raise Exception('No valid execution of the workflow. Please use the `run` method first.')
188
+
189
+ workflow = self.get()
190
+ if not workflow['data']['status'] == 'Succeeded':
191
+ raise Exception(f'Workflow not in `Succeeded` state. Current state: {workflow["data"]["status"]}')
192
+
193
+ _, result = api._get(resource=f'workflow/{self.id}/results')
194
+
195
+ with ThreadPoolExecutor(max_workers=8) as ex, tqdm(total=len(result['data']), desc="Downloading", unit="file") as pbar:
196
+ fut_map = {ex.submit(self._download_file, res["name"], res["url"], save_to): i
197
+ for i, res in enumerate(result['data'])}
198
+ for fut in as_completed(fut_map):
199
+ results.append(fut.result())
200
+ pbar.update(1)
201
+
202
+ return results
203
+
204
+ def _download_file(self, name, url, to=None):
205
+ local_path = os.path.join(to or os.getcwd(), name.split('/')[-1])
206
+
207
+ logger.debug(f'Downloading :{name}')
208
+
209
+ with requests.get(url, stream=True, timeout=120) as r:
210
+
211
+ logger.debug(f'Finished downloading: {name}')
212
+ r.raise_for_status()
213
+ with open(local_path, "wb") as f:
214
+ for chunk in r.iter_content(chunk_size=1024 * 1024):
215
+ if chunk:
216
+ f.write(chunk)
217
+
218
+ logger.debug(f'Finished saving: {name}')
219
+
220
+ return local_path
221
+
222
+
223
+ def List(search=None, offset=0, limit=100):
224
+ pd.set_option("display.max_colwidth", None)
225
+ resource = 'workflows'
226
+ _, data = api._list(resource=resource, params={'search': search, 'offset': offset, 'limit': limit})
227
+ df = pd.DataFrame(data['data'])
228
+ df = df.drop(columns=['update_date', 'uid'])
229
+ return df
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: atlasai-dstoolkit-client
3
- Version: 0.0.12
3
+ Version: 0.0.14
4
4
  Summary: UNKNOWN
5
5
  Home-page: UNKNOWN
6
6
  Author: AtlasAI SWE
@@ -12,12 +12,14 @@ License-File: LICENSE.txt
12
12
  Requires-Dist: arrow (<2.0.0,>=1.3.0)
13
13
  Requires-Dist: atlasai-mlhub-client
14
14
  Requires-Dist: furl (<3.0.0,>=2.1.2)
15
+ Requires-Dist: geopandas (~=1.1.1)
15
16
  Requires-Dist: pandas (<3.0.0,>=2.2.3)
16
17
  Requires-Dist: pyarrow (>=19.0.1)
17
18
  Requires-Dist: python-dotenv (<2.0.0,>=1.0.1)
18
19
  Requires-Dist: requests (<=3.0.0,>=2.32.3)
19
20
  Requires-Dist: semver (<4.0.0,>=3.0.2)
20
21
  Requires-Dist: shapely (~=2.1.0)
22
+ Requires-Dist: tqdm (~=4.66.5)
21
23
  Provides-Extra: dev
22
24
  Requires-Dist: coverage ; extra == 'dev'
23
25
  Requires-Dist: flake8 ; extra == 'dev'
@@ -1,16 +1,18 @@
1
- atlasai/toolkit/__init__.py,sha256=rjt3GfV7vA3RFMIzqPwBebJUXdQbeTLmEYmm7SOoOGw,1002
1
+ atlasai/toolkit/__init__.py,sha256=0d2EYTgFf-RH2z1M83gGfZdrt_PBXbJk6o5ZVAKCPtA,1013
2
2
  atlasai/toolkit/api.py,sha256=BvO-gLRmbmkKduwbbADjcLlIkS9blzfM_cbMR4DhQmU,5269
3
3
  atlasai/toolkit/constants.py,sha256=Jxozn9tKOvAxyOYOZ6bzFtI9m1YETJF1GURzTl9NNC8,422
4
4
  atlasai/toolkit/dataset.py,sha256=fhzhxF9YMzIwEpaJZEPOK2SuLMJhVGI75eahxnH_T2c,3254
5
5
  atlasai/toolkit/fabric.py,sha256=6aFR2PGQc9P3Qa07WdBg9eKoUzU8n2y_-gGjYcyMrWY,1921
6
- atlasai/toolkit/feature.py,sha256=VKwX_yAwwMo1WuLZ_RbKVJEH4L1PAoYEPzWa0lH9ats,2828
6
+ atlasai/toolkit/feature.py,sha256=Q5M9zOGafynYuKaELL1kZemYPfKAZh84TgH7jw9J3ZU,2949
7
7
  atlasai/toolkit/init.py,sha256=JkdJ6QGdYWrq65jgz2pn5RYXUeUe2Ez88_-eMf5CNi0,1100
8
8
  atlasai/toolkit/login.py,sha256=n4ydfo9qCsmbZq6er1xeljBD76vdTJGjbhYHMmOyDbQ,3061
9
9
  atlasai/toolkit/model.py,sha256=RUe0HbDpzvHOV9A4rzG3PgN9boMWDHQ2tR7IKHXzbx8,4126
10
+ atlasai/toolkit/output.py,sha256=FyDjrpVlbrEyfHfwOpxp8H57jx_qXahDjO1qpHIeuYM,473
10
11
  atlasai/toolkit/requests.py,sha256=X86nIo07hAjUlilZcZ1lV8RB7KOsTKbTGtcY_SpFEXY,1223
11
12
  atlasai/toolkit/utils.py,sha256=lYh3P2XOshRgHCjFeXJ0FOJWQW64sddgx8c2kL6Wqwc,1566
12
- atlasai_dstoolkit_client-0.0.12.dist-info/LICENSE.txt,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
13
- atlasai_dstoolkit_client-0.0.12.dist-info/METADATA,sha256=2vdA8hWTJEIvSm1OgPRt9wmsTtZO_5oR_znxLDNuDbI,1405
14
- atlasai_dstoolkit_client-0.0.12.dist-info/WHEEL,sha256=G16H4A3IeoQmnOrYV4ueZGKSjhipXx8zc8nu9FGlvMA,92
15
- atlasai_dstoolkit_client-0.0.12.dist-info/top_level.txt,sha256=HRTbErU8nmHFDaJJ5R_XYbwpt21dqdjDpSva8xyy_0k,8
16
- atlasai_dstoolkit_client-0.0.12.dist-info/RECORD,,
13
+ atlasai/toolkit/workflows.py,sha256=HlmLNma1P4xyd74gzM5moBXPGjVQqziGOtyRTXBXUhw,6994
14
+ atlasai_dstoolkit_client-0.0.14.dist-info/LICENSE.txt,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
15
+ atlasai_dstoolkit_client-0.0.14.dist-info/METADATA,sha256=vWbApYiDX_XA2mFvwXKRWDCV6KW5OwOYL-jqJN4S56A,1471
16
+ atlasai_dstoolkit_client-0.0.14.dist-info/WHEEL,sha256=G16H4A3IeoQmnOrYV4ueZGKSjhipXx8zc8nu9FGlvMA,92
17
+ atlasai_dstoolkit_client-0.0.14.dist-info/top_level.txt,sha256=HRTbErU8nmHFDaJJ5R_XYbwpt21dqdjDpSva8xyy_0k,8
18
+ atlasai_dstoolkit_client-0.0.14.dist-info/RECORD,,