terrakio-api 0.1.0__tar.gz

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.
File without changes
@@ -0,0 +1,52 @@
1
+ Metadata-Version: 2.4
2
+ Name: terrakio_api
3
+ Version: 0.1.0
4
+ Summary: A client library for Terrakio's WCS API service
5
+ Home-page: https://github.com/HaizeaAnalytics/terrakio-python-api
6
+ Author: Yupeng Chao
7
+ Author-email: yupeng@haizea.com.au
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Programming Language :: Python :: 3.7
10
+ Classifier: Programming Language :: Python :: 3.8
11
+ Classifier: Programming Language :: Python :: 3.9
12
+ Classifier: Programming Language :: Python :: 3.10
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Development Status :: 4 - Beta
16
+ Classifier: Intended Audience :: Science/Research
17
+ Classifier: Topic :: Scientific/Engineering
18
+ Requires-Python: >=3.7
19
+ Description-Content-Type: text/markdown
20
+ License-File: LICENSE
21
+ Requires-Dist: requests>=2.25.0
22
+ Requires-Dist: pyyaml>=5.1
23
+ Requires-Dist: xarray>=2023.1.0
24
+ Requires-Dist: netcdf4>=1.6.0
25
+ Requires-Dist: pandas>=1.5.0
26
+ Requires-Dist: numpy>=1.22.0
27
+ Requires-Dist: scipy>=1.8.0
28
+ Provides-Extra: dev
29
+ Requires-Dist: pytest>=6.0.0; extra == "dev"
30
+ Requires-Dist: pytest-cov>=2.10.0; extra == "dev"
31
+ Requires-Dist: black>=20.8b1; extra == "dev"
32
+ Requires-Dist: flake8>=3.8.0; extra == "dev"
33
+ Dynamic: author
34
+ Dynamic: author-email
35
+ Dynamic: classifier
36
+ Dynamic: description
37
+ Dynamic: description-content-type
38
+ Dynamic: home-page
39
+ Dynamic: license-file
40
+ Dynamic: provides-extra
41
+ Dynamic: requires-dist
42
+ Dynamic: requires-python
43
+ Dynamic: summary
44
+
45
+ # Terrakio API Client
46
+
47
+ A Python client for Terrakio's Web Coverage Service (WCS) API.
48
+
49
+ ## Installation
50
+
51
+ ```bash
52
+ pip install terrakio-api
@@ -0,0 +1,8 @@
1
+ # Terrakio API Client
2
+
3
+ A Python client for Terrakio's Web Coverage Service (WCS) API.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pip install terrakio-api
@@ -0,0 +1,3 @@
1
+ [build-system]
2
+ requires = ["setuptools>=42", "wheel"]
3
+ build-backend = "setuptools.build_meta"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,48 @@
1
+ from setuptools import setup, find_packages
2
+
3
+ with open("README.md", "r") as fh:
4
+ long_description = fh.read()
5
+
6
+ setup(
7
+ name="terrakio_api",
8
+ version="0.1.0",
9
+ author="Yupeng Chao",
10
+ author_email="yupeng@haizea.com.au",
11
+ description="A client library for Terrakio's WCS API service",
12
+ long_description=long_description,
13
+ long_description_content_type="text/markdown",
14
+ url="https://github.com/HaizeaAnalytics/terrakio-python-api",
15
+ # packages=find_packages(),
16
+ packages=["terrakio_api", "terrakio_api.utils"], # Explicitly list packages instead of find_packages()
17
+ classifiers=[
18
+ "Programming Language :: Python :: 3",
19
+ "Programming Language :: Python :: 3.7",
20
+ "Programming Language :: Python :: 3.8",
21
+ "Programming Language :: Python :: 3.9",
22
+ "Programming Language :: Python :: 3.10",
23
+ "License :: OSI Approved :: MIT License",
24
+ "Operating System :: OS Independent",
25
+ "Development Status :: 4 - Beta",
26
+ "Intended Audience :: Science/Research",
27
+ "Topic :: Scientific/Engineering",
28
+ ],
29
+ python_requires=">=3.7",
30
+ install_requires=[
31
+ "requests>=2.25.0",
32
+ "pyyaml>=5.1",
33
+ "xarray>=2023.1.0",
34
+ "netcdf4>=1.6.0",
35
+ "pandas>=1.5.0",
36
+ "numpy>=1.22.0",
37
+ "scipy>=1.8.0", # Added SciPy dependency
38
+ ],
39
+ extras_require={
40
+ "dev": [
41
+ "pytest>=6.0.0",
42
+ "pytest-cov>=2.10.0",
43
+ "black>=20.8b1",
44
+ "flake8>=3.8.0",
45
+ ],
46
+ },
47
+ metadata_version='2.2'
48
+ )
@@ -0,0 +1,20 @@
1
+ """
2
+ Terrakio API Client
3
+
4
+ A Python client for accessing Terrakio's Web Coverage Service (WCS) API.
5
+ """
6
+
7
+ __version__ = "0.1.0"
8
+
9
+ from .client import Client
10
+ from .config import create_default_config
11
+ from .exceptions import APIError, ConfigurationError, DownloadError, ValidationError
12
+
13
+ __all__ = [
14
+ 'Client',
15
+ 'create_default_config',
16
+ 'APIError',
17
+ 'ConfigurationError',
18
+ 'DownloadError',
19
+ 'ValidationError',
20
+ ]
@@ -0,0 +1,48 @@
1
+ import requests
2
+ import json
3
+ from typing import Dict, Any, Tuple, Optional
4
+
5
+ from .exceptions import APIError
6
+
7
+ def make_request(session: requests.Session, url: str, payload: Dict[str, Any],
8
+ timeout: int = 60, verify: bool = True):
9
+ """
10
+ Make an API request to the Terrakio API.
11
+
12
+ Args:
13
+ session: Requests session with authentication
14
+ url: API endpoint URL
15
+ payload: Request payload
16
+ timeout: Request timeout in seconds
17
+ verify: Verify SSL certificates
18
+
19
+ Returns:
20
+ Response object
21
+
22
+ Raises:
23
+ APIError: If the API returns an error
24
+ """
25
+ try:
26
+ response = session.post(
27
+ url,
28
+ json=payload,
29
+ timeout=timeout,
30
+ verify=verify
31
+ )
32
+
33
+ # Handle HTTP errors
34
+ if not response.ok:
35
+ error_msg = f"API request failed: {response.status_code} {response.reason}"
36
+ try:
37
+ error_data = response.json()
38
+ if "detail" in error_data:
39
+ error_msg += f" - {error_data['detail']}"
40
+ except:
41
+ pass
42
+
43
+ raise APIError(error_msg)
44
+
45
+ return response
46
+
47
+ except requests.RequestException as e:
48
+ raise APIError(f"Request failed: {str(e)}")
@@ -0,0 +1,158 @@
1
+ import requests
2
+ import xarray as xr
3
+ import time
4
+ from io import BytesIO
5
+ from pathlib import Path
6
+ from typing import Dict, Any, Optional, Union, BinaryIO
7
+
8
+ from .config import read_config_file, DEFAULT_CONFIG_FILE
9
+ from .exceptions import APIError, ConfigurationError, DownloadError
10
+ from .api import make_request
11
+ from .utils.validation import validate_feature
12
+
13
+ class Client:
14
+ def __init__(self, url: Optional[str] = None, key: Optional[str] = None,
15
+ quiet: bool = False, config_file: Optional[str] = None,
16
+ verify: bool = True, timeout: int = 60):
17
+ """
18
+ Initialize the Terrakio API client.
19
+
20
+ Args:
21
+ url: API base URL (optional, will use config file if not provided)
22
+ key: API key or token (optional, will use config file if not provided)
23
+ quiet: If True, suppress progress messages
24
+ config_file: Path to configuration file (default is ~/.terrakioapirc)
25
+ verify: Verify SSL certificates
26
+ timeout: Request timeout in seconds
27
+ """
28
+ self.quiet = quiet
29
+ self.verify = verify
30
+ self.timeout = timeout
31
+
32
+ # Try to get config from parameters first, then config file
33
+ if url is not None and key is not None:
34
+ self.url = url
35
+ self.key = key
36
+ else:
37
+ if config_file is None:
38
+ config_file = DEFAULT_CONFIG_FILE
39
+
40
+ try:
41
+ config = read_config_file(config_file)
42
+ self.url = config.get('url')
43
+ self.key = config.get('key')
44
+ except Exception as e:
45
+ raise ConfigurationError(
46
+ f"Failed to read configuration: {e}\n\n"
47
+ "To fix this issue:\n"
48
+ "1. Create a file at ~/.terrakioapirc with:\n"
49
+ "url: https://terrakio-server-candidate-d4w6vamyxq-ts.a.run.app/wcs_secure\n"
50
+ "key: your-api-key\n\n"
51
+ "OR\n\n"
52
+ "2. Initialize the client with explicit parameters:\n"
53
+ "client = terrakio_api.Client(\n"
54
+ " url='https://terrakio-server-candidate-d4w6vamyxq-ts.a.run.app/wcs_secure',\n"
55
+ " key='your-api-key'\n"
56
+ ")"
57
+ )
58
+
59
+ # Validate configuration
60
+ if not self.url:
61
+ raise ConfigurationError("Missing API URL in configuration")
62
+ if not self.key:
63
+ raise ConfigurationError("Missing API key in configuration")
64
+
65
+ # Ensure URL doesn't end with slash
66
+ self.url = self.url.rstrip('/')
67
+
68
+ if not self.quiet:
69
+ print(f"Using Terrakio API at: {self.url}")
70
+
71
+ # Initialize session for connection pooling
72
+ self.session = requests.Session()
73
+ self.session.headers.update({
74
+ 'Content-Type': 'application/json',
75
+ 'x-api-key': self.key
76
+ })
77
+
78
+ def wcs(self, expr: str, feature: Dict[str, Any], in_crs: str = "epsg:4326",
79
+ out_crs: str = "epsg:4326", output: str = "csv", resolution: int = -1,
80
+ **kwargs):
81
+ """
82
+ Make a WCS request to the Terrakio API.
83
+
84
+ Args:
85
+ expr: Expression string for data selection
86
+ feature: GeoJSON Feature dictionary containing geometry information
87
+ in_crs: Input coordinate reference system (default: "epsg:4326")
88
+ out_crs: Output coordinate reference system (default: "epsg:4326")
89
+ output: Output format (default: "csv")
90
+ resolution: Resolution value (default: -1)
91
+ **kwargs: Additional parameters to pass to the API
92
+
93
+ Returns:
94
+ Data in the requested format (xr.Dataset for netcdf, pd.DataFrame for csv)
95
+ """
96
+ # Validate the feature object
97
+ validate_feature(feature)
98
+
99
+ # Prepare the payload
100
+ payload = {
101
+ "feature": feature,
102
+ "in_crs": in_crs,
103
+ "out_crs": out_crs,
104
+ "output": output,
105
+ "resolution": resolution,
106
+ "expr": expr,
107
+ **kwargs
108
+ }
109
+
110
+ if not self.quiet:
111
+ print(f"Requesting data with expression: {expr}")
112
+
113
+ try:
114
+ # Make the API request
115
+ response = self.session.post(self.url, json=payload, timeout=self.timeout, verify=self.verify)
116
+
117
+ # Handle HTTP errors
118
+ if not response.ok:
119
+ error_msg = f"API request failed: {response.status_code} {response.reason}"
120
+ try:
121
+ error_data = response.json()
122
+ if "detail" in error_data:
123
+ error_msg += f" - {error_data['detail']}"
124
+ except:
125
+ pass
126
+
127
+ raise APIError(error_msg)
128
+
129
+ # Handle different output formats
130
+ if output.lower() == "csv":
131
+ import pandas as pd
132
+ return pd.read_csv(BytesIO(response.content))
133
+ elif output.lower() == "netcdf":
134
+ return xr.open_dataset(BytesIO(response.content))
135
+ else:
136
+ # Try to determine the format and use appropriate reader
137
+ try:
138
+ return xr.open_dataset(BytesIO(response.content))
139
+ except ValueError:
140
+ import pandas as pd
141
+ try:
142
+ return pd.read_csv(BytesIO(response.content))
143
+ except:
144
+ # If all else fails, return the raw content
145
+ return response.content
146
+
147
+ except requests.RequestException as e:
148
+ raise APIError(f"Request failed: {str(e)}")
149
+
150
+ def close(self):
151
+ """Close the client session."""
152
+ self.session.close()
153
+
154
+ def __enter__(self):
155
+ return self
156
+
157
+ def __exit__(self, exc_type, exc_val, exc_tb):
158
+ self.close()
@@ -0,0 +1,81 @@
1
+ import os
2
+ import yaml
3
+ from pathlib import Path
4
+ from typing import Dict, Any
5
+
6
+ from .exceptions import ConfigurationError
7
+
8
+ # Default configuration file location
9
+ DEFAULT_CONFIG_FILE = os.path.expanduser("~/.tkioapirc")
10
+
11
+ def read_config_file(config_file: str = DEFAULT_CONFIG_FILE) -> Dict[str, Any]:
12
+ """
13
+ Read and parse the configuration file.
14
+
15
+ Args:
16
+ config_file: Path to the configuration file
17
+
18
+ Returns:
19
+ Dict[str, Any]: Configuration parameters
20
+
21
+ Raises:
22
+ ConfigurationError: If the configuration file can't be read or parsed
23
+ """
24
+ config_path = Path(os.path.expanduser(config_file))
25
+
26
+ if not config_path.exists():
27
+ raise ConfigurationError(
28
+ f"Configuration file not found: {config_file}\n"
29
+ f"Please create a file at {config_file} with the following format:\n"
30
+ "url: https://terrakio-server-candidate-d4w6vamyxq-ts.a.run.app/wcs_secure\n"
31
+ "key: your-api-key-here"
32
+ )
33
+
34
+ try:
35
+ with open(config_path, 'r') as f:
36
+ content = f.read().strip()
37
+
38
+ # Support both YAML and simple key: value format
39
+ if ':' in content and '{' not in content:
40
+ # Simple key: value format (like CDSAPI uses)
41
+ config = {}
42
+ for line in content.split('\n'):
43
+ line = line.strip()
44
+ if line and not line.startswith('#'):
45
+ try:
46
+ key, value = [x.strip() for x in line.split(':', 1)]
47
+ config[key] = value
48
+ except ValueError:
49
+ # Skip lines that don't have a colon
50
+ pass
51
+ return config
52
+ else:
53
+ # YAML format for more complex configuration
54
+ return yaml.safe_load(content) or {}
55
+ except Exception as e:
56
+ raise ConfigurationError(f"Failed to parse configuration file: {e}")
57
+
58
+ def create_default_config(url: str, key: str, config_file: str = DEFAULT_CONFIG_FILE) -> None:
59
+ """
60
+ Create a default configuration file.
61
+
62
+ Args:
63
+ url: API base URL
64
+ key: API key
65
+ config_file: Path to configuration file
66
+
67
+ Raises:
68
+ ConfigurationError: If the configuration file can't be created
69
+ """
70
+ config_path = Path(os.path.expanduser(config_file))
71
+
72
+ # Ensure directory exists
73
+ config_path.parent.mkdir(parents=True, exist_ok=True)
74
+
75
+ try:
76
+ with open(config_path, 'w') as f:
77
+ f.write(f"url: {url}\n")
78
+ f.write(f"key: {key}\n")
79
+ f.write("# Configuration file for Terrakio API\n")
80
+ except Exception as e:
81
+ raise ConfigurationError(f"Failed to create configuration file: {e}")
@@ -0,0 +1,18 @@
1
+ class APIError(Exception):
2
+ """Exception raised for errors in the API responses."""
3
+ pass
4
+
5
+
6
+ class ConfigurationError(Exception):
7
+ """Exception raised for errors in the configuration."""
8
+ pass
9
+
10
+
11
+ class DownloadError(Exception):
12
+ """Exception raised for errors during data download."""
13
+ pass
14
+
15
+
16
+ class ValidationError(Exception):
17
+ """Exception raised for invalid request parameters."""
18
+ pass
@@ -0,0 +1,9 @@
1
+ """Utility functions for the Terrakio API client."""
2
+
3
+ from .validation import validate_feature, create_point_feature, create_polygon_feature
4
+
5
+ __all__ = [
6
+ 'validate_feature',
7
+ 'create_point_feature',
8
+ 'create_polygon_feature',
9
+ ]
@@ -0,0 +1,51 @@
1
+ import requests
2
+ from typing import Union, BinaryIO
3
+ from pathlib import Path
4
+
5
+ from ..exceptions import DownloadError
6
+
7
+ def download_file(url: str, target: Union[str, Path, BinaryIO],
8
+ session: requests.Session = None, chunk_size: int = 8192,
9
+ timeout: int = 60, verify: bool = True) -> None:
10
+ """
11
+ Download a file from a URL.
12
+
13
+ Args:
14
+ url: URL to download from
15
+ target: Path or file-like object to write to
16
+ session: Optional requests session to use
17
+ chunk_size: Size of chunks to download
18
+ timeout: Request timeout
19
+ verify: Verify SSL certificates
20
+
21
+ Raises:
22
+ DownloadError: If download fails
23
+ """
24
+ try:
25
+ # Use provided session or create a new one
26
+ if session is None:
27
+ _session = requests.Session()
28
+ else:
29
+ _session = session
30
+
31
+ # Stream the download
32
+ with _session.get(url, stream=True, timeout=timeout, verify=verify) as response:
33
+ if not response.ok:
34
+ raise DownloadError(f"Download failed: {response.status_code} {response.reason}")
35
+
36
+ # Handle different target types
37
+ if isinstance(target, (str, Path)):
38
+ with open(target, 'wb') as f:
39
+ for chunk in response.iter_content(chunk_size=chunk_size):
40
+ f.write(chunk)
41
+ else:
42
+ # Assume file-like object
43
+ for chunk in response.iter_content(chunk_size=chunk_size):
44
+ target.write(chunk)
45
+
46
+ except requests.RequestException as e:
47
+ raise DownloadError(f"Download failed: {str(e)}")
48
+ finally:
49
+ # Close the session if we created it
50
+ if session is None and '_session' in locals():
51
+ _session.close()
@@ -0,0 +1,85 @@
1
+ from typing import Dict, Any, List
2
+ from ..exceptions import ValidationError
3
+
4
+ def validate_feature(feature: Dict[str, Any]) -> None:
5
+ """
6
+ Validate a GeoJSON Feature object.
7
+
8
+ Args:
9
+ feature: GeoJSON Feature dictionary
10
+
11
+ Raises:
12
+ ValidationError: If the feature is invalid
13
+ """
14
+ if not isinstance(feature, dict):
15
+ raise ValidationError("Feature must be a dictionary")
16
+
17
+ if feature.get('type') != 'Feature':
18
+ raise ValidationError("Feature must have 'type' property set to 'Feature'")
19
+
20
+ if 'geometry' not in feature:
21
+ raise ValidationError("Feature must have a 'geometry' property")
22
+
23
+ geometry = feature['geometry']
24
+ if not isinstance(geometry, dict):
25
+ raise ValidationError("Feature geometry must be a dictionary")
26
+
27
+ if 'type' not in geometry:
28
+ raise ValidationError("Geometry must have a 'type' property")
29
+
30
+ if 'coordinates' not in geometry:
31
+ raise ValidationError("Geometry must have a 'coordinates' property")
32
+
33
+ # Add more specific validation based on geometry type if needed
34
+
35
+ def create_point_feature(lon: float, lat: float, properties: Dict[str, Any] = None) -> Dict[str, Any]:
36
+ """
37
+ Create a GeoJSON Point Feature object.
38
+
39
+ Args:
40
+ lon: Longitude coordinate
41
+ lat: Latitude coordinate
42
+ properties: Optional properties dictionary (default: empty dict)
43
+
44
+ Returns:
45
+ Dict: GeoJSON Feature object
46
+ """
47
+ if properties is None:
48
+ properties = {}
49
+
50
+ return {
51
+ "type": "Feature",
52
+ "properties": properties,
53
+ "geometry": {
54
+ "type": "Point",
55
+ "coordinates": [lon, lat]
56
+ }
57
+ }
58
+
59
+
60
+ def create_polygon_feature(coordinates: List[List[float]], properties: Dict[str, Any] = None) -> Dict[str, Any]:
61
+ """
62
+ Create a GeoJSON Polygon Feature object.
63
+
64
+ Args:
65
+ coordinates: List of [lon, lat] coordinate pairs forming a polygon (first and last must be identical)
66
+ properties: Optional properties dictionary (default: empty dict)
67
+
68
+ Returns:
69
+ Dict: GeoJSON Feature object
70
+ """
71
+ if properties is None:
72
+ properties = {}
73
+
74
+ # Check if polygon is closed
75
+ if coordinates[0] != coordinates[-1]:
76
+ coordinates.append(coordinates[0]) # Close the polygon
77
+
78
+ return {
79
+ "type": "Feature",
80
+ "properties": properties,
81
+ "geometry": {
82
+ "type": "Polygon",
83
+ "coordinates": [coordinates]
84
+ }
85
+ }
@@ -0,0 +1,52 @@
1
+ Metadata-Version: 2.4
2
+ Name: terrakio_api
3
+ Version: 0.1.0
4
+ Summary: A client library for Terrakio's WCS API service
5
+ Home-page: https://github.com/HaizeaAnalytics/terrakio-python-api
6
+ Author: Yupeng Chao
7
+ Author-email: yupeng@haizea.com.au
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Programming Language :: Python :: 3.7
10
+ Classifier: Programming Language :: Python :: 3.8
11
+ Classifier: Programming Language :: Python :: 3.9
12
+ Classifier: Programming Language :: Python :: 3.10
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Development Status :: 4 - Beta
16
+ Classifier: Intended Audience :: Science/Research
17
+ Classifier: Topic :: Scientific/Engineering
18
+ Requires-Python: >=3.7
19
+ Description-Content-Type: text/markdown
20
+ License-File: LICENSE
21
+ Requires-Dist: requests>=2.25.0
22
+ Requires-Dist: pyyaml>=5.1
23
+ Requires-Dist: xarray>=2023.1.0
24
+ Requires-Dist: netcdf4>=1.6.0
25
+ Requires-Dist: pandas>=1.5.0
26
+ Requires-Dist: numpy>=1.22.0
27
+ Requires-Dist: scipy>=1.8.0
28
+ Provides-Extra: dev
29
+ Requires-Dist: pytest>=6.0.0; extra == "dev"
30
+ Requires-Dist: pytest-cov>=2.10.0; extra == "dev"
31
+ Requires-Dist: black>=20.8b1; extra == "dev"
32
+ Requires-Dist: flake8>=3.8.0; extra == "dev"
33
+ Dynamic: author
34
+ Dynamic: author-email
35
+ Dynamic: classifier
36
+ Dynamic: description
37
+ Dynamic: description-content-type
38
+ Dynamic: home-page
39
+ Dynamic: license-file
40
+ Dynamic: provides-extra
41
+ Dynamic: requires-dist
42
+ Dynamic: requires-python
43
+ Dynamic: summary
44
+
45
+ # Terrakio API Client
46
+
47
+ A Python client for Terrakio's Web Coverage Service (WCS) API.
48
+
49
+ ## Installation
50
+
51
+ ```bash
52
+ pip install terrakio-api
@@ -0,0 +1,17 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ setup.py
5
+ terrakio_api/__init__.py
6
+ terrakio_api/api.py
7
+ terrakio_api/client.py
8
+ terrakio_api/config.py
9
+ terrakio_api/exceptions.py
10
+ terrakio_api.egg-info/PKG-INFO
11
+ terrakio_api.egg-info/SOURCES.txt
12
+ terrakio_api.egg-info/dependency_links.txt
13
+ terrakio_api.egg-info/requires.txt
14
+ terrakio_api.egg-info/top_level.txt
15
+ terrakio_api/utils/__init__.py
16
+ terrakio_api/utils/download.py
17
+ terrakio_api/utils/validation.py
@@ -0,0 +1,13 @@
1
+ requests>=2.25.0
2
+ pyyaml>=5.1
3
+ xarray>=2023.1.0
4
+ netcdf4>=1.6.0
5
+ pandas>=1.5.0
6
+ numpy>=1.22.0
7
+ scipy>=1.8.0
8
+
9
+ [dev]
10
+ pytest>=6.0.0
11
+ pytest-cov>=2.10.0
12
+ black>=20.8b1
13
+ flake8>=3.8.0
@@ -0,0 +1 @@
1
+ terrakio_api