terrakio-core 0.3.1__py3-none-any.whl → 0.3.3__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.

Potentially problematic release.


This version of terrakio-core might be problematic. Click here for more details.

terrakio_core/config.py CHANGED
@@ -9,51 +9,97 @@ from .exceptions import ConfigurationError
9
9
  DEFAULT_CONFIG_FILE = os.path.join(os.environ.get("HOME", ""), ".tkio_config.json")
10
10
  DEFAULT_API_URL = "https://api.terrak.io"
11
11
 
12
- def read_config_file(config_file: str = DEFAULT_CONFIG_FILE) -> Dict[str, Any]:
12
+ def read_config_file(config_file: str = DEFAULT_CONFIG_FILE, quiet: bool = False) -> Dict[str, Any]:
13
13
  """
14
14
  Read and parse the configuration file.
15
15
 
16
16
  Args:
17
17
  config_file: Path to the configuration file
18
+ quiet: If True, suppress informational messages
18
19
 
19
20
  Returns:
20
- Dict[str, Any]: Configuration parameters
21
+ Dict[str, Any]: Configuration parameters with additional flags:
22
+ 'is_logged_in': True if user is logged in
23
+ 'user_email': The email of the logged in user
24
+ 'token': Personal token if available
21
25
 
22
- Raises:
23
- ConfigurationError: If the configuration file can't be read or parsed
26
+ Note:
27
+ This function no longer raises ConfigurationError. Instead, it creates an empty config
28
+ file if one doesn't exist and returns appropriate status flags.
24
29
  """
25
30
  config_path = Path(os.path.expanduser(config_file))
26
-
31
+ # the first circumstance is that the config file does not exist
32
+ # that we need to login before using any of the functions
33
+ # Check if config file exists
27
34
  if not config_path.exists():
28
- raise ConfigurationError(
29
- f"Configuration file not found: {config_file}\n"
30
- f"Please create a file at {config_file} with the following format:\n"
31
- '{\n "EMAIL": "your-email@example.com",\n "TERRAKIO_API_KEY": "your-api-key-here"\n}'
32
- )
35
+ # Create an empty config file
36
+ config_path.parent.mkdir(parents=True, exist_ok=True)
37
+ with open(config_path, 'w') as f:
38
+ json.dump({}, f)
39
+ if not quiet:
40
+ print("No API key found. Please provide an API key to use this client.")
41
+ return {
42
+ 'url': DEFAULT_API_URL,
43
+ 'key': None,
44
+ 'is_logged_in': False,
45
+ 'user_email': None,
46
+ 'token': None
47
+ }
33
48
 
34
49
  try:
50
+ # Read the config file
35
51
  with open(config_path, 'r') as f:
36
52
  config_data = json.load(f)
37
-
53
+
54
+ # Read the config file data
55
+ # Check if config has an API key
56
+ if not config_data or 'TERRAKIO_API_KEY' not in config_data or not config_data.get('TERRAKIO_API_KEY'):
57
+ if not quiet:
58
+ print("No API key found. Please provide an API key to use this client.")
59
+ return {
60
+ 'url': DEFAULT_API_URL,
61
+ 'key': None,
62
+ 'is_logged_in': False,
63
+ 'user_email': None,
64
+ 'token': config_data.get('PERSONAL_TOKEN')
65
+ }
66
+
67
+ # If we have config values, use them
68
+ if not quiet:
69
+ print(f"Currently logged in as: {config_data.get('EMAIL')}")
70
+ # this meanb that we have already logged in to the tkio account
71
+
38
72
  # Convert the JSON config to our expected format
39
73
  config = {
40
- # Allow config to override default URL if provided
41
- 'url': config_data.get('TERRAKIO_API_URL', DEFAULT_API_URL),
42
- 'key': config_data.get('TERRAKIO_API_KEY')
74
+ # Always use the default URL, not from config file
75
+ 'url': DEFAULT_API_URL,
76
+ 'key': config_data.get('TERRAKIO_API_KEY'),
77
+ 'is_logged_in': True,
78
+ 'user_email': config_data.get('EMAIL'),
79
+ 'token': config_data.get('PERSONAL_TOKEN')
43
80
  }
44
81
  return config
45
82
 
83
+
46
84
  except Exception as e:
47
- raise ConfigurationError(f"Failed to parse configuration file: {e}")
85
+ if not quiet:
86
+ print(f"Error reading config: {e}")
87
+ print("No API key found. Please provide an API key to use this client.")
88
+ return {
89
+ 'url': DEFAULT_API_URL,
90
+ 'key': None,
91
+ 'is_logged_in': False,
92
+ 'user_email': None,
93
+ 'token': None
94
+ }
48
95
 
49
- def create_default_config(email: str, api_key: str, api_url: Optional[str] = None, config_file: str = DEFAULT_CONFIG_FILE) -> None:
96
+ def create_default_config(email: str, api_key: str, config_file: str = DEFAULT_CONFIG_FILE) -> None:
50
97
  """
51
98
  Create a default configuration file in JSON format.
52
99
 
53
100
  Args:
54
101
  email: User email
55
102
  api_key: Terrakio API key
56
- api_url: Optional API URL (if different from default)
57
103
  config_file: Path to configuration file
58
104
 
59
105
  Raises:
@@ -70,10 +116,6 @@ def create_default_config(email: str, api_key: str, api_url: Optional[str] = Non
70
116
  "TERRAKIO_API_KEY": api_key
71
117
  }
72
118
 
73
- # Add API URL if provided
74
- if api_url:
75
- config_data["TERRAKIO_API_URL"] = api_url
76
-
77
119
  with open(config_path, 'w') as f:
78
120
  json.dump(config_data, f, indent=2)
79
121
 
@@ -83,10 +83,63 @@ class DatasetManagement:
83
83
  except requests.RequestException as e:
84
84
  raise APIError(f"Request failed: {str(e)}")
85
85
 
86
+ # def create_dataset(self, name: str, collection: str = "terrakio-datasets", **kwargs) -> Dict[str, Any]:
87
+ # """
88
+ # Create a new dataset.
89
+
90
+ # Args:
91
+ # name: Name of the dataset (required)
92
+ # collection: Dataset collection (default: 'terrakio-datasets')
93
+ # **kwargs: Additional dataset parameters including:
94
+ # - products: List of products
95
+ # - dates_iso8601: List of dates
96
+ # - bucket: Storage bucket
97
+ # - path: Storage path
98
+ # - data_type: Data type
99
+ # - no_data: No data value
100
+ # - l_max: Maximum level
101
+ # - y_size: Y size
102
+ # - x_size: X size
103
+ # - proj4: Projection string
104
+ # - abstract: Dataset abstract
105
+ # - geotransform: Geotransform parameters
106
+
107
+ # Returns:
108
+ # Created dataset information
109
+
110
+ # Raises:
111
+ # APIError: If the API request fails
112
+ # """
113
+ # endpoint = f"{self.api_url}/datasets"
114
+ # params = {"collection": collection}
115
+ # # Create payload with required name parameter
116
+ # payload = {"name": name}
117
+
118
+ # # Add optional parameters if provided
119
+ # for param in ["products", "dates_iso8601", "bucket", "path", "data_type",
120
+ # "no_data", "l_max", "y_size", "x_size", "proj4", "abstract", "geotransform", "input"]:
121
+ # if param in kwargs:
122
+ # payload[param] = kwargs[param]
123
+
124
+ # try:
125
+ # response = self.session.post(
126
+ # endpoint,
127
+ # params=params,
128
+ # json=payload,
129
+ # timeout=self.timeout,
130
+ # verify=self.verify
131
+ # )
132
+
133
+ # if not response.ok:
134
+ # raise APIError(f"API request failed: {response.status_code} {response.reason}")
135
+ # return response.json()
136
+ # except requests.RequestException as e:
137
+ # raise APIError(f"Request failed: {str(e)}")
138
+
86
139
  def create_dataset(self, name: str, collection: str = "terrakio-datasets", **kwargs) -> Dict[str, Any]:
87
140
  """
88
141
  Create a new dataset.
89
-
142
+
90
143
  Args:
91
144
  name: Name of the dataset (required)
92
145
  collection: Dataset collection (default: 'terrakio-datasets')
@@ -103,24 +156,23 @@ class DatasetManagement:
103
156
  - proj4: Projection string
104
157
  - abstract: Dataset abstract
105
158
  - geotransform: Geotransform parameters
106
-
159
+ - padding: Padding value
160
+
107
161
  Returns:
108
162
  Created dataset information
109
-
163
+
110
164
  Raises:
111
165
  APIError: If the API request fails
112
166
  """
