atlasai-dstoolkit-client 0.0.1__py3-none-any.whl → 0.0.2__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.
@@ -0,0 +1,37 @@
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
+
15
+ # flake8: noqa
16
+ import os
17
+ import os.path as osp
18
+
19
+ from dotenv import load_dotenv
20
+
21
+ # do this first!
22
+ from .init import configure_logging
23
+ configure_logging()
24
+ del configure_logging
25
+
26
+ env_file = osp.join(osp.dirname(__file__), '.env')
27
+ if osp.exists(env_file):
28
+ load_dotenv(env_file)
29
+
30
+ del load_dotenv
31
+ del env_file
32
+ del osp
33
+ del os
34
+
35
+
36
+ from . import fabric, feature
37
+ from .login import login, logout
atlasai/toolkit/api.py ADDED
@@ -0,0 +1,170 @@
1
+ import os
2
+ from http import HTTPStatus
3
+ import json
4
+ import logging
5
+
6
+ from furl import furl
7
+
8
+ from .constants import DS_TOOLKIT_URL
9
+ from .requests import get_session
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+
14
+ def _get_headers(access_token):
15
+ return {
16
+ 'Authorization': f'Bearer {access_token}',
17
+ 'Content-Type': 'application/json'
18
+ }
19
+
20
+ def _get_access_token():
21
+ env_name = os.getenv('ATLASAI_TOKEN_NAME', 'ATLASAI_TOKEN')
22
+ access_token = os.getenv(env_name)
23
+ if not access_token:
24
+ raise Exception('No access token found. Call the `login()` method first!')
25
+ return access_token
26
+
27
+ def _add_params(f, params=None):
28
+ if params is None:
29
+ params = {}
30
+
31
+ for k, v in params.items():
32
+ if v:
33
+ f.args[k] = v
34
+ return f
35
+
36
+ def _process_data(data):
37
+ if not data:
38
+ return '{}'
39
+ return json.dumps({k: v for k, v in data.items() if v is not None})
40
+
41
+ def _paginate(method, url, access_token=None, params=None):
42
+ if access_token is None:
43
+ access_token = _get_access_token()
44
+ results = []
45
+ if params is None:
46
+ params = {}
47
+ if params.get('limit') is None:
48
+ params['limit'] = 100
49
+
50
+ if params.get('offset') is None:
51
+ params['offset'] = 0
52
+
53
+ def get_results(_url, _params):
54
+ f = furl(_url)
55
+ f = _add_params(f, _params)
56
+ _response = session.request(method, f.url, headers=_get_headers(access_token))
57
+ _response.raise_for_status()
58
+ return _response
59
+
60
+ session = get_session()
61
+ while True:
62
+ response = get_results(url, params)
63
+ data = response.json()
64
+ if not data:
65
+ break
66
+
67
+ results.extend(data)
68
+
69
+ if len(data) < params['limit']:
70
+ break
71
+ params['offset'] = params['offset'] + params['limit']
72
+ return results
73
+
74
+ def _get(access_token=None, resource=None, _id=None, method='get', params=None):
75
+ if access_token is None:
76
+ access_token = _get_access_token()
77
+ session = get_session()
78
+
79
+ f = furl(DS_TOOLKIT_URL)
80
+ f.path = f'api/{resource}/{_id}' if _id else f'api/{resource}'
81
+ f = _add_params(f, params)
82
+ url = f.url
83
+
84
+ response = session.request(method, url, headers=_get_headers(access_token))
85
+ response.raise_for_status()
86
+ return response.status_code, response.json()
87
+
88
+
89
+ def _list(access_token=None, resource=None, method='get', params=None, is_paginated=True):
90
+ if access_token is None:
91
+ access_token = _get_access_token()
92
+ session = get_session()
93
+
94
+ f = furl(DS_TOOLKIT_URL)
95
+ f.path = f'api/{resource}'
96
+ url = f.url
97
+
98
+ # return all the records if limit not specified
99
+ if is_paginated and not params.get('limit') and not params.get('offset'):
100
+ return 200, _paginate(method, url, access_token, params)
101
+
102
+ f = _add_params(f, params)
103
+ url = f.url
104
+ response = session.request(method, url, headers=_get_headers(access_token))
105
+ response.raise_for_status()
106
+ return response.status_code, response.json()
107
+
108
+ def _post(access_token=None, resource=None, method='post', data=None, params=None, url=None):
109
+ if access_token is None:
110
+ access_token = _get_access_token()
111
+ session = get_session()
112
+
113
+ f = furl(url or DS_TOOLKIT_URL)
114
+ f.path = f'api/{resource}'
115
+ f = _add_params(f, params)
116
+
117
+ url = f.url
118
+ data = _process_data(data)
119
+
120
+ response = session.request(method, url, headers=_get_headers(access_token), data=data)
121
+ response.raise_for_status()
122
+ if response.status_code == HTTPStatus.NO_CONTENT:
123
+ return response.status_code, None
124
+ elif response.status_code == HTTPStatus.ACCEPTED:
125
+ return response.status_code, response.headers['Location']
126
+ else:
127
+ return response.status_code, response.json()
128
+
129
+ def _patch(access_token=None, resource=None, method='patch', data=None, params=None):
130
+ if access_token is None:
131
+ access_token = _get_access_token()
132
+ session = get_session()
133
+
134
+ f = furl(DS_TOOLKIT_URL)
135
+ f.path = f'api/{resource}'
136
+ f = _add_params(f, params)
137
+
138
+ url = f.url
139
+ data = _process_data(data)
140
+
141
+ response = session.request(method, url, headers=_get_headers(access_token), data=data)
142
+ response.raise_for_status()
143
+ if response.status_code == HTTPStatus.NO_CONTENT:
144
+ return response.status_code, None
145
+ elif response.status_code == HTTPStatus.ACCEPTED:
146
+ return response.status_code, response.headers['Location']
147
+ else:
148
+ return response.status_code, response.json()
149
+
150
+ def _delete(access_token=None, resource=None, method='delete', data=None, params=None):
151
+ if access_token is None:
152
+ access_token = _get_access_token()
153
+ session = get_session()
154
+
155
+ f = furl(DS_TOOLKIT_URL)
156
+ f.path = f'api/{resource}'
157
+ f = _add_params(f, params)
158
+
159
+ url = f.url
160
+ if data:
161
+ response = session.request(method, url, headers=_get_headers(access_token), data=_process_data(data))
162
+ else:
163
+ response = session.request(method, url, headers=_get_headers(access_token))
164
+ response.raise_for_status()
165
+ if response.status_code == HTTPStatus.NO_CONTENT:
166
+ return response.status_code, None
167
+ elif response.status_code == HTTPStatus.ACCEPTED:
168
+ return response.status_code, response.headers['Location']
169
+ else:
170
+ return response.status_code, response.json()
@@ -0,0 +1,10 @@
1
+ import os
2
+
3
+ VINZ_URL = os.getenv('VINZ_URL') or 'https://vinz.atlasai.co'
4
+ DS_TOOLKIT_URL = os.getenv('DS_TOOLKIT_URL') or 'https://dstoolkit.atlasai.co'
5
+
6
+ DEFAULT_PAGE_SIZE = 20
7
+ DISABLE_SSL_VERIFICATION = 'DISABLE_SSL_VERIFICATION'
8
+
9
+ TOKEN_ENV_VAR = 'VINZ_ENCRYPTED_TOKEN'
10
+ TOKEN_TIMESTAMP_ENV_VAR = 'VINZ_ENCRYPTED_TOKEN_TIMESTAMP'
@@ -0,0 +1,67 @@
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
+
15
+ import uuid
16
+ import arrow
17
+
18
+ from . import api, constants, utils
19
+
20
+
21
+ def search(*args, **kwargs):
22
+ fs = FabricSearch(*args, **kwargs)
23
+ utils.show_page(fs.page)
24
+ return fs
25
+
26
+ class FabricSearch:
27
+
28
+ def __init__(self, id=None, *args, **kwargs):
29
+ self.id = id or uuid.uuid4()
30
+ self._search = None
31
+
32
+ def __repr__(self):
33
+ return f'FabricSearch({self.id})'
34
+
35
+ def __str__(self):
36
+ return f'FabricSearch({self.id})'
37
+
38
+ @property
39
+ def page(self):
40
+ return f'{constants.DS_TOOLKIT_URL}/fabric/search/{self.id}'
41
+
42
+ @property
43
+ def search(self):
44
+ return self._search
45
+
46
+ def refresh(self):
47
+ if self._search is None:
48
+ self._search = self._refresh()
49
+ else:
50
+ new_version = self._info()
51
+ # new updates ? pull them
52
+ if arrow.get(new_version['update_date']) > arrow.get(self._search['update_date']):
53
+ self._search = self._refresh()
54
+ return self._search
55
+
56
+ def info(self):
57
+ return self._info()
58
+
59
+ def _info(self):
60
+ resource = f'/fabric/search/{self.id}/info'
61
+ _, data = api._get(resource=resource)
62
+ return data['data']
63
+
64
+ def _refresh(self):
65
+ resource = f'/fabric/search/{self.id}/select'
66
+ _, data = api._get(resource=resource)
67
+ return data['data']
@@ -0,0 +1,75 @@
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
+
15
+ import uuid
16
+ import pandas as pd
17
+
18
+ from . import api, constants, utils
19
+
20
+
21
+ def export(*args, **kwargs):
22
+ fe = FeatureExport(*args, **kwargs)
23
+ utils.show_page(fe.page)
24
+ return fe
25
+
26
+ class FeatureExport:
27
+ def __init__(self, search, id=None, *args, **kwargs):
28
+ self.id = id or uuid.uuid4()
29
+ self._search = search
30
+ self._export = None
31
+
32
+ def __repr__(self):
33
+ return f'FeatureExport({self._search.id})'
34
+
35
+ def __str__(self):
36
+ return f'FeatureExport({self._search.id})'
37
+
38
+ @property
39
+ def page(self):
40
+ return f'{constants.DS_TOOLKIT_URL}/feature/export/{self.id}?feature_search_id={self._search.id}'
41
+
42
+ @property
43
+ def export(self):
44
+ return self._export
45
+
46
+ @property
47
+ def search(self):
48
+ return self._search.search
49
+
50
+ def details(self):
51
+ if self.export is None or self.export.status not in ['completed', 'failed']:
52
+ self._export = self._details()
53
+ return self.export
54
+
55
+ def refresh(self):
56
+ self._export = self._details()
57
+ return self.export
58
+
59
+ def results(self, limit=None) -> pd.DataFrame:
60
+ if self.export.status not in ['completed', 'failed']:
61
+ raise Exception(f'Export state is: {self.export.status}')
62
+
63
+ path = getattr(self.export, 'url_path', getattr(self.export, 'output_path'))
64
+ if not path:
65
+ raise Exception('Path not found')
66
+
67
+ df = pd.read_parquet(path, engine='pyarrow')
68
+ if limit:
69
+ df = df.head(limit)
70
+ return df
71
+
72
+ def _details(self):
73
+ resource = f'/feature/export/{self._search.id}/details'
74
+ _, data = api._get(resource=resource)
75
+ return data['data']
@@ -0,0 +1,30 @@
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
+
15
+ import logging
16
+ import os
17
+
18
+ def configure_logging():
19
+ handler = logging.StreamHandler()
20
+ formatter = logging.Formatter(
21
+ '%(asctime)s %(levelname)-5.5s [%(name)s][pid:%(process)s tid:%(thread)s] %(message)s'
22
+ )
23
+ handler.setFormatter(formatter)
24
+
25
+ log_level = (os.getenv('DSTOOLKIT_LOG_LEVEL') or 'INFO').upper()
26
+ log_level = getattr(logging, log_level, logging.INFO)
27
+
28
+ logger = logging.getLogger('atlasai.toolkit')
29
+ logger.setLevel(log_level)
30
+ logger.addHandler(handler)
@@ -0,0 +1,107 @@
1
+ import hmac
2
+ import logging
3
+ import os
4
+ from urllib.parse import urlparse
5
+ import warnings
6
+
7
+ import arrow
8
+ from furl import furl
9
+ import requests
10
+
11
+ from . import constants
12
+ from .utils import clean_token_env_vars
13
+
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+
18
+ def login(env_name='ATLASAI_TOKEN', return_access_token=False):
19
+ """
20
+ Authenticate with Vinz
21
+
22
+ Returns an OAuth2 Access Token
23
+
24
+ If `env_name` provided, the Access Token will be saved
25
+ to the named environment variable
26
+
27
+ #### Usage
28
+
29
+ ```python
30
+ from atlasai.vinz import client
31
+
32
+ token = client.authenticate(<OPTIONAL_ENV_VARIABLE_NAME>)
33
+ ```
34
+ """
35
+ os.environ['ATLASAI_TOKEN_NAME'] = env_name
36
+ f = furl(constants.VINZ_URL)
37
+ f.path = 'api/token'
38
+ url = f.url
39
+ headers = {}
40
+ include_authorization(url, headers)
41
+
42
+ response = requests.get(url, headers=headers)
43
+ response.raise_for_status()
44
+ data = response.json()
45
+ token = data['access_token']
46
+
47
+ os.environ[env_name] = token
48
+
49
+ user_id = data.get('email') or data.get('sub') or 'AtlasAI Employee'
50
+ os.environ['LOGNAME'] = user_id
51
+
52
+ if return_access_token:
53
+ return token
54
+
55
+ def logout():
56
+ os.environ.pop(os.getenv('ATLASAI_TOKEN_NAME', ''), None)
57
+ clean_token_env_vars()
58
+
59
+ def load_credentials(access_key=None, secret_key=None):
60
+ access_key = access_key or os.getenv('ATLASAI_ACCESS_KEY')
61
+ secret_key = secret_key or os.getenv('ATLASAI_SECRET_KEY')
62
+
63
+ return access_key, secret_key
64
+
65
+
66
+ def include_authorization(url, headers, bearer_token=None, access_key=None, secret_key=None):
67
+ bearer_token = bearer_token or os.getenv('ATLASAI_BEARER_TOKEN')
68
+ access_key, secret_key = load_credentials(
69
+ access_key=access_key,
70
+ secret_key=secret_key,
71
+ )
72
+
73
+ if bearer_token:
74
+ headers['Authorization'] = f'Bearer {bearer_token}'
75
+ return
76
+
77
+ if not access_key or not secret_key:
78
+ warnings.warn('No API Keys provided to access Vinz API. Provide the following pair ATLASAI_ACCESS_KEY and ATLASAI_SECRET_KEY')
79
+ raise ValueError('ATLASAI_ACCESS_KEY and ATLASAI_SECRET_KEY must be provided together')
80
+
81
+ product, version = 'atlasai', '1'
82
+ headers.update({
83
+ 'Host': urlparse(url).netloc,
84
+ 'X-AtlasAI-Date': arrow.utcnow().isoformat(),
85
+ 'X-AtlasAI-Credential': '/'.join([product, version, access_key]),
86
+ 'X-AtlasAI-SignedHeaders': 'x-atlasai-date;x-atlasai-credential;host',
87
+ })
88
+
89
+ sign_request(headers, secret_key)
90
+
91
+
92
+ def sign_request(headers, secret_key):
93
+ product, version, access_key = headers['X-AtlasAI-Credential'].split('/')
94
+ key = f'{product}{version}{secret_key}'.encode('utf-8')
95
+ for msg in (
96
+ headers['X-AtlasAI-Date'],
97
+ f'{product}_{version}_request',
98
+ ):
99
+ obj = hmac.new(key, msg.encode('utf-8'), 'sha256')
100
+ key = obj.digest()
101
+
102
+ msg = '\n'.join([
103
+ headers['X-AtlasAI-Date'],
104
+ headers['X-AtlasAI-Credential'],
105
+ headers['Host']
106
+ ])
107
+ headers['X-AtlasAI-Signature'] = hmac.new(key, msg.encode('utf-8'), 'sha256').hexdigest()
@@ -0,0 +1,52 @@
1
+ import os
2
+
3
+ import requests
4
+ from requests.adapters import HTTPAdapter, Retry
5
+ from .constants import DISABLE_SSL_VERIFICATION
6
+
7
+
8
+ STATUS_FORCELIST = tuple([429, 500, 502, 503, 504])
9
+
10
+ def mount_retry(
11
+ session,
12
+ total=10,
13
+ backoff_factor=0.2,
14
+ allowed_methods=None,
15
+ status_forcelist=STATUS_FORCELIST,
16
+ ):
17
+ """
18
+ Attach retry handlers to HTTP and HTTPS endpoints of a Requests Session
19
+ """
20
+
21
+ retries = Retry(
22
+ total=total,
23
+ backoff_factor=backoff_factor,
24
+ allowed_methods=allowed_methods,
25
+ status_forcelist=status_forcelist,
26
+ )
27
+
28
+ session.mount('http://', HTTPAdapter(max_retries=retries))
29
+ session.mount('https://', HTTPAdapter(max_retries=retries))
30
+
31
+ def get_session(
32
+ total=3,
33
+ backoff_factor=0.2,
34
+ allowed_methods=None,
35
+ status_forcelist=STATUS_FORCELIST,
36
+ ):
37
+ """
38
+ Get a Requests Session with retry handlers for HTTP and HTTPS endpoints
39
+ """
40
+
41
+ sess = requests.Session()
42
+ if os.getenv(DISABLE_SSL_VERIFICATION):
43
+ sess.verify = False
44
+ mount_retry(
45
+ sess,
46
+ total=total,
47
+ backoff_factor=backoff_factor,
48
+ allowed_methods=allowed_methods,
49
+ status_forcelist=status_forcelist,
50
+ )
51
+
52
+ return sess
@@ -0,0 +1,58 @@
1
+ import arrow
2
+ import os
3
+
4
+ from furl import furl
5
+ import webbrowser
6
+
7
+ from . import api, constants
8
+
9
+ def is_notebook() -> bool:
10
+ try:
11
+ from IPython import get_ipython
12
+ shell = get_ipython().__class__.__name__
13
+ if shell == 'ZMQInteractiveShell':
14
+ return True
15
+ elif shell == 'TerminalInteractiveShell':
16
+ return False
17
+ else:
18
+ return False
19
+ except Exception:
20
+ return False
21
+
22
+
23
+ def show_page(page):
24
+ f = furl(page)
25
+
26
+ if is_first_page():
27
+ token = retrieve_one_time_token()
28
+ set_token_env_vars(token)
29
+ f.args['token'] = token
30
+
31
+ if is_notebook():
32
+ from IPython.core.display import display
33
+ from IPython.display import IFrame
34
+ display(IFrame(f.url, width=800, height=600))
35
+ else:
36
+ webbrowser.open(f.url)
37
+
38
+ def is_first_page():
39
+ if os.getenv(constants.TOKEN_ENV_VAR) is None:
40
+ return True
41
+ last_set = os.getenv(constants.TOKEN_TIMESTAMP_ENV_VAR)
42
+ if last_set:
43
+ last_set = arrow.get(last_set)
44
+ # if had passed more than 24h since last iframe. pass the token again in case it needs it
45
+ if arrow.utcnow() >= last_set.shift(hours=+24):
46
+ return True
47
+
48
+ def set_token_env_vars(token):
49
+ os.environ[constants.TOKEN_ENV_VAR] = token
50
+ os.environ[constants.TOKEN_TIMESTAMP_ENV_VAR] = arrow.now().isoformat()
51
+
52
+ def clean_token_env_vars():
53
+ os.environ.pop(constants.TOKEN_ENV_VAR, None)
54
+ os.environ.pop(constants.TOKEN_TIMESTAMP_ENV_VAR, None)
55
+
56
+ def retrieve_one_time_token():
57
+ _, data = api._post(resource='token/wrap', url=constants.VINZ_URL)
58
+ return data['token']
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: atlasai-dstoolkit-client
3
- Version: 0.0.1
3
+ Version: 0.0.2
4
4
  Summary: UNKNOWN
