perd 0.0.7a3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,26 @@
1
+ """PER Datasets - A module for loading reservoir datasets."""
2
+
3
+ __version__ = "0.0.7-alpha-3"
4
+
5
+ from .talkaholic.reservoir import Reservoir
6
+ from .reservoir import load_random, load
7
+ from .utils.init import initialize
8
+ from .workflow import workflow, WorkflowStreamInput, WorkflowStreamOutput
9
+ from . import utils
10
+ from .visual import Visualizer
11
+
12
+ # Instantiate the visualizer as a singleton object for generic access.
13
+ visual = Visualizer()
14
+
15
+ __all__ = [
16
+ "load_random",
17
+ "load",
18
+ "Reservoir",
19
+ "__version__",
20
+ "initialize",
21
+ "workflow",
22
+ "WorkflowStreamInput",
23
+ "WorkflowStreamOutput",
24
+ "visual",
25
+ "utils",
26
+ ]
per_datasets/cli.py ADDED
@@ -0,0 +1,197 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Command-line interface for managing perd API keys
4
+ """
5
+
6
+ import os
7
+ import json
8
+ import sys
9
+ import argparse
10
+ import getpass
11
+ from pathlib import Path
12
+ from typing import Optional, Dict, Any
13
+
14
+ def get_config_file() -> Path:
15
+ """Get the path to the configuration file"""
16
+ # Use user's home directory for cross-platform compatibility
17
+ home = Path.home()
18
+ config_dir = home / ".per_datasets"
19
+ config_dir.mkdir(exist_ok=True)
20
+ return config_dir / "config.json"
21
+
22
+ def load_config() -> Dict[str, Any]:
23
+ """Load configuration from file"""
24
+ config_file = get_config_file()
25
+ if config_file.exists():
26
+ try:
27
+ with open(config_file, 'r') as f:
28
+ return json.load(f)
29
+ except (json.JSONDecodeError, IOError):
30
+ return {}
31
+ return {}
32
+
33
+ def save_config(config: Dict[str, Any]) -> bool:
34
+ """Save configuration to file"""
35
+ config_file = get_config_file()
36
+ try:
37
+ with open(config_file, 'w') as f:
38
+ json.dump(config, f, indent=2)
39
+ return True
40
+ except IOError:
41
+ return False
42
+
43
+ def set_api_key(api_key: str) -> bool:
44
+ """Set the global API key"""
45
+ config = load_config()
46
+ config['api_key'] = api_key.strip()
47
+
48
+ if save_config(config):
49
+ print("✅ API key stored successfully!")
50
+ print(f"🔑 API Key: pk...{api_key[-4:] if len(api_key) > 4 else api_key}")
51
+ return True
52
+ else:
53
+ print("❌ Failed to save API key")
54
+ return False
55
+
56
+ def get_api_key() -> Optional[str]:
57
+ """Get the stored API key"""
58
+ config = load_config()
59
+ return config.get('api_key')
60
+
61
+
62
+ def remove_api_key() -> bool:
63
+ """Remove the stored API key"""
64
+ config = load_config()
65
+ if 'api_key' in config:
66
+ del config['api_key']
67
+ if save_config(config):
68
+ print("✅ API key removed successfully!")
69
+ return True
70
+ else:
71
+ print("❌ Failed to remove API key")
72
+ return False
73
+ else:
74
+ print("⚠️ No API key found to remove")
75
+ return False
76
+
77
+ def show_status() -> None:
78
+ """Show current configuration status"""
79
+ config = load_config()
80
+
81
+ if 'api_key' in config:
82
+ api_key = config['api_key']
83
+ print("🔑 API Key Status: CONFIGURED")
84
+ print(f" Key: pk...{api_key[-4:] if len(api_key) > 4 else api_key}")
85
+ print(f" Config File: {get_config_file()}")
86
+
87
+ # Test the API key
88
+ try:
89
+ import requests
90
+ headers = {'X-API-Key': api_key}
91
+ response = requests.get('https://perd-server.onrender.com/datasets', headers=headers, timeout=5)
92
+ if response.status_code == 200:
93
+ print("🌐 API Status: ACTIVE")
94
+ else:
95
+ print(f"🌐 API Status: INACTIVE (HTTP {response.status_code})")
96
+ except Exception as e:
97
+ print(f"🌐 API Status: ERROR ({type(e).__name__})")
98
+ else:
99
+ print("🔑 API Key Status: NOT CONFIGURED")
100
+ print(f" Config File: {get_config_file()}")
101
+ print(" Use 'perd set-key <api_key>' to configure")
102
+
103
+ def clear_config() -> bool:
104
+ """Clear all configuration"""
105
+ config_file = get_config_file()
106
+ if config_file.exists():
107
+ try:
108
+ config_file.unlink()
109
+ print("✅ Configuration cleared successfully!")
110
+ return True
111
+ except IOError:
112
+ print("❌ Failed to clear configuration")
113
+ return False
114
+ else:
115
+ print("⚠️ No configuration found to clear")
116
+ return False
117
+
118
+ def main():
119
+ parser = argparse.ArgumentParser(
120
+ description="Manage perd API keys globally",
121
+ prog="perd"
122
+ )
123
+
124
+ subparsers = parser.add_subparsers(dest='command', help='Available commands')
125
+
126
+ # Set API key command
127
+ set_parser = subparsers.add_parser('set-key', help='Set the API key')
128
+ set_parser.add_argument('api_key', help='Your API key')
129
+
130
+ # Get API key command
131
+ get_parser = subparsers.add_parser('get-key', help='Get the stored API key')
132
+
133
+ # Remove API key command
134
+ remove_parser = subparsers.add_parser('remove-key', help='Remove the API key')
135
+
136
+ # Status command
137
+ status_parser = subparsers.add_parser('status', help='Show configuration status')
138
+
139
+ # Clear command
140
+ clear_parser = subparsers.add_parser('clear', help='Clear all configuration')
141
+
142
+ # Interactive mode
143
+ interactive_parser = subparsers.add_parser('interactive', help='Interactive setup')
144
+
145
+ args = parser.parse_args()
146
+
147
+ if not args.command:
148
+ parser.print_help()
149
+ return
150
+
151
+ if args.command == 'set-key':
152
+ set_api_key(args.api_key)
153
+
154
+ elif args.command == 'get-key':
155
+ api_key = get_api_key()
156
+ if api_key:
157
+ print(f"pk...{api_key[-4:] if len(api_key) > 4 else api_key}")
158
+ else:
159
+ print("No API key configured")
160
+
161
+ elif args.command == 'remove-key':
162
+ remove_api_key()
163
+
164
+ elif args.command == 'status':
165
+ show_status()
166
+
167
+ elif args.command == 'clear':
168
+ clear_config()
169
+
170
+ elif args.command == 'interactive':
171
+ print("🔧 Interactive API Key Setup")
172
+ print("=" * 40)
173
+
174
+ # Check if key already exists
175
+ existing_key = get_api_key()
176
+ if existing_key:
177
+ print(f"Current API key: pk...{existing_key[-4:] if len(existing_key) > 4 else existing_key}")
178
+ response = input("Do you want to replace it? (y/N): ").strip().lower()
179
+ if response != 'y':
180
+ print("Setup cancelled")
181
+ return
182
+
183
+ # Get new API key
184
+ print("\nEnter your API key:")
185
+ api_key = getpass.getpass("API Key: ").strip()
186
+
187
+ if not api_key:
188
+ print("❌ API key cannot be empty")
189
+ return
190
+
191
+ # Save configuration
192
+ if set_api_key(api_key):
193
+ print("\n🎉 Setup complete!")
194
+ print("You can now use perd in any project without specifying the API key.")
195
+
196
+ if __name__ == "__main__":
197
+ main()
@@ -0,0 +1,9 @@
1
+ """
2
+ Reservoir module for per_datasets
3
+ """
4
+
5
+ from .load_random import load_random
6
+ from .load import load
7
+ from ..talkaholic.reservoir import Reservoir
8
+
9
+ __all__ = ['load_random', 'load', 'Reservoir']
@@ -0,0 +1,67 @@
1
+ """
2
+ load function for the reservoir module
3
+ """
4
+
5
+ import json
6
+ import requests
7
+ import pandas as pd
8
+ from typing import Dict, Any, List, Union, Optional
9
+ from pathlib import Path
10
+
11
+ from ..talkaholic.reservoir import Reservoir
12
+ from ..utils.config import load_stored_api_key
13
+ from ..utils.display import digital_screen
14
+ from ..utils.api import get_api_config, get_headers, make_request
15
+
16
+
17
+ def load(reservoir_id: str) -> pd.DataFrame:
18
+ """
19
+ ## load(reservoir id)
20
+
21
+ Loads a specific reservoir model by ID from the reservoir datasets in the database.
22
+
23
+ Args:
24
+ reservoir_id (str): The ID of the reservoir to load
25
+
26
+ ### **returns**
27
+
28
+ [`pandas.DataFrame`]
29
+ A DataFrame containing all rows of the reservoir dataset
30
+
31
+ Raises:
32
+ RuntimeError: If the module hasn't been initialized
33
+ ValueError: If the reservoir ID is invalid or not found
34
+ """
35
+ try:
36
+ headers = get_headers()
37
+ _API_CONFIG = get_api_config()
38
+ url = f"{_API_CONFIG['base_url']}/datasets/{reservoir_id}"
39
+ response = requests.get(url, headers=headers)
40
+ response.raise_for_status()
41
+ api_data = response.json()
42
+
43
+ # Handle the actual API response structure
44
+ if 'data' in api_data and 'columns' in api_data:
45
+ data = api_data['data']
46
+ columns = api_data['columns']
47
+ returned_reservoir_id = api_data.get('dataset_id', reservoir_id)
48
+
49
+ if len(data) == 0:
50
+ raise ValueError("API returned reservoir with no data")
51
+
52
+ # Convert all rows to a DataFrame
53
+ df = pd.DataFrame(data)
54
+
55
+ # Return the DataFrame with all rows
56
+ return df
57
+ else:
58
+ raise ValueError("API response does not contain 'data' and 'columns' fields")
59
+
60
+ except requests.exceptions.RequestException as e:
61
+ raise ConnectionError(f"Failed to connect to API endpoint: {e}")
62
+ except json.JSONDecodeError as e:
63
+ raise ValueError(f"Invalid JSON response from API: {e}")
64
+ except Exception as e:
65
+ if "not initialized" in str(e):
66
+ raise e
67
+ raise RuntimeError(f"Error loading reservoir {reservoir_id}: {e}")
@@ -0,0 +1,45 @@
1
+ """load_random function for the reservoir module."""
2
+
3
+ from typing import Any, Dict, List, Optional
4
+
5
+ import pandas as pd
6
+
7
+ from ..utils.api import make_request
8
+
9
+
10
+ def load_random(
11
+ filters: Optional[Dict[str, Any]] = None,
12
+ fields: Optional[List[str]] = None,
13
+ partitions: Optional[List[str]] = None,
14
+ ) -> pd.DataFrame:
15
+ """
16
+ Load a random reservoir model from the reservoir datasets in the database.
17
+
18
+ Args:
19
+ filters: Optional query filters.
20
+ fields: Optional selected fields.
21
+ partitions: Optional partition dimensions.
22
+
23
+ Returns:
24
+ pandas.DataFrame: DataFrame containing all rows from the selected dataset.
25
+ """
26
+ try:
27
+ api_data = make_request(filters=filters, fields=fields, partitions=partitions)
28
+
29
+ if "data" in api_data and "columns" in api_data:
30
+ data = api_data["data"]
31
+ dataset_id = api_data.get("dataset_id", "Unknown")
32
+
33
+ if len(data) == 0:
34
+ raise ValueError("API returned dataset with no data")
35
+
36
+ df = pd.DataFrame(data)
37
+ print(f"Loaded dataset with ID: {dataset_id}")
38
+ return df
39
+
40
+ raise ValueError("API response does not contain 'data' and 'columns' fields")
41
+
42
+ except Exception as e:
43
+ if "not initialized" in str(e):
44
+ raise e
45
+ raise RuntimeError(f"Error loading reservoir data: {e}")
@@ -0,0 +1,7 @@
1
+ """
2
+ Talkaholic submodule for reservoir datasets
3
+ """
4
+
5
+ from .reservoir import Reservoir
6
+
7
+ __all__ = ['Reservoir']
@@ -0,0 +1,52 @@
1
+ """
2
+ Reservoir class for the talkaholic submodule
3
+ """
4
+
5
+ from typing import Dict, Any
6
+ import pandas as pd
7
+
8
+
9
+ class Reservoir:
10
+ """
11
+ A class representing a reservoir dataset that behaves like a pandas DataFrame
12
+ """
13
+
14
+ def __init__(self, data: Dict[str, Any]):
15
+ """
16
+ Initialize a Reservoir object with the given data
17
+
18
+ Args:
19
+ data: Dictionary containing reservoir parameters
20
+ """
21
+ self.data = data
22
+ # Create a DataFrame internally for pandas-like behavior
23
+ self._df = pd.DataFrame([data]) if data else pd.DataFrame()
24
+
25
+ def __repr__(self):
26
+ return f"Reservoir({self.data})"
27
+
28
+ @property
29
+ def shape(self):
30
+ """Return the shape of the reservoir data (rows, columns)"""
31
+ return self._df.shape
32
+
33
+ def to_dict(self) -> Dict[str, Any]:
34
+ """Convert the reservoir data back to a dictionary"""
35
+ return self.data
36
+
37
+ def to_dataframe(self) -> pd.DataFrame:
38
+ """Convert the reservoir data to a pandas DataFrame"""
39
+ return self._df.copy()
40
+
41
+ def get_field(self, field_name: str, default: Any = None) -> Any:
42
+ """
43
+ Get any field from the original data with a default value
44
+
45
+ Args:
46
+ field_name: Name of the field to retrieve
47
+ default: Default value if field doesn't exist
48
+
49
+ Returns:
50
+ The field value or default
51
+ """
52
+ return self.data.get(field_name, default)
@@ -0,0 +1,21 @@
1
+ """Utils module for per_datasets."""
2
+
3
+ from .config import get_config_file, load_stored_api_key
4
+ from .display import digital_screen
5
+ from .api import get_headers, make_request, get_api_config, set_api_key
6
+ from .init import initialize
7
+ from . import names
8
+ from . import units
9
+
10
+ __all__ = [
11
+ "get_config_file",
12
+ "load_stored_api_key",
13
+ "digital_screen",
14
+ "get_headers",
15
+ "make_request",
16
+ "get_api_config",
17
+ "set_api_key",
18
+ "initialize",
19
+ "names",
20
+ "units",
21
+ ]