terrakio-core 0.3.2__py3-none-any.whl → 0.3.4__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/__init__.py CHANGED
@@ -4,4 +4,4 @@ Terrakio Core
4
4
  Core components for Terrakio API clients.
5
5
  """
6
6
 
7
- __version__ = "0.3.2"
7
+ __version__ = "0.3.4"
terrakio_core/auth.py CHANGED
@@ -127,12 +127,8 @@ class AuthClient:
127
127
  API key
128
128
 
129
129
  Raises:
130
- ConfigurationError: If not authenticated
131
130
  APIError: If refresh fails
132
131
  """
133
- if not self.token:
134
- raise ConfigurationError("Not authenticated. Call login() first.")
135
-
136
132
  endpoint = f"{self.base_url}/users/refresh_key"
137
133
 
138
134
  try:
@@ -167,14 +163,9 @@ class AuthClient:
167
163
  API key
168
164
 
169
165
  Raises:
170
- ConfigurationError: If not authenticated
171
166
  APIError: If retrieval fails
172
167
  """
173
- if not self.token:
174
- raise ConfigurationError("Not authenticated. Call login() first.")
175
-
176
168
  endpoint = f"{self.base_url}/users/key"
177
-
178
169
  try:
179
170
  # Use session with updated headers from login
180
171
  response = self.session.get(
@@ -207,14 +198,9 @@ class AuthClient:
207
198
  User information data
208
199
 
209
200
  Raises:
210
- ConfigurationError: If not authenticated
211
201
  APIError: If retrieval fails
212
202
  """
213
- if not self.token:
214
- raise ConfigurationError("Not authenticated. Call login() first.")
215
-
216
203
  endpoint = f"{self.base_url}/users/info"
217
-
218
204
  try:
219
205
  # Use session with updated headers from login
220
206
  response = self.session.get(
@@ -222,7 +208,6 @@ class AuthClient:
222
208
  verify=self.verify,
223
209
  timeout=self.timeout
224
210
  )
225
-
226
211
  if not response.ok:
227
212
  error_msg = f"Failed to retrieve user info: {response.status_code} {response.reason}"
228
213
  try:
terrakio_core/client.py CHANGED
@@ -2,6 +2,7 @@ import json
2
2
  import asyncio
3
3
  from io import BytesIO
4
4
  from typing import Dict, Any, Optional, Union
5
+ from functools import wraps
5
6
 
6
7
  import requests
7
8
  import aiohttp
@@ -18,8 +19,39 @@ import logging
18
19
  import textwrap
19
20
 
20
21
 
22
+ def require_api_key(func):
23
+ """
24
+ Decorator to ensure an API key is available before a method can be executed.
25
+ Will check for the presence of an API key before allowing the function to be called.
26
+ """
27
+ @wraps(func)
28
+ def wrapper(self, *args, **kwargs):
29
+ # Check if the API key is set
30
+ if not self.key:
31
+ error_msg = "No API key found. Please provide an API key to use this client."
32
+ if not self.quiet:
33
+ print(error_msg)
34
+ print("API key is required for this function.")
35
+ print("You can set an API key by either:")
36
+ print("1. Loading from a config file")
37
+ print("2. Using the login() method: client.login(email='your-email@example.com', password='your-password')")
38
+ raise ConfigurationError(error_msg)
39
+
40
+ # Check if URL is set (required for API calls)
41
+ if not self.url:
42
+ if not self.quiet:
43
+ print("API URL is not set. Using default API URL: https://api.terrak.io")
44
+ self.url = "https://api.terrak.io"
45
+
46
+ return func(self, *args, **kwargs)
47
+
48
+ # Mark as decorated to avoid double decoration
49
+ wrapper._is_decorated = True
50
+ return wrapper
51
+
52
+
21
53
  class BaseClient:
22
- def __init__(self, url: Optional[str] = None, key: Optional[str] = None,
54
+ def __init__(self, url: Optional[str] = None,
23
55
  auth_url: Optional[str] = "https://dev-au.terrak.io",
24
56
  quiet: bool = False, config_file: Optional[str] = None,
25
57
  verify: bool = True, timeout: int = 300):
@@ -28,57 +60,96 @@ class BaseClient:
28
60
  self.verify = verify
29
61
  self.timeout = timeout
30
62
  self.auth_client = None
31
- if auth_url:
32
- from terrakio_core.auth import AuthClient
33
- self.auth_client = AuthClient(
34
- base_url=auth_url,
35
- verify=verify,
36
- timeout=timeout
37
- )
63
+ # Initialize session early to avoid AttributeError
64
+ self.session = requests.Session()
65
+ self.user_management = None
66
+ self.dataset_management = None
67
+ self.mass_stats = None
68
+ self._aiohttp_session = None
69
+
70
+ # if auth_url:
71
+ # from terrakio_core.auth import AuthClient
72
+ # self.auth_client = AuthClient(
73
+ # base_url=auth_url,
74
+ # verify=verify,
75
+ # timeout=timeout
76
+ # )
38
77
  self.url = url
39
- self.key = key
40
- if self.url is None or self.key is None:
41
- from terrakio_core.config import read_config_file, DEFAULT_CONFIG_FILE
42
- if config_file is None:
43
- config_file = DEFAULT_CONFIG_FILE
44
- try:
45
- config = read_config_file(config_file)
46
- if self.url is None:
47
- self.url = config.get('url')
48
- if self.key is None:
49
- self.key = config.get('key')
50
- except Exception as e:
51
- raise ConfigurationError(
52
- f"Failed to read configuration: {e}\n\n"
53
- "To fix this issue:\n"
54
- "1. Create a file at ~/.terrakioapirc with:\n"
55
- "url: https://api.terrak.io\n"
56
- "key: your-api-key\n\n"
57
- "OR\n\n"
58
- "2. Initialize the client with explicit parameters:\n"
59
- "client = terrakio_api.Client(\n"
60
- " url='https://api.terrak.io',\n"
61
- " key='your-api-key'\n"
62
- ")"
63
- )
64
- if not self.url:
65
- raise ConfigurationError("Missing API URL in configuration")
78
+ self.key = None
79
+
80
+ # Load configuration from file
81
+ from terrakio_core.config import read_config_file, DEFAULT_CONFIG_FILE
82
+
83
+ if config_file is None:
84
+ config_file = DEFAULT_CONFIG_FILE
85
+
86
+ # Get config using the read_config_file function that now handles all cases
87
+ config = read_config_file(config_file, quiet=self.quiet)
88
+
89
+ # If URL is not provided, try to get it from config
90
+ if self.url is None:
91
+ self.url = config.get('url')
92
+
93
+ # Get API key from config file (never from parameters)
94
+ self.key = config.get('key')
95
+
96
+ self.token = config.get('token')
97
+
98
+ # Update auth_client with token if it exists
99
+ if self.auth_client and self.token:
100
+ self.auth_client.token = self.token
101
+ self.auth_client.session.headers.update({
102
+ "Authorization": self.token
103
+ })
104
+
105
+ # If we have a key, we're good to go even if not logged in
106
+ if self.key:
107
+ # If URL is missing but we have a key, use the default URL
108
+ if not self.url:
109
+ self.url = "https://api.terrak.io"
110
+ print("the token is! ", self.token)
111
+ print("the key is! ", self.key)
112
+ # Update session headers with the key
113
+ headers = {
114
+ 'Content-Type': 'application/json',
115
+ 'x-api-key': self.key
116
+ }
117
+
118
+ # Only add Authorization header if token exists and is not None
119
+ if self.token:
120
+ headers['Authorization'] = self.token
121
+
122
+ self.session.headers.update(headers)
123
+ print("the session headers are ", self.session.headers)
124
+
125
+
126
+ if not self.quiet and config.get('user_email'):
127
+ print(f"Using API key for: {config.get('user_email')}")
128
+
129
+ return
130
+
131
+ # Check if we have the required configuration
66
132
  if not self.key:
67
- raise ConfigurationError("Missing API key in configuration")
133
+ # No API key available - inform the user
134
+ if not self.quiet:
135
+ print("API key is required to use this client.")
136
+ print("You can set an API key by either:")
137
+ print("1. Loading from a config file")
138
+ print("2. Using the login() method: client.login(email='your-email@example.com', password='your-password')")
139
+ return
140
+
68
141
  self.url = self.url.rstrip('/')
69
142
  if not self.quiet:
70
143
  print(f"Using Terrakio API at: {self.url}")
71
- self.session = requests.Session()
144
+
145
+ # Update the session headers with API key
72
146
  self.session.headers.update({
73
147
  'Content-Type': 'application/json',
74
148
  'x-api-key': self.key
75
149
  })
76
- self.user_management = None
77
- self.dataset_management = None
78
- self.mass_stats = None
79
- self._aiohttp_session = None
80
150
 
81
151
  @property
152
+ @require_api_key
82
153
  async def aiohttp_session(self):
83
154
  if self._aiohttp_session is None or self._aiohttp_session.closed:
84
155
  self._aiohttp_session = aiohttp.ClientSession(
@@ -90,6 +161,7 @@ class BaseClient:
90
161
  )
91
162
  return self._aiohttp_session
92
163
 
164
+ @require_api_key
93
165
  async def wcs_async(self, expr: str, feature: Union[Dict[str, Any], ShapelyGeometry],
94
166
  in_crs: str = "epsg:4326", out_crs: str = "epsg:4326",
95
167
  output: str = "csv", resolution: int = -1, buffer: bool = False,
@@ -130,7 +202,6 @@ class BaseClient:
130
202
  "resolution": resolution,
131
203
  **kwargs
132
204
  }
133
- print("the payload is ", payload)
134
205
  request_url = f"{self.url}/geoquery"
135
206
  for attempt in range(retry + 1):
136
207
  try:
@@ -187,38 +258,67 @@ class BaseClient:
187
258
  raise
188
259
  continue
189
260
 
261
+ @require_api_key
190
262
  async def close_async(self):
191
263
  """Close the aiohttp session"""
192
264
  if self._aiohttp_session and not self._aiohttp_session.closed:
193
265
  await self._aiohttp_session.close()
194
266
  self._aiohttp_session = None
195
267
 
268
+ @require_api_key
196
269
  async def __aenter__(self):
197
270
  return self
198
271
 
272
+ @require_api_key
199
273
  async def __aexit__(self, exc_type, exc_val, exc_tb):
200
274
  await self.close_async()
201
275
 
202
276
  def signup(self, email: str, password: str) -> Dict[str, Any]:
203
277
  if not self.auth_client:
204
- raise ConfigurationError("Authentication client not initialized. Please provide auth_url during client initialization.")
278
+ from terrakio_core.auth import AuthClient
279
+ self.auth_client = AuthClient(
280
+ base_url=self.url,
281
+ verify=self.verify,
282
+ timeout=self.timeout
283
+ )
284
+ self.auth_client.session = self.session
205
285
  return self.auth_client.signup(email, password)
206
286
 
207
287
  def login(self, email: str, password: str) -> Dict[str, str]:
288
+
208
289
  if not self.auth_client:
209
- raise ConfigurationError("Authentication client not initialized. Please provide auth_url during client initialization.")
210
-
290
+ from terrakio_core.auth import AuthClient
291
+ self.auth_client = AuthClient(
292
+ base_url=self.url,
293
+ verify=self.verify,
294
+ timeout=self.timeout
295
+ )
296
+ self.auth_client.session = self.session
211
297
  try:
212
298
  # First attempt to login
213
299
  token_response = self.auth_client.login(email, password)
214
300
 
215
- print("the token response is ", token_response)
216
301
  # Only proceed with API key retrieval if login was successful
217
302
  if token_response:
218
303
  # After successful login, get the API key
219
- api_key_response = self.view_api_key()
304
+ api_key_response = self.auth_client.view_api_key()
220
305
  self.key = api_key_response
221
306
 
307
+ # Make sure URL is set
308
+ if not self.url:
309
+ self.url = "https://api.terrak.io"
310
+
311
+ # Make sure session headers are updated with the new key
312
+ self.session.headers.update({
313
+ 'Content-Type': 'application/json',
314
+ 'x-api-key': self.key
315
+ })
316
+
317
+ # Set URL if not already set
318
+ if not self.url:
319
+ self.url = "https://api.terrak.io"
320
+ self.url = self.url.rstrip('/')
321
+
222
322
  # Save email and API key to config file
223
323
  import os
224
324
  import json
@@ -230,6 +330,7 @@ class BaseClient:
230
330
  config = json.load(f)
231
331
  config["EMAIL"] = email
232
332
  config["TERRAKIO_API_KEY"] = self.key
333
+ config["PERSONAL_TOKEN"] = token_response
233
334
 
234
335
  os.makedirs(os.path.dirname(config_path), exist_ok=True)
235
336
  with open(config_path, 'w') as f:
@@ -237,6 +338,7 @@ class BaseClient:
237
338
 
238
339
  if not self.quiet:
239
340
  print(f"Successfully authenticated as: {email}")
341
+ print(f"Using Terrakio API at: {self.url}")
240
342
  print(f"API key saved to {config_path}")
241
343
  except Exception as e:
242
344
  if not self.quiet:
@@ -248,47 +350,81 @@ class BaseClient:
248
350
  print(f"Login failed: {str(e)}")
249
351
  raise
250
352
 
353
+ @require_api_key
251
354
  def refresh_api_key(self) -> str:
252
- if not self.auth_client:
253
- raise ConfigurationError("Authentication client not initialized. Please provide auth_url during client initialization.")
254
- if not self.auth_client.token:
255
- raise ConfigurationError("Not authenticated. Call login() first.")
256
- self.key = self.auth_client.refresh_api_key()
257
- self.session.headers.update({'x-api-key': self.key})
258
- import os
259
- config_path = os.path.join(os.environ.get("HOME", ""), ".tkio_config.json")
260
- try:
261
- config = {"EMAIL": "", "TERRAKIO_API_KEY": ""}
262
- if os.path.exists(config_path):
263
- with open(config_path, 'r') as f:
264
- config = json.load(f)
265
- config["TERRAKIO_API_KEY"] = self.key
266
- os.makedirs(os.path.dirname(config_path), exist_ok=True)
267
- with open(config_path, 'w') as f:
268
- json.dump(config, f, indent=4)
269
- if not self.quiet:
270
- print(f"API key generated successfully and updated in {config_path}")
271
- except Exception as e:
272
- if not self.quiet:
273
- print(f"Warning: Failed to update config file: {e}")
355
+ # If we have auth_client and it has a token, refresh the key
356
+ if self.auth_client and self.auth_client.token:
357
+ self.key = self.auth_client.refresh_api_key()
358
+ self.session.headers.update({'x-api-key': self.key})
359
+
360
+ # Update the config file with the new key
361
+ import os
362
+ config_path = os.path.join(os.environ.get("HOME", ""), ".tkio_config.json")
363
+ try:
364
+ config = {"EMAIL": "", "TERRAKIO_API_KEY": ""}
365
+ if os.path.exists(config_path):
366
+ with open(config_path, 'r') as f:
367
+ config = json.load(f)
368
+ config["TERRAKIO_API_KEY"] = self.key
369
+ os.makedirs(os.path.dirname(config_path), exist_ok=True)
370
+ with open(config_path, 'w') as f:
371
+ json.dump(config, f, indent=4)
372
+ if not self.quiet:
373
+ print(f"API key generated successfully and updated in {config_path}")
374
+ except Exception as e:
375
+ if not self.quiet:
376
+ print(f"Warning: Failed to update config file: {e}")
377
+ return self.key
378
+ else:
379
+ # If we don't have auth_client with a token but have a key already, return it
380
+ if self.key:
381
+ if not self.quiet:
382
+ print("Using existing API key from config file.")
383
+ return self.key
384
+ else:
385
+ raise ConfigurationError("No authentication token available. Please login first to refresh the API key.")
386
+
387
+ @require_api_key
388
+ def view_api_key(self) -> str:
389
+ # If we have the auth client and token, refresh the key
390
+ if self.auth_client and self.auth_client.token:
391
+ self.key = self.auth_client.view_api_key()
392
+ self.session.headers.update({'x-api-key': self.key})
393
+
274
394
  return self.key
275
395
 
396
+ @require_api_key
276
397
  def view_api_key(self) -> str:
398
+ # If we have the auth client and token, refresh the key
277
399
  if not self.auth_client:
278
- raise ConfigurationError("Authentication client not initialized. Please provide auth_url during client initialization.")
279
- if not self.auth_client.token:
280
- raise ConfigurationError("Not authenticated. Call login() first.")
281
- self.key = self.auth_client.view_api_key()
282
- self.session.headers.update({'x-api-key': self.key})
283
- return self.key
400
+ from terrakio_core.auth import AuthClient
401
+ self.auth_client = AuthClient(
402
+ base_url=self.url,
403
+ verify=self.verify,
404
+ timeout=self.timeout
405
+ )
406
+ self.auth_client.session = self.session
407
+ return self.auth_client.view_api_key()
284
408
 
409
+ # @require_api_key
410
+ # def get_user_info(self) -> Dict[str, Any]:
411
+ # return self.auth_client.get_user_info()
412
+ @require_api_key
285
413
  def get_user_info(self) -> Dict[str, Any]:
414
+ # Initialize auth_client if it doesn't exist
286
415
  if not self.auth_client:
287
- raise ConfigurationError("Authentication client not initialized. Please provide auth_url during client initialization.")
288
- if not self.auth_client.token:
289
- raise ConfigurationError("Not authenticated. Call login() first.")
416
+ from terrakio_core.auth import AuthClient
417
+ self.auth_client = AuthClient(
418
+ base_url=self.url,
419
+ verify=self.verify,
420
+ timeout=self.timeout
421
+ )
422
+ # Use the same session as the base client
423
+ self.auth_client.session = self.session
424
+
290
425
  return self.auth_client.get_user_info()
291
426
 
427
+ @require_api_key
292
428
  def wcs(self, expr: str, feature: Union[Dict[str, Any], ShapelyGeometry], in_crs: str = "epsg:4326",
293
429
  out_crs: str = "epsg:4326", output: str = "csv", resolution: int = -1,
294
430
  **kwargs):
@@ -310,8 +446,6 @@ class BaseClient:
310
446
  }
