terrakio-core 0.3.2__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/__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.3"
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,
@@ -187,38 +259,67 @@ class BaseClient:
187
259
  raise
188
260
  continue
189
261
 
262
+ @require_api_key
190
263
  async def close_async(self):
191
264
  """Close the aiohttp session"""
192
265
  if self._aiohttp_session and not self._aiohttp_session.closed:
193
266
  await self._aiohttp_session.close()
194
267
  self._aiohttp_session = None
195
268
 
269
+ @require_api_key
196
270
  async def __aenter__(self):
197
271
  return self
198
272
 
273
+ @require_api_key
199
274
  async def __aexit__(self, exc_type, exc_val, exc_tb):
200
275
  await self.close_async()
201
276
 
202
277
  def signup(self, email: str, password: str) -> Dict[str, Any]:
203
278
  if not self.auth_client:
204
- raise ConfigurationError("Authentication client not initialized. Please provide auth_url during client initialization.")
279
+ from terrakio_core.auth import AuthClient
280
+ self.auth_client = AuthClient(
281
+ base_url=self.url,
282
+ verify=self.verify,
283
+ timeout=self.timeout
284
+ )
285
+ self.auth_client.session = self.session
205
286
  return self.auth_client.signup(email, password)
206
287
 
207
288
  def login(self, email: str, password: str) -> Dict[str, str]:
289
+
208
290
  if not self.auth_client:
209
- raise ConfigurationError("Authentication client not initialized. Please provide auth_url during client initialization.")
210
-
291
+ from terrakio_core.auth import AuthClient
292
+ self.auth_client = AuthClient(
293
+ base_url=self.url,
294
+ verify=self.verify,
295
+ timeout=self.timeout
296
+ )
297
+ self.auth_client.session = self.session
211
298
  try:
212
299
  # First attempt to login
213
300
  token_response = self.auth_client.login(email, password)
214
301
 
215
- print("the token response is ", token_response)
216
302
  # Only proceed with API key retrieval if login was successful
217
303
  if token_response:
218
304
  # After successful login, get the API key
219
- api_key_response = self.view_api_key()
305
+ api_key_response = self.auth_client.view_api_key()
220
306
  self.key = api_key_response
221
307
 
308
+ # Make sure URL is set
309
+ if not self.url:
310
+ self.url = "https://api.terrak.io"
311
+
312
+ # Make sure session headers are updated with the new key
313
+ self.session.headers.update({
314
+ 'Content-Type': 'application/json',
315
+ 'x-api-key': self.key
316
+ })
317
+
318
+ # Set URL if not already set
319
+ if not self.url:
320
+ self.url = "https://api.terrak.io"
321
+ self.url = self.url.rstrip('/')
322
+
222
323
  # Save email and API key to config file
223
324
  import os
224
325
  import json
@@ -230,6 +331,7 @@ class BaseClient:
230
331
  config = json.load(f)
231
332
  config["EMAIL"] = email
232
333
  config["TERRAKIO_API_KEY"] = self.key
334
+ config["PERSONAL_TOKEN"] = token_response
233
335
 
234
336
  os.makedirs(os.path.dirname(config_path), exist_ok=True)
235
337
  with open(config_path, 'w') as f:
@@ -237,6 +339,7 @@ class BaseClient:
237
339
 
238
340
  if not self.quiet:
239
341
  print(f"Successfully authenticated as: {email}")
342
+ print(f"Using Terrakio API at: {self.url}")
240
343
  print(f"API key saved to {config_path}")
241
344
  except Exception as e:
242
345
  if not self.quiet:
@@ -248,47 +351,81 @@ class BaseClient:
248
351
  print(f"Login failed: {str(e)}")
249
352
  raise
250
353
 
354
+ @require_api_key
251
355
  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}")