5
5
  Home-page: UNKNOWN
6
6
  Author: AtlasAI SWE
@@ -0,0 +1,14 @@
1
+ atlasai/toolkit/__init__.py,sha256=szu5LHZ41ccWztCI2LvQ3QnZRybosgTbU3yd6tS7cUw,986
2
+ atlasai/toolkit/api.py,sha256=BvO-gLRmbmkKduwbbADjcLlIkS9blzfM_cbMR4DhQmU,5269
3
+ atlasai/toolkit/constants.py,sha256=sE0PeFa9_htCPVFADHbkyPIz3QOai0tAIA5IiiZI8wA,329
4
+ atlasai/toolkit/fabric.py,sha256=vjc8PJ30bUZ9z-ZqovpfqDkOo_OkMH80GMX8YoOPdfI,1923
5
+ atlasai/toolkit/feature.py,sha256=ijoljwdtlI2djeALY14ctL97K5wbzpJkRMtwq-y-PIg,2276
6
+ atlasai/toolkit/init.py,sha256=JkdJ6QGdYWrq65jgz2pn5RYXUeUe2Ez88_-eMf5CNi0,1100
7
+ atlasai/toolkit/login.py,sha256=n4ydfo9qCsmbZq6er1xeljBD76vdTJGjbhYHMmOyDbQ,3061
8
+ atlasai/toolkit/requests.py,sha256=X86nIo07hAjUlilZcZ1lV8RB7KOsTKbTGtcY_SpFEXY,1223
9
+ atlasai/toolkit/utils.py,sha256=kEoNz3BbSgKbq6X2dDlM4Odcbw2BfoNyls2RKRAL5wQ,1639
10
+ atlasai_dstoolkit_client-0.0.2.dist-info/LICENSE.txt,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
11
+ atlasai_dstoolkit_client-0.0.2.dist-info/METADATA,sha256=2c-eLkb3z0P2uuQ1E-3J3B4Bi-rQIEYHslmGZ1H3LSU,1301
12
+ atlasai_dstoolkit_client-0.0.2.dist-info/WHEEL,sha256=G16H4A3IeoQmnOrYV4ueZGKSjhipXx8zc8nu9FGlvMA,92
13
+ atlasai_dstoolkit_client-0.0.2.dist-info/top_level.txt,sha256=HRTbErU8nmHFDaJJ5R_XYbwpt21dqdjDpSva8xyy_0k,8
14
+ atlasai_dstoolkit_client-0.0.2.dist-info/RECORD,,
@@ -0,0 +1 @@
1
+ atlasai
@@ -1,5 +0,0 @@
1
- atlasai_dstoolkit_client-0.0.1.dist-info/LICENSE.txt,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
2
- atlasai_dstoolkit_client-0.0.1.dist-info/METADATA,sha256=Kqg9p64yglqrHsSkqwxYOcZGUDC-2D9QG1RCSS4oa0k,1301
3
- atlasai_dstoolkit_client-0.0.1.dist-info/WHEEL,sha256=G16H4A3IeoQmnOrYV4ueZGKSjhipXx8zc8nu9FGlvMA,92
4
- atlasai_dstoolkit_client-0.0.1.dist-info/top_level.txt,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
5
- atlasai_dstoolkit_client-0.0.1.dist-info/RECORD,,