311
447
  request_url = f"{self.url}/geoquery"
312
448
  try:
313
- print("the request url is ", request_url)
314
- print("the payload is ", payload)
315
449
  response = self.session.post(request_url, json=payload, timeout=self.timeout, verify=self.verify)
316
450
  print("the response is ", response.text)
317
451
  if not response.ok:
@@ -341,6 +475,7 @@ class BaseClient:
341
475
  raise APIError(f"Request failed: {str(e)}")
342
476
 
343
477
  # Admin/protected methods
478
+ @require_api_key
344
479
  def _get_user_by_id(self, user_id: str):
345
480
  if not self.user_management:
346
481
  from terrakio_core.user_management import UserManagement
@@ -354,6 +489,7 @@ class BaseClient:
354
489
  )
355
490
  return self.user_management.get_user_by_id(user_id)
356
491
 
492
+ @require_api_key
357
493
  def _get_user_by_email(self, email: str):
358
494
  if not self.user_management:
359
495
  from terrakio_core.user_management import UserManagement
@@ -367,6 +503,7 @@ class BaseClient:
367
503
  )
368
504
  return self.user_management.get_user_by_email(email)
369
505
 
506
+ @require_api_key
370
507
  def _list_users(self, substring: str = None, uid: bool = False):
371
508
  if not self.user_management:
372
509
  from terrakio_core.user_management import UserManagement
@@ -380,6 +517,7 @@ class BaseClient:
380
517
  )
381
518
  return self.user_management.list_users(substring=substring, uid=uid)
382
519
 
520
+ @require_api_key
383
521
  def _edit_user(self, user_id: str, uid: str = None, email: str = None, role: str = None, apiKey: str = None, groups: list = None, quota: int = None):
384
522
  if not self.user_management:
385
523
  from terrakio_core.user_management import UserManagement
@@ -401,6 +539,7 @@ class BaseClient:
401
539
  quota=quota
402
540
  )
403
541
 
542
+ @require_api_key
404
543
  def _reset_quota(self, email: str, quota: int = None):
405
544
  if not self.user_management:
406
545
  from terrakio_core.user_management import UserManagement
@@ -414,6 +553,7 @@ class BaseClient:
414
553
  )
415
554
  return self.user_management.reset_quota(email=email, quota=quota)
416
555
 
556
+ @require_api_key
417
557
  def _delete_user(self, uid: str):
418
558
  if not self.user_management:
419
559
  from terrakio_core.user_management import UserManagement
@@ -428,6 +568,7 @@ class BaseClient:
428
568
  return self.user_management.delete_user(uid=uid)
429
569
 
430
570
  # Dataset management protected methods
571
+ @require_api_key
431
572
  def _get_dataset(self, name: str, collection: str = "terrakio-datasets"):
432
573
  if not self.dataset_management:
433
574
  from terrakio_core.dataset_management import DatasetManagement
@@ -441,6 +582,7 @@ class BaseClient:
441
582
  )
442
583
  return self.dataset_management.get_dataset(name=name, collection=collection)
443
584
 
585
+ @require_api_key
444
586
  def _list_datasets(self, substring: str = None, collection: str = "terrakio-datasets"):
445
587
  if not self.dataset_management:
446
588
  from terrakio_core.dataset_management import DatasetManagement
@@ -454,6 +596,7 @@ class BaseClient:
454
596
  )
455
597
  return self.dataset_management.list_datasets(substring=substring, collection=collection)
456
598
 
599
+ @require_api_key
457
600
  def _create_dataset(self, name: str, collection: str = "terrakio-datasets", **kwargs):
458
601
  if not self.dataset_management:
459
602
  from terrakio_core.dataset_management import DatasetManagement
@@ -467,6 +610,7 @@ class BaseClient:
467
610
  )
468
611
  return self.dataset_management.create_dataset(name=name, collection=collection, **kwargs)
469
612
 
613
+ @require_api_key
470
614
  def _update_dataset(self, name: str, append: bool = True, collection: str = "terrakio-datasets", **kwargs):
471
615
  if not self.dataset_management:
472
616
  from terrakio_core.dataset_management import DatasetManagement
@@ -480,6 +624,7 @@ class BaseClient:
480
624
  )
481
625
  return self.dataset_management.update_dataset(name=name, append=append, collection=collection, **kwargs)
482
626
 
627
+ @require_api_key
483
628
  def _overwrite_dataset(self, name: str, collection: str = "terrakio-datasets", **kwargs):
484
629
  if not self.dataset_management:
485
630
  from terrakio_core.dataset_management import DatasetManagement
@@ -493,6 +638,7 @@ class BaseClient:
493
638
  )
494
639
  return self.dataset_management.overwrite_dataset(name=name, collection=collection, **kwargs)
495
640
 
641
+ @require_api_key
496
642
  def _delete_dataset(self, name: str, collection: str = "terrakio-datasets"):
497
643
  if not self.dataset_management:
498
644
  from terrakio_core.dataset_management import DatasetManagement
@@ -506,6 +652,7 @@ class BaseClient:
506
652
  )
507
653
  return self.dataset_management.delete_dataset(name=name, collection=collection)
508
654
 
655
+ @require_api_key
509
656
  def close(self):
510
657
  """Close all client sessions"""
511
658
  self.session.close()
@@ -532,13 +679,16 @@ class BaseClient:
532
679
  # Event loop may already be closed, ignore
533
680
  pass
534
681
 
682
+ @require_api_key
535
683
  def __enter__(self):
536
684
  return self
537
685
 
686
+ @require_api_key
538
687
  def __exit__(self, exc_type, exc_val, exc_tb):
539
688
  self.close()
540
689
 
541
690
  @admin_only_params('location', 'force_loc', 'server')
691
+ @require_api_key
542
692
  def execute_job(self, name, region, output, config, overwrite=False, skip_existing=False, request_json=None, manifest_json=None, location=None, force_loc=None, server="dev-au.terrak.io"):
543
693
  if not self.mass_stats:
544
694
  from terrakio_core.mass_stats import MassStats
@@ -553,6 +703,7 @@ class BaseClient:
553
703
  return self.mass_stats.execute_job(name, region, output, config, overwrite, skip_existing, request_json, manifest_json, location, force_loc, server)
554
704
 
555
705
 
706
+ @require_api_key
556
707
  def get_mass_stats_task_id(self, name, stage, uid=None):
557
708
  if not self.mass_stats:
558
709
  from terrakio_core.mass_stats import MassStats
@@ -566,6 +717,7 @@ class BaseClient:
566
717
  )
567
718
  return self.mass_stats.get_task_id(name, stage, uid)
568
719
 
720
+ @require_api_key
569
721
  def track_mass_stats_job(self, ids: Optional[list] = None):
570
722
  if not self.mass_stats:
571
723
  from terrakio_core.mass_stats import MassStats
@@ -579,6 +731,7 @@ class BaseClient:
579
731
  )
580
732
  return self.mass_stats.track_job(ids)
581
733
 
734
+ @require_api_key
582
735
  def get_mass_stats_history(self, limit=100):
583
736
  if not self.mass_stats:
584
737
  from terrakio_core.mass_stats import MassStats
@@ -592,6 +745,7 @@ class BaseClient:
592
745
  )
593
746
  return self.mass_stats.get_history(limit)
594
747
 
748
+ @require_api_key
595
749
  def start_mass_stats_post_processing(self, process_name, data_name, output, consumer_path, overwrite=False):
596
750
  if not self.mass_stats:
597
751
  from terrakio_core.mass_stats import MassStats
@@ -605,6 +759,7 @@ class BaseClient:
605
759
  )
606
760
  return self.mass_stats.start_post_processing(process_name, data_name, output, consumer_path, overwrite)
607
761
 
762
+ @require_api_key
608
763
  def download_mass_stats_results(self, id=None, force_loc=False, **kwargs):
609
764
  if not self.mass_stats:
610
765
  from terrakio_core.mass_stats import MassStats
@@ -618,6 +773,7 @@ class BaseClient:
618
773
  )
619
774
  return self.mass_stats.download_results(id, force_loc, **kwargs)
620
775
 
776
+ @require_api_key
621
777
  def cancel_mass_stats_job(self, id):
622
778
  if not self.mass_stats:
623
779
  from terrakio_core.mass_stats import MassStats
@@ -631,6 +787,7 @@ class BaseClient:
631
787
  )
632
788
  return self.mass_stats.cancel_job(id)
633
789
 
790
+ @require_api_key
634
791
  def cancel_all_mass_stats_jobs(self):
635
792
  if not self.mass_stats:
636
793
  from terrakio_core.mass_stats import MassStats
@@ -644,6 +801,7 @@ class BaseClient:
644
801
  )
645
802
  return self.mass_stats.cancel_all_jobs()
646
803
 
804
+ @require_api_key
647
805
  def _create_pyramids(self, name, levels, config):
648
806
  if not self.mass_stats:
649
807
  from terrakio_core.mass_stats import MassStats
@@ -657,6 +815,7 @@ class BaseClient:
657
815
  )
658
816
  return self.mass_stats.create_pyramids(name, levels, config)
659
817
 
818
+ @require_api_key
660
819
  def random_sample(self, name, **kwargs):
661
820
  if not self.mass_stats:
662
821
  from terrakio_core.mass_stats import MassStats
@@ -670,6 +829,7 @@ class BaseClient:
670
829
  )
671
830
  return self.mass_stats.random_sample(name, **kwargs)
672
831
 
832
+ @require_api_key
673
833
  async def zonal_stats_async(self, gdb, expr, conc=20, inplace=False, output="csv",
674
834
  in_crs="epsg:4326", out_crs="epsg:4326", resolution=-1, buffer=False):
675
835
  """
@@ -858,6 +1018,7 @@ class BaseClient:
858
1018
  else:
859
1019
  return result_gdf
860
1020
 
1021
+ @require_api_key
861
1022
  def zonal_stats(self, gdb, expr, conc=20, inplace=False, output="csv",
862
1023
  in_crs="epsg:4326", out_crs="epsg:4326", resolution=-1, buffer=False):
863
1024
  """
@@ -920,75 +1081,80 @@ class BaseClient:
920
1081
  return result
921
1082
 
922
1083
  # Group access management protected methods
1084
+ @require_api_key
923
1085
  def _get_group_users_and_datasets(self, group_name: str):
924
- if not hasattr(self, "group_access_management") or self.group_access_management is None:
1086
+ if not self.group_access:
925
1087
  from terrakio_core.group_access_management import GroupAccessManagement
926
1088
  if not self.url or not self.key:
927
1089
  raise ConfigurationError("Group access management client not initialized. Make sure API URL and key are set.")