356
+ # If we have auth_client and it has a token, refresh the key
357
+ if self.auth_client and self.auth_client.token:
358
+ self.key = self.auth_client.refresh_api_key()
359
+ self.session.headers.update({'x-api-key': self.key})
360
+
361
+ # Update the config file with the new key
362
+ import os
363
+ config_path = os.path.join(os.environ.get("HOME", ""), ".tkio_config.json")
364
+ try:
365
+ config = {"EMAIL": "", "TERRAKIO_API_KEY": ""}
366
+ if os.path.exists(config_path):
367
+ with open(config_path, 'r') as f:
368
+ config = json.load(f)
369
+ config["TERRAKIO_API_KEY"] = self.key
370
+ os.makedirs(os.path.dirname(config_path), exist_ok=True)
371
+ with open(config_path, 'w') as f:
372
+ json.dump(config, f, indent=4)
373
+ if not self.quiet:
374
+ print(f"API key generated successfully and updated in {config_path}")
375
+ except Exception as e:
376
+ if not self.quiet:
377
+ print(f"Warning: Failed to update config file: {e}")
378
+ return self.key
379
+ else:
380
+ # If we don't have auth_client with a token but have a key already, return it
381
+ if self.key:
382
+ if not self.quiet:
383
+ print("Using existing API key from config file.")
384
+ return self.key
385
+ else:
386
+ raise ConfigurationError("No authentication token available. Please login first to refresh the API key.")
387
+
388
+ @require_api_key
389
+ def view_api_key(self) -> str:
390
+ # If we have the auth client and token, refresh the key
391
+ if self.auth_client and self.auth_client.token:
392
+ self.key = self.auth_client.view_api_key()
393
+ self.session.headers.update({'x-api-key': self.key})
394
+
274
395
  return self.key
275
396
 
397
+ @require_api_key
276
398
  def view_api_key(self) -> str:
399
+ # If we have the auth client and token, refresh the key
277
400
  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
401
+ from terrakio_core.auth import AuthClient
402
+ self.auth_client = AuthClient(
403
+ base_url=self.url,
404
+ verify=self.verify,
405
+ timeout=self.timeout
406
+ )
407
+ self.auth_client.session = self.session
408
+ return self.auth_client.view_api_key()
284
409
 
410
+ # @require_api_key
411
+ # def get_user_info(self) -> Dict[str, Any]:
412
+ # return self.auth_client.get_user_info()
413
+ @require_api_key
285
414
  def get_user_info(self) -> Dict[str, Any]:
415
+ # Initialize auth_client if it doesn't exist
286
416
  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.")
417
+ from terrakio_core.auth import AuthClient
418
+ self.auth_client = AuthClient(
419
+ base_url=self.url,
420
+ verify=self.verify,
421
+ timeout=self.timeout
422
+ )
423
+ # Use the same session as the base client
424
+ self.auth_client.session = self.session
425
+
290
426
  return self.auth_client.get_user_info()
291
427
 
428
+ @require_api_key
292
429
  def wcs(self, expr: str, feature: Union[Dict[str, Any], ShapelyGeometry], in_crs: str = "epsg:4326",
293
430
  out_crs: str = "epsg:4326", output: str = "csv", resolution: int = -1,
294
431
  **kwargs):
@@ -341,6 +478,7 @@ class BaseClient:
341
478
  raise APIError(f"Request failed: {str(e)}")
342
479
 
343
480
  # Admin/protected methods
481
+ @require_api_key
344
482
  def _get_user_by_id(self, user_id: str):
345
483
  if not self.user_management:
346
484
  from terrakio_core.user_management import UserManagement
@@ -354,6 +492,7 @@ class BaseClient:
354
492
  )
355
493
  return self.user_management.get_user_by_id(user_id)
356
494
 
495
+ @require_api_key
357
496
  def _get_user_by_email(self, email: str):
358
497
  if not self.user_management:
359
498
  from terrakio_core.user_management import UserManagement
@@ -367,6 +506,7 @@ class BaseClient:
367
506
  )