113
167
  endpoint = f"{self.api_url}/datasets"
114
168
  params = {"collection": collection}
115
- # Create payload with required name parameter
116
169
  payload = {"name": name}
117
-
118
- # Add optional parameters if provided
119
- for param in ["products", "dates_iso8601", "bucket", "path", "data_type",
120
- "no_data", "l_max", "y_size", "x_size", "proj4", "abstract", "geotransform", "input"]:
170
+
171
+ for param in ["products", "dates_iso8601", "bucket", "path", "data_type",
172
+ "no_data", "l_max", "y_size", "x_size", "proj4", "abstract", "geotransform", "input", "padding"]:
121
173
  if param in kwargs:
122
174
  payload[param] = kwargs[param]
123
-
175
+
124
176
  try:
125
177
  response = self.session.post(
126
178
  endpoint,
@@ -129,7 +181,7 @@ class DatasetManagement:
129
181
  timeout=self.timeout,
130
182
  verify=self.verify
131
183
  )
132
-
184
+
133
185
  if not response.ok:
134
186
  raise APIError(f"API request failed: {response.status_code} {response.reason}")
135
187
  return response.json()
@@ -61,36 +61,89 @@ class MassStats:
61
61
  return response
62
62
 
63
63
 
64
- # def _download_file(self, url: str, output_path: str) -> str:
65
- # """
66
- # Helper method to download a file from a signed URL.
64
+ def download_file(self, job_name: str, bucket:str, file_name: str, output_path: str) -> str:
65
+ """
66
+ Download a file from mass_stats using job name and file name.
67
+
68
+ Args:
69
+ job_name: Name of the job
70
+ file_name: Name of the file to download
71
+ output_path: Path where the file should be saved
72
+
73
+ Returns:
74
+ str: Path to the downloaded file
75
+ """
76
+ import os
77
+ from pathlib import Path
67
78
 
68
- # Args:
69
- # url: Signed URL to download from
70
- # output_path: Path where the file should be saved
79
+ endpoint_url = f"{self.base_url}/mass_stats/download_files"
80
+ request_body = {
81
+ "job_name": job_name,
82
+ "bucket": bucket,
83
+ "file_name": file_name
84
+ }
85
+
86
+ try:
87
+ # Get signed URL
88
+ response = self.session.post(
89
+ endpoint_url,
90
+ json=request_body,
91
+ verify=self.verify,
92
+ timeout=self.timeout
93
+ )
94
+ signed_url = response.json().get('download_url')
95
+ if not signed_url:
96
+ raise Exception("No download URL received from server")
97
+ print(f"Generated signed URL for download")
98
+
99
+ # Create output directory if it doesn't exist
100
+ output_dir = Path(output_path).parent
101
+ output_dir.mkdir(parents=True, exist_ok=True)
102
+
103
+ # Download the file using the signed URL
104
+ download_response = self.session.get(
105
+ signed_url,
106
+ verify=self.verify,
107
+ timeout=self.timeout,
108
+ stream=True # Stream for large files
109
+ )
110
+ download_response.raise_for_status()
111
+
112
+ # Check if file exists in the response (content-length header)
113
+ content_length = download_response.headers.get('content-length')
114
+ if content_length and int(content_length) == 0:
115
+ raise Exception("File appears to be empty")
71
116
 
72
- # Returns:
73
- # str: Path to the downloaded file
74
- # """
75
-
76
- # try:
77
- # response = requests.get(
78
- # url,
79
- # verify=self.verify,
80
- # timeout=self.timeout
81
- # )
82
- # response.raise_for_status()
117
+ # Write the file
118
+ with open(output_path, 'wb') as file:
119
+ for chunk in download_response.iter_content(chunk_size=8192):
120
+ if chunk:
121
+ file.write(chunk)
83
122
 
84
- # # Download and write the file
85
- # with open(output_path, 'wb') as file:
86
- # file.write(response.content)
87
- # print(f"File downloaded successfully to {output_path}")
88
- # return output_path
123
+ # Verify file was written
124
+ if not os.path.exists(output_path):
125
+ raise Exception(f"File was not written to {output_path}")
126
+
127
+ file_size = os.path.getsize(output_path)
128
+ print(f"File downloaded successfully to {output_path} (size: {file_size / (1024 * 1024):.4f} mb)")
129
+
130
+ return output_path
89
131
 
