perd 0.0.1__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.
- perd/__init__.py +26 -0
- perd/cli.py +197 -0
- perd/reservoir/__init__.py +9 -0
- perd/reservoir/load.py +67 -0
- perd/reservoir/load_random.py +45 -0
- perd/talkaholic/__init__.py +7 -0
- perd/talkaholic/reservoir.py +52 -0
- perd/utils/__init__.py +21 -0
- perd/utils/api.py +279 -0
- perd/utils/config.py +28 -0
- perd/utils/display.py +41 -0
- perd/utils/init.py +326 -0
- perd/utils/names/Fields.py +11 -0
- perd/utils/names/Filters.py +8 -0
- perd/utils/names/Partitions.py +5 -0
- perd/utils/names/__init__.py +5 -0
- perd/utils/units.py +6 -0
- perd/visual.py +567 -0
- perd/workflow.py +89 -0
- perd/workflows/__init__.py +6 -0
- perd/workflows/runtime.py +853 -0
- perd-0.0.1.dist-info/METADATA +280 -0
- perd-0.0.1.dist-info/RECORD +26 -0
- perd-0.0.1.dist-info/WHEEL +5 -0
- perd-0.0.1.dist-info/entry_points.txt +2 -0
- perd-0.0.1.dist-info/top_level.txt +1 -0
perd/__init__.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""PER Datasets - A module for loading reservoir datasets."""
|
|
2
|
+
|
|
3
|
+
__version__ = "0.0.1"
|
|
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
|
+
]
|
perd/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()
|
perd/reservoir/load.py
ADDED
|
@@ -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,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)
|
perd/utils/__init__.py
ADDED
|
@@ -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
|
+
]
|