368
507
  return self.user_management.get_user_by_email(email)
369
508
 
509
+ @require_api_key
370
510
  def _list_users(self, substring: str = None, uid: bool = False):
371
511
  if not self.user_management:
372
512
  from terrakio_core.user_management import UserManagement
@@ -380,6 +520,7 @@ class BaseClient:
380
520
  )
381
521
  return self.user_management.list_users(substring=substring, uid=uid)
382
522
 
523
+ @require_api_key
383
524
  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
525
  if not self.user_management:
385
526
  from terrakio_core.user_management import UserManagement
@@ -401,6 +542,7 @@ class BaseClient:
401
542
  quota=quota
402
543
  )
403
544
 
545
+ @require_api_key
404
546
  def _reset_quota(self, email: str, quota: int = None):
405
547
  if not self.user_management:
406
548
  from terrakio_core.user_management import UserManagement
@@ -414,6 +556,7 @@ class BaseClient:
414
556
  )
415
557
  return self.user_management.reset_quota(email=email, quota=quota)
416
558
 
559
+ @require_api_key
417
560
  def _delete_user(self, uid: str):
418
561
  if not self.user_management:
419
562
  from terrakio_core.user_management import UserManagement
@@ -428,6 +571,7 @@ class BaseClient:
428
571
  return self.user_management.delete_user(uid=uid)
429
572
 
430
573
  # Dataset management protected methods
574
+ @require_api_key
431
575
  def _get_dataset(self, name: str, collection: str = "terrakio-datasets"):
432
576
  if not self.dataset_management:
433
577
  from terrakio_core.dataset_management import DatasetManagement
@@ -441,6 +585,7 @@ class BaseClient:
441
585
  )
442
586
  return self.dataset_management.get_dataset(name=name, collection=collection)
443
587
 
588
+ @require_api_key
444
589
  def _list_datasets(self, substring: str = None, collection: str = "terrakio-datasets"):
445
590
  if not self.dataset_management:
446
591
  from terrakio_core.dataset_management import DatasetManagement
@@ -454,6 +599,7 @@ class BaseClient:
454
599
  )
455
600
  return self.dataset_management.list_datasets(substring=substring, collection=collection)
456
601
 
602
+ @require_api_key
457
603
  def _create_dataset(self, name: str, collection: str = "terrakio-datasets", **kwargs):
458
604
  if not self.dataset_management:
459
605
  from terrakio_core.dataset_management import DatasetManagement
@@ -467,6 +613,7 @@ class BaseClient:
467
613
  )
468
614
  return self.dataset_management.create_dataset(name=name, collection=collection, **kwargs)
469
615
 
616
+ @require_api_key
470
617
  def _update_dataset(self, name: str, append: bool = True, collection: str = "terrakio-datasets", **kwargs):
471
618
  if not self.dataset_management:
472
619
  from terrakio_core.dataset_management import DatasetManagement
@@ -480,6 +627,7 @@ class BaseClient:
480
627
  )
481
628
  return self.dataset_management.update_dataset(name=name, append=append, collection=collection, **kwargs)
482
629
 
630
+ @require_api_key
483
631
  def _overwrite_dataset(self, name: str, collection: str = "terrakio-datasets", **kwargs):
484
632
  if not self.dataset_management:
485
633
  from terrakio_core.dataset_management import DatasetManagement
@@ -493,6 +641,7 @@ class BaseClient:
493
641
  )
494
642
  return self.dataset_management.overwrite_dataset(name=name, collection=collection, **kwargs)
495
643
 
644
+ @require_api_key
496
645
  def _delete_dataset(self, name: str, collection: str = "terrakio-datasets"):
497
646
  if not self.dataset_management:
498
647
  from terrakio_core.dataset_management import DatasetManagement
@@ -506,6 +655,7 @@ class BaseClient:
506
655
  )
507
656
  return self.dataset_management.delete_dataset(name=name, collection=collection)