90
- # except requests.exceptions.RequestException as e:
91
- # raise Exception(f"Error downloading file from {url}: {e}")
92
- # except IOError as e:
93
- # raise Exception(f"Error writing file to {output_path}: {e}")
132
+ except self.session.exceptions.RequestException as e:
133
+ if hasattr(e, 'response') and e.response is not None:
134
+ error_detail = e.response.text
135
+ raise Exception(f"Error getting signed URL: {e}. Details: {error_detail}")
136
+ raise Exception(f"Error in download process: {e}")
137
+ except IOError as e:
138
+ raise Exception(f"Error writing file to {output_path}: {e}")
139
+ except Exception as e:
140
+ # Clean up partial file if it exists
141
+ if os.path.exists(output_path):
142
+ try:
143
+ os.remove(output_path)
144
+ except:
145
+ pass
146
+ raise
94
147
 
95
148
 
96
149
 
@@ -152,53 +205,7 @@ class MassStats:
152
205
  timeout=self.timeout
153
206
  )
154
207
  return response.json()
155
-
156
-
157
- # def construct_download_url(
158
- # self,
159
- # name: str,
160
- # output: str,
161
- # region: Optional[str] = None,
162
- # ) -> Dict[str, Any]:
163
- # """
164
- # Request a signed download URL for a file.
165
-
166
- # Args:
167
- # name: job name
168
- # file_type: Type of file to download (e.g., "output", "manifest", "log")
169
- # region: Region where the file is stored
170
-
171
- # Returns:
172
- # Dict containing download_url and file metadata
173
- # """
174
- # url = f"{self.base_url}/mass_stats/download"
175
-
176
- # data = {
177
- # "name": name,
178
- # "output": output
179
- # }
180
-
181
- # if region is not None:
182
- # data["region"] = region
183
-
184
- # response = self.session.post(
185
- # url,
186
- # json=data,
187
- # verify=self.verify,
188
- # timeout=self.timeout
189
- # )
190
-
191
- # return response.json()
192
-
193
208
 
194
- # def testdownload(
195
- # self,
196
- # name: str,
197
- # region: str,
198
- # output: str,
199
- # ):
200
- # upload_result = self.construct_download_url(name, region, output)
201
- # return upload_result
202
209
 
203
210
 
204
211
 
@@ -286,7 +293,7 @@ class MassStats:
286
293
  if uid is not None:
287
294
  url += f"&uid={uid}"
288
295
  response = self.session.get(url, verify=self.verify, timeout=self.timeout)
289
- print("response text is ", response.text)
296
+ #print("response text is ", response.text)
290
297
  return response.json()
291
298
 
292
299
  def track_job(self, ids: Optional[list] = None) -> Dict[str, Any]:
@@ -491,6 +498,7 @@ class MassStats:
491
498
 
492
499
 
493
500
 
501
+
494
502
 
495
503
 
496
504
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: terrakio-core
3
- Version: 0.3.1
3
+ Version: 0.3.3
4
4
  Summary: Core components for Terrakio API clients
5
5
  Author-email: Yupeng Chao <yupeng@haizea.com.au>
6
6
  Project-URL: Homepage, https://github.com/HaizeaAnalytics/terrakio-python-api
@@ -22,6 +22,7 @@ Requires-Dist: xarray>=2023.1.0
22
22
  Requires-Dist: shapely>=2.0.0
23
23
  Requires-Dist: geopandas>=0.13.0
24
24
  Requires-Dist: google-cloud-storage>=2.0.0
25
+ Requires-Dist: nest_asyncio
25
26
 
26
27
  # Terrakio Core
27
28
 