928
- self.group_access_management = GroupAccessManagement(
1090
+ self.group_access = GroupAccessManagement(
929
1091
  api_url=self.url,
930
1092
  api_key=self.key,
931
1093
  verify=self.verify,
932
1094
  timeout=self.timeout
933
1095
  )
934
- return self.group_access_management.get_group_users_and_datasets(group_name)
1096
+ return self.group_access.get_group_users_and_datasets(group_name=group_name)
935
1097
 
1098
+ @require_api_key
936
1099
  def _add_group_to_dataset(self, dataset: str, group: str):
937
- if not hasattr(self, "group_access_management") or self.group_access_management is None:
1100
+ if not self.group_access:
938
1101
  from terrakio_core.group_access_management import GroupAccessManagement
939
1102
  if not self.url or not self.key:
940
1103
  raise ConfigurationError("Group access management client not initialized. Make sure API URL and key are set.")
941
- self.group_access_management = GroupAccessManagement(
1104
+ self.group_access = GroupAccessManagement(
942
1105
  api_url=self.url,
943
1106
  api_key=self.key,
944
1107
  verify=self.verify,
945
1108
  timeout=self.timeout
946
1109
  )
947
- return self.group_access_management.add_group_to_dataset(dataset, group)
1110
+ return self.group_access.add_group_to_dataset(dataset=dataset, group=group)
948
1111
 
1112
+ @require_api_key
949
1113
  def _add_group_to_user(self, uid: str, group: str):
950
- if not hasattr(self, "group_access_management") or self.group_access_management is None:
1114
+ if not self.group_access:
951
1115
  from terrakio_core.group_access_management import GroupAccessManagement
952
1116
  if not self.url or not self.key:
953
1117
  raise ConfigurationError("Group access management client not initialized. Make sure API URL and key are set.")
954
- self.group_access_management = GroupAccessManagement(
1118
+ self.group_access = GroupAccessManagement(
955
1119
  api_url=self.url,
956
1120
  api_key=self.key,
957
1121
  verify=self.verify,
958
1122
  timeout=self.timeout
959
1123
  )
960
- print("the uid is and the group is ", uid, group)
961
- return self.group_access_management.add_group_to_user(uid, group)
1124
+ return self.group_access.add_group_to_user(uid=uid, group=group)
962
1125
 
1126
+ @require_api_key
963
1127
  def _delete_group_from_user(self, uid: str, group: str):
964
- if not hasattr(self, "group_access_management") or self.group_access_management is None:
1128
+ if not self.group_access:
965
1129
  from terrakio_core.group_access_management import GroupAccessManagement
966
1130
  if not self.url or not self.key:
967
1131
  raise ConfigurationError("Group access management client not initialized. Make sure API URL and key are set.")
968
- self.group_access_management = GroupAccessManagement(
1132
+ self.group_access = GroupAccessManagement(
969
1133
  api_url=self.url,
970
1134
  api_key=self.key,
971
1135
  verify=self.verify,
972
1136
  timeout=self.timeout
973
1137
  )
974
- return self.group_access_management.delete_group_from_user(uid, group)
1138
+ return self.group_access.delete_group_from_user(uid=uid, group=group)
975
1139
 
1140
+ @require_api_key
976
1141
  def _delete_group_from_dataset(self, dataset: str, group: str):
977
- if not hasattr(self, "group_access_management") or self.group_access_management is None:
1142
+ if not self.group_access:
978
1143
  from terrakio_core.group_access_management import GroupAccessManagement
979
1144
  if not self.url or not self.key:
980
1145
  raise ConfigurationError("Group access management client not initialized. Make sure API URL and key are set.")
981
- self.group_access_management = GroupAccessManagement(
1146
+ self.group_access = GroupAccessManagement(
982
1147
  api_url=self.url,
983
1148
  api_key=self.key,
984
1149
  verify=self.verify,
985
1150
  timeout=self.timeout
986
1151
  )
987
- return self.group_access_management.delete_group_from_dataset(dataset, group)
1152
+ return self.group_access.delete_group_from_dataset(dataset=dataset, group=group)
988
1153
 
989
1154
  # Space management protected methods
1155
+ @require_api_key
990
1156
  def _get_total_space_used(self):
991
- if not hasattr(self, "space_management") or self.space_management is None:
1157
+ if not self.space_management:
992
1158
  from terrakio_core.space_management import SpaceManagement
993
1159
  if not self.url or not self.key:
994
1160
  raise ConfigurationError("Space management client not initialized. Make sure API URL and key are set.")
@@ -1000,8 +1166,9 @@ class BaseClient:
1000
1166
  )
1001
1167
  return self.space_management.get_total_space_used()
1002
1168
 
1169
+ @require_api_key
1003
1170
  def _get_space_used_by_job(self, name: str, region: str = None):
1004
- if not hasattr(self, "space_management") or self.space_management is None:
1171
+ if not self.space_management:
1005
1172
  from terrakio_core.space_management import SpaceManagement
1006
1173
  if not self.url or not self.key:
1007
1174
  raise ConfigurationError("Space management client not initialized. Make sure API URL and key are set.")
@@ -1013,8 +1180,9 @@ class BaseClient:
1013
1180
  )
1014
1181
  return self.space_management.get_space_used_by_job(name, region)
1015
1182
 
1183
+ @require_api_key
1016
1184
  def _delete_user_job(self, name: str, region: str = None):
1017
- if not hasattr(self, "space_management") or self.space_management is None:
1185
+ if not self.space_management:
1018
1186
  from terrakio_core.space_management import SpaceManagement
1019
1187
  if not self.url or not self.key:
1020
1188
  raise ConfigurationError("Space management client not initialized. Make sure API URL and key are set.")
@@ -1026,8 +1194,9 @@ class BaseClient:
1026
1194
  )
1027
1195
  return self.space_management.delete_user_job(name, region)
1028
1196
 
1197
+ @require_api_key
1029
1198
  def _delete_data_in_path(self, path: str, region: str = None):
1030
- if not hasattr(self, "space_management") or self.space_management is None:
1199
+ if not self.space_management:
1031
1200
  from terrakio_core.space_management import SpaceManagement
1032
1201
  if not self.url or not self.key:
1033
1202
  raise ConfigurationError("Space management client not initialized. Make sure API URL and key are set.")
@@ -1039,6 +1208,7 @@ class BaseClient:
1039
1208
  )
1040
1209
  return self.space_management.delete_data_in_path(path, region)
1041
1210
 
1211
+ @require_api_key
1042
1212
  def start_mass_stats_job(self, task_id):
1043
1213
  if not self.mass_stats:
1044
1214
  from terrakio_core.mass_stats import MassStats
@@ -1053,6 +1223,7 @@ class BaseClient:
1053
1223
  return self.mass_stats.start_job(task_id)
1054
1224
 
1055
1225
 
1226
+ @require_api_key
1056
1227
  def generate_ai_dataset(
1057
1228
  self,
1058
1229
  name: str,
@@ -1147,6 +1318,7 @@ class BaseClient:
1147
1318
  # print("the task id is ", task_id)
1148
1319
  return task_id
1149
1320
 
1321
+ @require_api_key
1150
1322
  def train_model(self, model_name: str, training_dataset: str, task_type: str, model_category: str, architecture: str, region: str, hyperparameters: dict = None) -> dict:
1151
1323
  """
1152
1324
  Train a model using the external model training API.
@@ -1173,7 +1345,6 @@ class BaseClient:
1173
1345
  "hyperparameters": hyperparameters
1174
1346
  }
1175
1347
  endpoint = f"{self.url.rstrip('/')}/train_model"
1176
- print("the payload is ", payload)
1177
1348
  try:
1178
1349
  response = self.session.post(endpoint, json=payload, timeout=self.timeout, verify=self.verify)
1179
1350
  if not response.ok:
@@ -1191,6 +1362,7 @@ class BaseClient:
1191
1362
  raise APIError(f"Model training request failed: {str(e)}")
1192
1363
 
1193
1364
  # Mass Stats methods
1365
+ @require_api_key
1194
1366
  def combine_tiles(self,
1195
1367
  data_name: str,
1196
1368
  usezarr: bool,
@@ -1211,6 +1383,7 @@ class BaseClient:
1211
1383
 
1212
1384
 
1213
1385
 
1386
+ @require_api_key
1214
1387
  def create_dataset_file(
1215
1388
  self,
1216
1389
  name: str,
@@ -1226,9 +1399,22 @@ class BaseClient:
1226
1399
  skip_existing: bool = False,
1227
1400
  non_interactive: bool = True,
1228
1401
  usezarr: bool = False,
1229
- poll_interval: int = 30 # seconds between job status checks
1402
+ poll_interval: int = 30,
1403
+ download_path: str = "/home/user/Downloads",
1230
1404
  ) -> dict:
1231
1405
 
1406
+ if not self.mass_stats:
1407
+ from terrakio_core.mass_stats import MassStats
1408
+ if not self.url or not self.key:
1409
+ raise ConfigurationError("Mass Stats client not initialized. Make sure API URL and key are set.")
1410
+ self.mass_stats = MassStats(
1411
+ base_url=self.url,
1412
+ api_key=self.key,
1413
+ verify=self.verify,
1414
+ timeout=self.timeout
1415
+ )
1416
+
1417
+
1232
1418
  from terrakio_core.generation.tiles import tiles
1233
1419
  import tempfile
1234
1420
  import time
@@ -1317,26 +1503,60 @@ class BaseClient:
1317
1503
  os.unlink(tempmanifestname)
1318
1504
 
1319
1505
 
1506
+ # return self.mass_stats.combine_tiles(body["name"], usezarr, body["overwrite"], body["output"])
1507
+
1320
1508
  # Start combining tiles
1321
- if not self.mass_stats:
1322
- from terrakio_core.mass_stats import MassStats
1323
- if not self.url or not self.key:
1324
- raise ConfigurationError("Mass Stats client not initialized. Make sure API URL and key are set.")
1325
- self.mass_stats = MassStats(
1326
- base_url=self.url,
1327
- api_key=self.key,
1328
- verify=self.verify,
1329
- timeout=self.timeout
1330
- )
1331
-
1332
- return self.mass_stats.combine_tiles(body["name"], usezarr, body["overwrite"], body["output"])
1509
+ combine_result = self.mass_stats.combine_tiles(body["name"], usezarr, body["overwrite"], body["output"])
1510
+ combine_task_id = combine_result.get("task_id")
1511
+
1512
+ # Poll combine_tiles job status
1513
+ combine_start_time = time.time()
1514
+ while True:
1515
+ try:
1516
+ trackinfo = self.mass_stats.track_job([combine_task_id])
1517
+ download_file_name = trackinfo[combine_task_id]['folder'] + '.nc'
1518
+ bucket = trackinfo[combine_task_id]['bucket']
1519
+ combine_status = trackinfo[combine_task_id]['status']
1520
+ if combine_status == 'Completed':
1521
+ print('Tiles combined successfully!')
1522
+ break
1523
+ elif combine_status in ['Failed', 'Cancelled', 'Error']:
1524
+ raise RuntimeError(f"Combine job {combine_task_id} failed with status: {combine_status}")
1525
+ else:
1526
+ elapsed_time = time.time() - combine_start_time
1527
+ print(f"Combine job status: {combine_status} - Elapsed time: {elapsed_time:.1f}s", end='\r')
1528
+ time.sleep(poll_interval)
1529
+ except KeyboardInterrupt:
1530
+ print(f"\nInterrupted! Combine job {combine_task_id} is still running in the background.")
1531
+ raise
1532
+ except Exception as e:
1533
+ print(f"\nError tracking combine job: {e}")
1534
+ raise
1535
+
1536
+ # Download the resulting file
1537
+ if download_path:
1538
+ self.mass_stats.download_file(body["name"], bucket, download_file_name, download_path)
1539
+ else:
1540
+ path = f"{body['name']}/combinedOutput/{download_file_name}"
1541
+ print(f"Combined file is available at {path}")
1542
+
1543
+ return {"generation_task_id": task_id, "combine_task_id": combine_task_id}
1544
+
1545
+
1546
+ # taskid = self.mass_stats.get_task_id(job_name, stage).get('task_id')
1547
+ # trackinfo = self.mass_stats.track_job([taskid])
1548
+ # bucket = trackinfo[taskid]['bucket']
1549
+ # return self.mass_stats.download_file(job_name, bucket, file_name, output_path)
1333
1550
 
1551
+
1552
+ @require_api_key
1334
1553
  def deploy_model(self, dataset: str, product:str, model_name:str, input_expression: str, model_training_job_name: str, uid: str, dates_iso8601: list):
1335
1554
  script_content = self._generate_script(model_name, product, model_training_job_name, uid)
1336
1555
  script_name = f"{product}.py"
1337
1556
  self._upload_script_to_bucket(script_content, script_name, model_training_job_name, uid)
1338
1557
  self._create_dataset(name = dataset, collection = "terrakio-datasets", products = [product], path = f"gs://terrakio-mass-requests/{uid}/{model_training_job_name}/inference_scripts", input = input_expression, dates_iso8601 = dates_iso8601, padding = 0)
1339
1558
 
1559
+ @require_api_key
1340
1560
  def _generate_script(self, model_name: str, product: str, model_training_job_name: str, uid: str) -> str:
1341
1561
  return textwrap.dedent(f'''
1342
1562
  import logging
@@ -1443,6 +1663,7 @@ class BaseClient:
1443
1663
  return result
1444
1664
  ''').strip()
1445
1665
 
1666
+ @require_api_key
1446
1667
  def _upload_script_to_bucket(self, script_content: str, script_name: str, model_training_job_name: str, uid: str):
1447
1668
  """Upload the generated script to Google Cloud Storage"""
1448
1669
 
@@ -1455,6 +1676,7 @@ class BaseClient:
1455
1676
 
1456
1677
 
1457
1678
 
1679
+ @require_api_key
1458
1680
  def download_file_to_path(self, job_name, stage, file_name, output_path):
1459
1681
  if not self.mass_stats:
1460
1682
  from terrakio_core.mass_stats import MassStats
@@ -1472,4 +1694,34 @@ class BaseClient:
1472
1694
  taskid = self.mass_stats.get_task_id(job_name, stage).get('task_id')
1473
1695
  trackinfo = self.mass_stats.track_job([taskid])
1474
1696
  bucket = trackinfo[taskid]['bucket']
1475
- return self.mass_stats.download_file(job_name, bucket, file_name, output_path)
1697
+ return self.mass_stats.download_file(job_name, bucket, file_name, output_path)
1698
+
1699
+
1700
+ # Apply the @require_api_key decorator to ALL methods in BaseClient
1701
+ # except only the absolute minimum that shouldn't require auth
1702
+ def _apply_api_key_decorator():
1703
+ # Only these methods can be used without API key
1704
+ skip_auth_methods = [
1705
+ '__init__', 'login', 'signup'
1706
+ ]
1707
+
1708
+ # Get all attributes of BaseClient
1709
+ for attr_name in dir(BaseClient):
1710
+ # Skip special methods and methods in skip list
1711
+ if attr_name.startswith('__') and attr_name not in ['__enter__', '__exit__', '__aenter__', '__aexit__'] or attr_name in skip_auth_methods:
1712
+ continue
1713
+
1714
+ # Get the attribute
1715
+ attr = getattr(BaseClient, attr_name)
1716
+
1717
+ # Skip if not callable (not a method) or already decorated
1718
+ if not callable(attr) or hasattr(attr, '_is_decorated'):
1719
+ continue
1720
+
1721
+ # Apply decorator to EVERY method not in skip list
1722
+ setattr(BaseClient, attr_name, require_api_key(attr))
1723
+ # Mark as decorated to avoid double decoration
1724
+ getattr(BaseClient, attr_name)._is_decorated = True
1725
+
1726
+ # Run the decorator application
1727
+ _apply_api_key_decorator()
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
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: terrakio-core
3
- Version: 0.3.2
3
+ Version: 0.3.4
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
@@ -1,7 +1,7 @@
1
- terrakio_core/__init__.py,sha256=faOeYeL7Lmg3aTRVxVEBT6Dbhi62N4eeXK3mAluD3pA,88
2
- terrakio_core/auth.py,sha256=Nuj0_X3Hiy17svYgGxrSAR-LXpTlP0J0dSrfMnkPUbI,7717
3
- terrakio_core/client.py,sha256=oJPg4hcdCBaPwYN0SKQy57YUsJBTTA-SbQ4aaHaNq8E,65578
4
- terrakio_core/config.py,sha256=AwJ1VgR5K7N32XCU5k7_Dp1nIv_FYt8MBonq9yKlGzA,2658
1
+ terrakio_core/__init__.py,sha256=Pn9_C8YL77990JgyJlSu_zCafX8L-zCDjnFiKl_SDUY,88
2
+ terrakio_core/auth.py,sha256=tqviZwtW7qWSo1TEjvv0dMDkegUtaEN_tfdrJ6cPJtc,7182
3
+ terrakio_core/client.py,sha256=YMbLyevSdxAKlX7K93aOhvfjnM7NCCb4ohetcYzigJM,73676
4
+ terrakio_core/config.py,sha256=MKTGSQRji8ty9dJlX8SOP7_3K2iR-3BXL-hUG7PsZQY,4324
5
5
  terrakio_core/dataset_management.py,sha256=Hdk3nkwd70jw3lBNEaGixrqNVhUWOmsIYktzm_8vXdc,10913
6
6
  terrakio_core/decorators.py,sha256=QeNOUX6WEAmdgBL5Igt5DXyYduh3jnmLbodttmwvXhE,785
7
7
  terrakio_core/exceptions.py,sha256=9S-I20-QiDRj1qgjFyYUwYM7BLic_bxurcDOIm2Fu_0,410
@@ -10,7 +10,7 @@ terrakio_core/mass_stats.py,sha256=UGZo8BH4hzWe3k7pevsYAdRwnVZl-08lXjTlHD4nMgQ,1
10
10
  terrakio_core/space_management.py,sha256=wlUUQrlj_4U_Lpjn9lbF5oj0Rv3NPvvnrd5mWej5kmA,4211
11
11
  terrakio_core/user_management.py,sha256=MMNWkz0V_9X7ZYjjteuRU4H4W3F16iuQw1dpA2wVTGg,7400
12
12
  terrakio_core/generation/tiles.py,sha256=eiiMNzqaga-c42kG_7zHXTF2o8ZInCPUj0Vu4Ye30Ts,2980
13
- terrakio_core-0.3.2.dist-info/METADATA,sha256=QaQJrukRnVgy2jAqNr-BDCUaybrqFMbjOlr2KqLlhJI,1476
14
- terrakio_core-0.3.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
15
- terrakio_core-0.3.2.dist-info/top_level.txt,sha256=5cBj6O7rNWyn97ND4YuvvXm0Crv4RxttT4JZvNdOG6Q,14
16
- terrakio_core-0.3.2.dist-info/RECORD,,
13
+ terrakio_core-0.3.4.dist-info/METADATA,sha256=yC1XxiHDyyzVvXsqgYP7T2mvXHuN_w6h7UYp4rO-fBY,1476
14
+ terrakio_core-0.3.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
15
+ terrakio_core-0.3.4.dist-info/top_level.txt,sha256=5cBj6O7rNWyn97ND4YuvvXm0Crv4RxttT4JZvNdOG6Q,14
16
+ terrakio_core-0.3.4.dist-info/RECORD,,