508
657
 
658
+ @require_api_key
509
659
  def close(self):
510
660
  """Close all client sessions"""
511
661
  self.session.close()
@@ -532,13 +682,16 @@ class BaseClient:
532
682
  # Event loop may already be closed, ignore
533
683
  pass
534
684
 
685
+ @require_api_key
535
686
  def __enter__(self):
536
687
  return self
537
688
 
689
+ @require_api_key
538
690
  def __exit__(self, exc_type, exc_val, exc_tb):
539
691
  self.close()
540
692
 
541
693
  @admin_only_params('location', 'force_loc', 'server')
694
+ @require_api_key
542
695
  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
696
  if not self.mass_stats:
544
697
  from terrakio_core.mass_stats import MassStats
@@ -553,6 +706,7 @@ class BaseClient:
553
706
  return self.mass_stats.execute_job(name, region, output, config, overwrite, skip_existing, request_json, manifest_json, location, force_loc, server)
554
707
 
555
708
 
709
+ @require_api_key
556
710
  def get_mass_stats_task_id(self, name, stage, uid=None):
557
711
  if not self.mass_stats:
558
712
  from terrakio_core.mass_stats import MassStats
@@ -566,6 +720,7 @@ class BaseClient:
566
720
  )
567
721
  return self.mass_stats.get_task_id(name, stage, uid)
568
722
 
723
+ @require_api_key
569
724
  def track_mass_stats_job(self, ids: Optional[list] = None):
570
725
  if not self.mass_stats:
571
726
  from terrakio_core.mass_stats import MassStats
@@ -579,6 +734,7 @@ class BaseClient:
579
734
  )
580
735
  return self.mass_stats.track_job(ids)
581
736
 
737
+ @require_api_key
582
738
  def get_mass_stats_history(self, limit=100):
583
739
  if not self.mass_stats:
584
740
  from terrakio_core.mass_stats import MassStats
@@ -592,6 +748,7 @@ class BaseClient:
592
748
  )
593
749
  return self.mass_stats.get_history(limit)
594
750
 
751
+ @require_api_key
595
752
  def start_mass_stats_post_processing(self, process_name, data_name, output, consumer_path, overwrite=False):
596
753
  if not self.mass_stats:
597
754
  from terrakio_core.mass_stats import MassStats
@@ -605,6 +762,7 @@ class BaseClient:
605
762
  )
606
763
  return self.mass_stats.start_post_processing(process_name, data_name, output, consumer_path, overwrite)
607
764
 
765
+ @require_api_key
608
766
  def download_mass_stats_results(self, id=None, force_loc=False, **kwargs):
609
767
  if not self.mass_stats:
610
768
  from terrakio_core.mass_stats import MassStats
@@ -618,6 +776,7 @@ class BaseClient:
618
776
  )
619
777
  return self.mass_stats.download_results(id, force_loc, **kwargs)
620
778
 
779
+ @require_api_key
621
780
  def cancel_mass_stats_job(self, id):
622
781
  if not self.mass_stats:
623
782
  from terrakio_core.mass_stats import MassStats
@@ -631,6 +790,7 @@ class BaseClient:
631
790
  )
632
791
  return self.mass_stats.cancel_job(id)
633
792
 
793
+ @require_api_key
634
794
  def cancel_all_mass_stats_jobs(self):
635
795
  if not self.mass_stats:
636
796
  from terrakio_core.mass_stats import MassStats
@@ -644,6 +804,7 @@ class BaseClient:
644
804
  )
645
805
  return self.mass_stats.cancel_all_jobs()
646
806
 
807
+ @require_api_key
647
808
  def _create_pyramids(self, name, levels, config):
648
809
  if not self.mass_stats:
649
810
  from terrakio_core.mass_stats import MassStats
@@ -657,6 +818,7 @@ class BaseClient:
657
818
  )
658
819
  return self.mass_stats.create_pyramids(name, levels, config)