@@ -0,0 +1,16 @@
1
+ terrakio_core/__init__.py,sha256=MhRAUXppa228Lm-kvJGTngU1cUkJF5Q6PIBPwtqxn0E,88
2
+ terrakio_core/auth.py,sha256=tqviZwtW7qWSo1TEjvv0dMDkegUtaEN_tfdrJ6cPJtc,7182
3
+ terrakio_core/client.py,sha256=b76IaqhWYSq2XKgVlj4BQTtCPiBJ2wUNzwiNlVZ3X2k,73860
4
+ terrakio_core/config.py,sha256=MKTGSQRji8ty9dJlX8SOP7_3K2iR-3BXL-hUG7PsZQY,4324
5
+ terrakio_core/dataset_management.py,sha256=Hdk3nkwd70jw3lBNEaGixrqNVhUWOmsIYktzm_8vXdc,10913
6
+ terrakio_core/decorators.py,sha256=QeNOUX6WEAmdgBL5Igt5DXyYduh3jnmLbodttmwvXhE,785
7
+ terrakio_core/exceptions.py,sha256=9S-I20-QiDRj1qgjFyYUwYM7BLic_bxurcDOIm2Fu_0,410
8
+ terrakio_core/group_access_management.py,sha256=NJ7SX4keUzZAUENmJ5L6ynKf4eRlqtyir5uoKFyY17A,7315
9
+ terrakio_core/mass_stats.py,sha256=UGZo8BH4hzWe3k7pevsYAdRwnVZl-08lXjTlHD4nMgQ,18212
10
+ terrakio_core/space_management.py,sha256=wlUUQrlj_4U_Lpjn9lbF5oj0Rv3NPvvnrd5mWej5kmA,4211
11
+ terrakio_core/user_management.py,sha256=MMNWkz0V_9X7ZYjjteuRU4H4W3F16iuQw1dpA2wVTGg,7400
12
+ terrakio_core/generation/tiles.py,sha256=eiiMNzqaga-c42kG_7zHXTF2o8ZInCPUj0Vu4Ye30Ts,2980
13
+ terrakio_core-0.3.3.dist-info/METADATA,sha256=DIKsBR8Uqgt5ExhlwcaPNYXSLxWV3AZMRmkehbmgF3I,1476
14
+ terrakio_core-0.3.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
15
+ terrakio_core-0.3.3.dist-info/top_level.txt,sha256=5cBj6O7rNWyn97ND4YuvvXm0Crv4RxttT4JZvNdOG6Q,14
16
+ terrakio_core-0.3.3.dist-info/RECORD,,
@@ -1,16 +0,0 @@
1
- terrakio_core/__init__.py,sha256=279LuD40cJZBle4GQS_vxLIyyfejhEhcl2m9-4Qkdkk,88
2
- terrakio_core/auth.py,sha256=Nuj0_X3Hiy17svYgGxrSAR-LXpTlP0J0dSrfMnkPUbI,7717
3
- terrakio_core/client.py,sha256=rTwWP6r_aqEHoVGIpyMsy7pgVoaT2lSLNGpx0jf8QMU,62658
4
- terrakio_core/config.py,sha256=AwJ1VgR5K7N32XCU5k7_Dp1nIv_FYt8MBonq9yKlGzA,2658
5
- terrakio_core/dataset_management.py,sha256=LKUESSDPRu1JubQaQJWdPqHLGt-_Xv77Fpb4IM7vkzM,8751
6
- terrakio_core/decorators.py,sha256=QeNOUX6WEAmdgBL5Igt5DXyYduh3jnmLbodttmwvXhE,785
7
- terrakio_core/exceptions.py,sha256=9S-I20-QiDRj1qgjFyYUwYM7BLic_bxurcDOIm2Fu_0,410
8
- terrakio_core/group_access_management.py,sha256=NJ7SX4keUzZAUENmJ5L6ynKf4eRlqtyir5uoKFyY17A,7315
9
- terrakio_core/mass_stats.py,sha256=HHrpnlshADfCyVMD4VqR3jpIN9jCGnZaFDc_q5igG-Y,17215
10
- terrakio_core/space_management.py,sha256=wlUUQrlj_4U_Lpjn9lbF5oj0Rv3NPvvnrd5mWej5kmA,4211
11
- terrakio_core/user_management.py,sha256=MMNWkz0V_9X7ZYjjteuRU4H4W3F16iuQw1dpA2wVTGg,7400
12
- terrakio_core/generation/tiles.py,sha256=eiiMNzqaga-c42kG_7zHXTF2o8ZInCPUj0Vu4Ye30Ts,2980
13
- terrakio_core-0.3.1.dist-info/METADATA,sha256=fv09EWJ75yEd2k80SuM0-KKzOeCICvqbnxj9DKTdKrw,1448
14
- terrakio_core-0.3.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
15
- terrakio_core-0.3.1.dist-info/top_level.txt,sha256=5cBj6O7rNWyn97ND4YuvvXm0Crv4RxttT4JZvNdOG6Q,14
16
- terrakio_core-0.3.1.dist-info/RECORD,,