659
820
 
821
+ @require_api_key
660
822
  def random_sample(self, name, **kwargs):
661
823
  if not self.mass_stats:
662
824
  from terrakio_core.mass_stats import MassStats
@@ -670,6 +832,7 @@ class BaseClient:
670
832
  )
671
833
  return self.mass_stats.random_sample(name, **kwargs)
672
834
 
835
+ @require_api_key
673
836
  async def zonal_stats_async(self, gdb, expr, conc=20, inplace=False, output="csv",
674
837
  in_crs="epsg:4326", out_crs="epsg:4326", resolution=-1, buffer=False):
675
838
  """
@@ -858,6 +1021,7 @@ class BaseClient:
858
1021
  else:
859
1022
  return result_gdf
860
1023
 
1024
+ @require_api_key
861
1025
  def zonal_stats(self, gdb, expr, conc=20, inplace=False, output="csv",
862
1026
  in_crs="epsg:4326", out_crs="epsg:4326", resolution=-1, buffer=False):
863
1027
  """
@@ -920,75 +1084,80 @@ class BaseClient:
920
1084
  return result
921
1085
 
922
1086
  # Group access management protected methods
1087
+ @require_api_key
923
1088
  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:
1089
+ if not self.group_access:
925
1090
  from terrakio_core.group_access_management import GroupAccessManagement
926
1091
  if not self.url or not self.key:
927
1092
  raise ConfigurationError("Group access management client not initialized. Make sure API URL and key are set.")
928
- self.group_access_management = GroupAccessManagement(
1093
+ self.group_access = GroupAccessManagement(
929
1094
  api_url=self.url,
930
1095
  api_key=self.key,
931
1096
  verify=self.verify,
932
1097
  timeout=self.timeout
933
1098
  )
934
- return self.group_access_management.get_group_users_and_datasets(group_name)
1099
+ return self.group_access.get_group_users_and_datasets(group_name=group_name)
935
1100
 
1101
+ @require_api_key
936
1102
  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:
1103
+ if not self.group_access:
938
1104
  from terrakio_core.group_access_management import GroupAccessManagement
939
1105
  if not self.url or not self.key:
940
1106
  raise ConfigurationError("Group access management client not initialized. Make sure API URL and key are set.")
941
- self.group_access_management = GroupAccessManagement(
1107
+ self.group_access = GroupAccessManagement(
942
1108
  api_url=self.url,
943
1109
  api_key=self.key,
944
1110
  verify=self.verify,
945
1111
  timeout=self.timeout
946
1112
  )
947
- return self.group_access_management.add_group_to_dataset(dataset, group)
1113
+ return self.group_access.add_group_to_dataset(dataset=dataset, group=group)
948
1114
 
1115
+ @require_api_key
949
1116
  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:
1117
+ if not self.group_access:
951
1118
  from terrakio_core.group_access_management import GroupAccessManagement
952
1119
  if not self.url or not self.key:
953
1120
  raise ConfigurationError("Group access management client not initialized. Make sure API URL and key are set.")
954
- self.group_access_management = GroupAccessManagement(
1121
+ self.group_access = GroupAccessManagement(
955
1122
  api_url=self.url,
956
1123
  api_key=self.key,
957
1124
  verify=self.verify,
958
1125
  timeout=self.timeout
959
1126
  )
960
- print("the uid is and the group is ", uid, group)
961
- return self.group_access_management.add_group_to_user(uid, group)
1127
+ return self.group_access.add_group_to_user(uid=uid, group=group)
962
1128
 
1129
+ @require_api_key
963
1130
  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:
1131
+ if not self.group_access:
965
1132
  from terrakio_core.group_access_management import GroupAccessManagement
966
1133
  if not self.url or not self.key:
967
1134
  raise ConfigurationError("Group access management client not initialized. Make sure API URL and key are set.")
968
- self.group_access_management = GroupAccessManagement(
1135
+ self.group_access = GroupAccessManagement(
969
1136
  api_url=self.url,
970
1137
  api_key=self.key,
971
1138
  verify=self.verify,
972
1139
  timeout=self.timeout
973
1140
  )
974
- return self.group_access_management.delete_group_from_user(uid, group)
1141
+ return self.group_access.delete_group_from_user(uid=uid, group=group)
975
1142
 
1143
+ @require_api_key
976
1144
  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:
1145
+ if not self.group_access:
978
1146
  from terrakio_core.group_access_management import GroupAccessManagement
979
1147
  if not self.url or not self.key:
980
1148
  raise ConfigurationError("Group access management client not initialized. Make sure API URL and key are set.")
981
- self.group_access_management = GroupAccessManagement(
1149
+ self.group_access = GroupAccessManagement(
982
1150
  api_url=self.url,
983
1151
  api_key=self.key,
984
1152
  verify=self.verify,
985
1153
  timeout=self.timeout
986
1154
  )
987
- return self.group_access_management.delete_group_from_dataset(dataset, group)
1155
+ return self.group_access.delete_group_from_dataset(dataset=dataset, group=group)
988
1156
 
989
1157
  # Space management protected methods
1158
+ @require_api_key
990
1159
  def _get_total_space_used(self):
991
- if not hasattr(self, "space_management") or self.space_management is None:
1160
+ if not self.space_management:
992
1161
  from terrakio_core.space_management import SpaceManagement
993
1162
  if not self.url or not self.key:
994
1163
  raise ConfigurationError("Space management client not initialized. Make sure API URL and key are set.")
@@ -1000,8 +1169,9 @@ class BaseClient:
1000
1169
  )
1001
1170
  return self.space_management.get_total_space_used()
1002
1171
 
1172
+ @require_api_key
1003
1173
  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:
1174
+ if not self.space_management:
1005
1175
  from terrakio_core.space_management import SpaceManagement
1006
1176
  if not self.url or not self.key:
1007
1177
  raise ConfigurationError("Space management client not initialized. Make sure API URL and key are set.")
@@ -1013,8 +1183,9 @@ class BaseClient:
1013
1183
  )
1014
1184
  return self.space_management.get_space_used_by_job(name, region)
1015
1185
 
1186
+ @require_api_key
1016
1187
  def _delete_user_job(self, name: str, region: str = None):
1017
- if not hasattr(self, "space_management") or self.space_management is None:
1188
+ if not self.space_management:
1018
1189
  from terrakio_core.space_management import SpaceManagement
1019
1190
  if not self.url or not self.key:
1020
1191
  raise ConfigurationError("Space management client not initialized. Make sure API URL and key are set.")
@@ -1026,8 +1197,9 @@ class BaseClient:
1026
1197
  )
1027
1198
  return self.space_management.delete_user_job(name, region)
1028
1199
 
1200
+ @require_api_key
1029
1201
  def _delete_data_in_path(self, path: str, region: str = None):
1030
- if not hasattr(self, "space_management") or self.space_management is None:
1202
+ if not self.space_management:
1031
1203
  from terrakio_core.space_management import SpaceManagement
1032
1204
  if not self.url or not self.key:
1033
1205
  raise ConfigurationError("Space management client not initialized. Make sure API URL and key are set.")
@@ -1039,6 +1211,7 @@ class BaseClient:
1039
1211
  )
1040
1212
  return self.space_management.delete_data_in_path(path, region)
1041
1213
 
1214
+ @require_api_key
1042
1215
  def start_mass_stats_job(self, task_id):
1043
1216
  if not self.mass_stats:
1044
1217
  from terrakio_core.mass_stats import MassStats
@@ -1053,6 +1226,7 @@ class BaseClient:
1053
1226
  return self.mass_stats.start_job(task_id)
1054
1227
 
1055
1228
 
1229
+ @require_api_key
1056
1230
  def generate_ai_dataset(
1057
1231
  self,
1058
1232
  name: str,
@@ -1147,6 +1321,7 @@ class BaseClient:
1147
1321
  # print("the task id is ", task_id)
1148
1322
  return task_id
1149
1323
 
1324
+ @require_api_key
1150
1325
  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
1326
  """
1152
1327
  Train a model using the external model training API.
@@ -1191,6 +1366,7 @@ class BaseClient:
1191
1366
  raise APIError(f"Model training request failed: {str(e)}")
1192
1367
 
1193
1368
  # Mass Stats methods
1369
+ @require_api_key
1194
1370
  def combine_tiles(self,
1195
1371
  data_name: str,
1196
1372
  usezarr: bool,
@@ -1211,6 +1387,7 @@ class BaseClient:
1211
1387
 
1212
1388
 
1213
1389
 
1390
+ @require_api_key
1214
1391
  def create_dataset_file(
1215
1392
  self,
1216
1393
  name: str,
@@ -1226,9 +1403,22 @@ class BaseClient:
1226
1403
  skip_existing: bool = False,
1227
1404
  non_interactive: bool = True,
1228
1405
  usezarr: bool = False,
1229
- poll_interval: int = 30 # seconds between job status checks
1406
+ poll_interval: int = 30,
1407
+ download_path: str = "/home/user/Downloads",
1230
1408
  ) -> dict:
1231
1409
 
1410
+ if not self.mass_stats:
1411
+ from terrakio_core.mass_stats import MassStats
1412
+ if not self.url or not self.key:
1413
+ raise ConfigurationError("Mass Stats client not initialized. Make sure API URL and key are set.")
1414
+ self.mass_stats = MassStats(
1415
+ base_url=self.url,
1416
+ api_key=self.key,
1417
+ verify=self.verify,
1418
+ timeout=self.timeout
1419
+ )
1420
+
1421
+
1232
1422
  from terrakio_core.generation.tiles import tiles
1233
1423
  import tempfile
1234
1424
  import time
@@ -1317,26 +1507,60 @@ class BaseClient:
1317
1507
  os.unlink(tempmanifestname)
1318
1508
 
1319
1509
 
1510
+ # return self.mass_stats.combine_tiles(body["name"], usezarr, body["overwrite"], body["output"])
1511
+
1320
1512
  # 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"])
1513
+ combine_result = self.mass_stats.combine_tiles(body["name"], usezarr, body["overwrite"], body["output"])
1514
+ combine_task_id = combine_result.get("task_id")
1515
+
1516
+ # Poll combine_tiles job status
1517
+ combine_start_time = time.time()
1518
+ while True:
1519
+ try:
1520
+ trackinfo = self.mass_stats.track_job([combine_task_id])
1521
+ download_file_name = trackinfo[combine_task_id]['folder'] + '.nc'
1522
+ bucket = trackinfo[combine_task_id]['bucket']
1523
+ combine_status = trackinfo[combine_task_id]['status']
1524
+ if combine_status == 'Completed':
1525
+ print('Tiles combined successfully!')
1526
+ break
1527
+ elif combine_status in ['Failed', 'Cancelled', 'Error']:
1528
+ raise RuntimeError(f"Combine job {combine_task_id} failed with status: {combine_status}")
1529
+ else:
1530
+ elapsed_time = time.time() - combine_start_time
1531
+ print(f"Combine job status: {combine_status} - Elapsed time: {elapsed_time:.1f}s", end='\r')
1532
+ time.sleep(poll_interval)
1533
+ except KeyboardInterrupt:
1534
+ print(f"\nInterrupted! Combine job {combine_task_id} is still running in the background.")
1535
+ raise
1536
+ except Exception as e:
1537
+ print(f"\nError tracking combine job: {e}")
1538
+ raise
1539
+
1540
+ # Download the resulting file
1541
+ if download_path:
1542
+ self.mass_stats.download_file(body["name"], bucket, download_file_name, download_path)
1543
+ else:
1544
+ path = f"{body['name']}/combinedOutput/{download_file_name}"
1545
+ print(f"Combined file is available at {path}")
1546
+
1547
+ return {"generation_task_id": task_id, "combine_task_id": combine_task_id}
1548
+
1549
+
1550
+ # taskid = self.mass_stats.get_task_id(job_name, stage).get('task_id')
1551
+ # trackinfo = self.mass_stats.track_job([taskid])
1552
+ # bucket = trackinfo[taskid]['bucket']
1553
+ # return self.mass_stats.download_file(job_name, bucket, file_name, output_path)
1333
1554
 
1555
+
1556
+ @require_api_key
1334
1557
  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
1558
  script_content = self._generate_script(model_name, product, model_training_job_name, uid)
1336
1559
  script_name = f"{product}.py"
1337
1560
  self._upload_script_to_bucket(script_content, script_name, model_training_job_name, uid)
1338
1561
  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
1562
 
1563
+ @require_api_key
1340
1564
  def _generate_script(self, model_name: str, product: str, model_training_job_name: str, uid: str) -> str:
1341
1565
  return textwrap.dedent(f'''
1342
1566
  import logging
@@ -1443,6 +1667,7 @@ class BaseClient:
1443
1667
  return result
1444
1668
  ''').strip()
1445
1669
 
1670
+ @require_api_key
1446
1671
  def _upload_script_to_bucket(self, script_content: str, script_name: str, model_training_job_name: str, uid: str):
1447
1672
  """Upload the generated script to Google Cloud Storage"""
1448
1673
 
@@ -1455,6 +1680,7 @@ class BaseClient:
1455
1680
 
1456
1681
 
1457
1682
 
1683
+ @require_api_key
1458
1684
  def download_file_to_path(self, job_name, stage, file_name, output_path):
1459
1685
  if not self.mass_stats:
1460
1686
  from terrakio_core.mass_stats import MassStats
@@ -1472,4 +1698,34 @@ class BaseClient:
1472
1698
  taskid = self.mass_stats.get_task_id(job_name, stage).get('task_id')
1473
1699
  trackinfo = self.mass_stats.track_job([taskid])
1474
1700
  bucket = trackinfo[taskid]['bucket']
1475
- return self.mass_stats.download_file(job_name, bucket, file_name, output_path)
1701
+ return self.mass_stats.download_file(job_name, bucket, file_name, output_path)
1702
+
1703
+
1704
+ # Apply the @require_api_key decorator to ALL methods in BaseClient
1705
+ # except only the absolute minimum that shouldn't require auth
1706
+ def _apply_api_key_decorator():
1707
+ # Only these methods can be used without API key
1708
+ skip_auth_methods = [
1709
+ '__init__', 'login', 'signup'
1710
+ ]
1711
+
1712
+ # Get all attributes of BaseClient
1713
+ for attr_name in dir(BaseClient):
1714
+ # Skip special methods and methods in skip list
1715
+ if attr_name.startswith('__') and attr_name not in ['__enter__', '__exit__', '__aenter__', '__aexit__'] or attr_name in skip_auth_methods:
1716
+ continue
1717
+
1718
+ # Get the attribute
1719
+ attr = getattr(BaseClient, attr_name)
1720
+
1721
+ # Skip if not callable (not a method) or already decorated
1722
+ if not callable(attr) or hasattr(attr, '_is_decorated'):
1723
+ continue
1724
+
1725
+ # Apply decorator to EVERY method not in skip list
1726
+ setattr(BaseClient, attr_name, require_api_key(attr))
1727
+ # Mark as decorated to avoid double decoration
1728
+ getattr(BaseClient, attr_name)._is_decorated = True
1729
+
1730
+ # Run the decorator application
1731
+ _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.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
@@ -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=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
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.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,,