pyconvexity 0.1.0__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 pyconvexity might be problematic. Click here for more details.
- pyconvexity/__init__.py +120 -0
- pyconvexity/_version.py +2 -0
- pyconvexity/core/__init__.py +64 -0
- pyconvexity/core/database.py +314 -0
- pyconvexity/core/errors.py +100 -0
- pyconvexity/core/types.py +306 -0
- pyconvexity/models/__init__.py +36 -0
- pyconvexity/models/attributes.py +383 -0
- pyconvexity/models/components.py +464 -0
- pyconvexity/models/network.py +426 -0
- pyconvexity/validation/__init__.py +17 -0
- pyconvexity/validation/rules.py +301 -0
- pyconvexity-0.1.0.dist-info/METADATA +135 -0
- pyconvexity-0.1.0.dist-info/RECORD +16 -0
- pyconvexity-0.1.0.dist-info/WHEEL +5 -0
- pyconvexity-0.1.0.dist-info/top_level.txt +1 -0
pyconvexity/__init__.py
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"""
|
|
2
|
+
PyConvexity - Python library for energy system modeling and optimization.
|
|
3
|
+
|
|
4
|
+
This library provides the core functionality of the Convexity desktop application
|
|
5
|
+
as a reusable, pip-installable package for building and solving energy system models.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
# Version information
|
|
9
|
+
from pyconvexity._version import __version__
|
|
10
|
+
|
|
11
|
+
__author__ = "Convexity Team"
|
|
12
|
+
|
|
13
|
+
# Core imports - always available
|
|
14
|
+
from pyconvexity.core.errors import (
|
|
15
|
+
PyConvexityError,
|
|
16
|
+
DatabaseError,
|
|
17
|
+
ValidationError,
|
|
18
|
+
ComponentNotFound,
|
|
19
|
+
AttributeNotFound,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
from pyconvexity.core.types import (
|
|
23
|
+
StaticValue,
|
|
24
|
+
TimeseriesPoint,
|
|
25
|
+
Component,
|
|
26
|
+
Network,
|
|
27
|
+
CreateNetworkRequest,
|
|
28
|
+
CreateComponentRequest,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
from pyconvexity.core.database import (
|
|
32
|
+
create_database_with_schema,
|
|
33
|
+
database_context,
|
|
34
|
+
open_connection,
|
|
35
|
+
validate_database,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
# Import main API functions
|
|
39
|
+
from pyconvexity.models import (
|
|
40
|
+
# Component operations
|
|
41
|
+
get_component, create_component, update_component, delete_component,
|
|
42
|
+
list_components_by_type, list_component_attributes,
|
|
43
|
+
|
|
44
|
+
# Attribute operations
|
|
45
|
+
set_static_attribute, set_timeseries_attribute, get_attribute, delete_attribute,
|
|
46
|
+
|
|
47
|
+
# Network operations
|
|
48
|
+
create_network, get_network_info, get_network_time_periods, list_networks,
|
|
49
|
+
create_carrier, list_carriers, get_network_config, set_network_config,
|
|
50
|
+
get_master_scenario_id, resolve_scenario_id,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
from pyconvexity.validation import (
|
|
54
|
+
get_validation_rule, list_validation_rules, validate_timeseries_alignment
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
# High-level API functions
|
|
58
|
+
__all__ = [
|
|
59
|
+
# Version info
|
|
60
|
+
"__version__",
|
|
61
|
+
"__author__",
|
|
62
|
+
|
|
63
|
+
# Core types
|
|
64
|
+
"StaticValue",
|
|
65
|
+
"TimeseriesPoint",
|
|
66
|
+
"Component",
|
|
67
|
+
"Network",
|
|
68
|
+
"CreateNetworkRequest",
|
|
69
|
+
"CreateComponentRequest",
|
|
70
|
+
|
|
71
|
+
# Database operations
|
|
72
|
+
"create_database_with_schema",
|
|
73
|
+
"database_context",
|
|
74
|
+
"open_connection",
|
|
75
|
+
"validate_database",
|
|
76
|
+
|
|
77
|
+
# Exceptions
|
|
78
|
+
"PyConvexityError",
|
|
79
|
+
"DatabaseError",
|
|
80
|
+
"ValidationError",
|
|
81
|
+
"ComponentNotFound",
|
|
82
|
+
"AttributeNotFound",
|
|
83
|
+
|
|
84
|
+
# Component operations
|
|
85
|
+
"get_component", "create_component", "update_component", "delete_component",
|
|
86
|
+
"list_components_by_type", "list_component_attributes",
|
|
87
|
+
|
|
88
|
+
# Attribute operations
|
|
89
|
+
"set_static_attribute", "set_timeseries_attribute", "get_attribute", "delete_attribute",
|
|
90
|
+
|
|
91
|
+
# Network operations
|
|
92
|
+
"create_network", "get_network_info", "get_network_time_periods", "list_networks",
|
|
93
|
+
"create_carrier", "list_carriers", "get_network_config", "set_network_config",
|
|
94
|
+
"get_master_scenario_id", "resolve_scenario_id",
|
|
95
|
+
|
|
96
|
+
# Validation
|
|
97
|
+
"get_validation_rule", "list_validation_rules", "validate_timeseries_alignment",
|
|
98
|
+
]
|
|
99
|
+
|
|
100
|
+
# Optional imports with graceful fallbacks
|
|
101
|
+
try:
|
|
102
|
+
from pyconvexity.solvers.pypsa import PyPSASolver
|
|
103
|
+
__all__.append("PyPSASolver")
|
|
104
|
+
except ImportError:
|
|
105
|
+
# PyPSA not available
|
|
106
|
+
pass
|
|
107
|
+
|
|
108
|
+
try:
|
|
109
|
+
from pyconvexity.io.excel import ExcelImporter, ExcelExporter
|
|
110
|
+
__all__.extend(["ExcelImporter", "ExcelExporter"])
|
|
111
|
+
except ImportError:
|
|
112
|
+
# Excel dependencies not available
|
|
113
|
+
pass
|
|
114
|
+
|
|
115
|
+
try:
|
|
116
|
+
from pyconvexity.io.netcdf import NetCDFImporter, NetCDFExporter
|
|
117
|
+
__all__.extend(["NetCDFImporter", "NetCDFExporter"])
|
|
118
|
+
except ImportError:
|
|
119
|
+
# NetCDF dependencies not available
|
|
120
|
+
pass
|
pyconvexity/_version.py
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Core module for PyConvexity.
|
|
3
|
+
|
|
4
|
+
Contains fundamental types, database operations, and error handling.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from pyconvexity.core.errors import (
|
|
8
|
+
PyConvexityError,
|
|
9
|
+
DatabaseError,
|
|
10
|
+
ValidationError,
|
|
11
|
+
ComponentNotFound,
|
|
12
|
+
AttributeNotFound,
|
|
13
|
+
InvalidDataType,
|
|
14
|
+
TimeseriesError,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
from pyconvexity.core.types import (
|
|
18
|
+
StaticValue,
|
|
19
|
+
TimeseriesPoint,
|
|
20
|
+
AttributeValue,
|
|
21
|
+
ValidationRule,
|
|
22
|
+
Component,
|
|
23
|
+
Network,
|
|
24
|
+
TimePeriod,
|
|
25
|
+
TimeseriesValidationResult,
|
|
26
|
+
CreateComponentRequest,
|
|
27
|
+
CreateNetworkRequest,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
from pyconvexity.core.database import (
|
|
31
|
+
DatabaseContext,
|
|
32
|
+
open_connection,
|
|
33
|
+
validate_database,
|
|
34
|
+
create_database_with_schema,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
__all__ = [
|
|
38
|
+
# Errors
|
|
39
|
+
"PyConvexityError",
|
|
40
|
+
"DatabaseError",
|
|
41
|
+
"ValidationError",
|
|
42
|
+
"ComponentNotFound",
|
|
43
|
+
"AttributeNotFound",
|
|
44
|
+
"InvalidDataType",
|
|
45
|
+
"TimeseriesError",
|
|
46
|
+
|
|
47
|
+
# Types
|
|
48
|
+
"StaticValue",
|
|
49
|
+
"TimeseriesPoint",
|
|
50
|
+
"AttributeValue",
|
|
51
|
+
"ValidationRule",
|
|
52
|
+
"Component",
|
|
53
|
+
"Network",
|
|
54
|
+
"TimePeriod",
|
|
55
|
+
"TimeseriesValidationResult",
|
|
56
|
+
"CreateComponentRequest",
|
|
57
|
+
"CreateNetworkRequest",
|
|
58
|
+
|
|
59
|
+
# Database
|
|
60
|
+
"DatabaseContext",
|
|
61
|
+
"open_connection",
|
|
62
|
+
"validate_database",
|
|
63
|
+
"create_database_with_schema",
|
|
64
|
+
]
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Database connection and schema management for PyConvexity.
|
|
3
|
+
|
|
4
|
+
Provides clean abstractions for database operations with proper connection
|
|
5
|
+
management, schema validation, and resource cleanup.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import sqlite3
|
|
9
|
+
import sys
|
|
10
|
+
from contextlib import contextmanager
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Generator, List, Optional
|
|
13
|
+
|
|
14
|
+
from pyconvexity.core.errors import ConnectionError, DatabaseError, ValidationError
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class DatabaseContext:
|
|
18
|
+
"""
|
|
19
|
+
Context manager for database connections with automatic cleanup.
|
|
20
|
+
|
|
21
|
+
Provides a clean way to manage database connections with proper
|
|
22
|
+
resource cleanup and error handling.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def __init__(self, db_path: str, read_only: bool = False):
|
|
26
|
+
self.db_path = db_path
|
|
27
|
+
self.read_only = read_only
|
|
28
|
+
self.connection: Optional[sqlite3.Connection] = None
|
|
29
|
+
|
|
30
|
+
def __enter__(self) -> sqlite3.Connection:
|
|
31
|
+
self.connection = open_connection(self.db_path, read_only=self.read_only)
|
|
32
|
+
return self.connection
|
|
33
|
+
|
|
34
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
35
|
+
if self.connection:
|
|
36
|
+
if exc_type is None:
|
|
37
|
+
# No exception, commit any pending changes
|
|
38
|
+
self.connection.commit()
|
|
39
|
+
else:
|
|
40
|
+
# Exception occurred, rollback
|
|
41
|
+
self.connection.rollback()
|
|
42
|
+
self.connection.close()
|
|
43
|
+
self.connection = None
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@contextmanager
|
|
47
|
+
def database_context(db_path: str, read_only: bool = False) -> Generator[sqlite3.Connection, None, None]:
|
|
48
|
+
"""
|
|
49
|
+
Context manager function for database connections.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
db_path: Path to the SQLite database file
|
|
53
|
+
read_only: If True, open in read-only mode
|
|
54
|
+
|
|
55
|
+
Yields:
|
|
56
|
+
sqlite3.Connection: Database connection with proper configuration
|
|
57
|
+
|
|
58
|
+
Example:
|
|
59
|
+
with database_context("model.db") as conn:
|
|
60
|
+
cursor = conn.execute("SELECT * FROM networks")
|
|
61
|
+
networks = cursor.fetchall()
|
|
62
|
+
"""
|
|
63
|
+
with DatabaseContext(db_path, read_only) as conn:
|
|
64
|
+
yield conn
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def open_connection(db_path: str, read_only: bool = False) -> sqlite3.Connection:
|
|
68
|
+
"""
|
|
69
|
+
Open database connection with proper settings.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
db_path: Path to the SQLite database file
|
|
73
|
+
read_only: If True, open in read-only mode
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
sqlite3.Connection: Configured database connection
|
|
77
|
+
|
|
78
|
+
Raises:
|
|
79
|
+
ConnectionError: If database connection fails
|
|
80
|
+
"""
|
|
81
|
+
try:
|
|
82
|
+
# Build connection URI for read-only mode if needed
|
|
83
|
+
if read_only:
|
|
84
|
+
uri = f"file:{db_path}?mode=ro"
|
|
85
|
+
conn = sqlite3.connect(uri, uri=True)
|
|
86
|
+
else:
|
|
87
|
+
conn = sqlite3.connect(db_path)
|
|
88
|
+
|
|
89
|
+
# Configure connection
|
|
90
|
+
conn.row_factory = sqlite3.Row # Enable column access by name
|
|
91
|
+
conn.execute("PRAGMA foreign_keys = ON") # Enable foreign key constraints
|
|
92
|
+
|
|
93
|
+
# Set reasonable timeouts
|
|
94
|
+
conn.execute("PRAGMA busy_timeout = 30000") # 30 second timeout
|
|
95
|
+
|
|
96
|
+
return conn
|
|
97
|
+
|
|
98
|
+
except sqlite3.Error as e:
|
|
99
|
+
raise ConnectionError(f"Failed to open database at {db_path}: {e}") from e
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def validate_database(conn: sqlite3.Connection) -> None:
|
|
103
|
+
"""
|
|
104
|
+
Validate database schema has required tables.
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
conn: Database connection to validate
|
|
108
|
+
|
|
109
|
+
Raises:
|
|
110
|
+
ValidationError: If required tables are missing
|
|
111
|
+
"""
|
|
112
|
+
required_tables = [
|
|
113
|
+
"networks",
|
|
114
|
+
"components",
|
|
115
|
+
"component_attributes",
|
|
116
|
+
"attribute_validation_rules",
|
|
117
|
+
"carriers",
|
|
118
|
+
"scenarios"
|
|
119
|
+
]
|
|
120
|
+
|
|
121
|
+
missing_tables = []
|
|
122
|
+
|
|
123
|
+
for table in required_tables:
|
|
124
|
+
cursor = conn.execute(
|
|
125
|
+
"SELECT name FROM sqlite_master WHERE type='table' AND name=?",
|
|
126
|
+
(table,)
|
|
127
|
+
)
|
|
128
|
+
if not cursor.fetchone():
|
|
129
|
+
missing_tables.append(table)
|
|
130
|
+
|
|
131
|
+
if missing_tables:
|
|
132
|
+
raise ValidationError(
|
|
133
|
+
f"Required tables not found in database: {', '.join(missing_tables)}"
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def create_database_with_schema(db_path: str) -> None:
|
|
138
|
+
"""
|
|
139
|
+
Create a new database and apply the complete schema.
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
db_path: Path where the new database should be created
|
|
143
|
+
|
|
144
|
+
Raises:
|
|
145
|
+
DatabaseError: If schema files cannot be found or applied
|
|
146
|
+
"""
|
|
147
|
+
db_path_obj = Path(db_path)
|
|
148
|
+
|
|
149
|
+
# Ensure parent directory exists
|
|
150
|
+
if db_path_obj.parent and not db_path_obj.parent.exists():
|
|
151
|
+
db_path_obj.parent.mkdir(parents=True, exist_ok=True)
|
|
152
|
+
|
|
153
|
+
# Remove existing file if it exists, to ensure a clean start
|
|
154
|
+
if db_path_obj.exists():
|
|
155
|
+
db_path_obj.unlink()
|
|
156
|
+
|
|
157
|
+
# Find schema files
|
|
158
|
+
schema_dir = _find_schema_directory()
|
|
159
|
+
if not schema_dir:
|
|
160
|
+
raise DatabaseError("Could not find schema directory")
|
|
161
|
+
|
|
162
|
+
schema_files = [
|
|
163
|
+
"01_core_schema.sql",
|
|
164
|
+
"02_data_metadata.sql",
|
|
165
|
+
"03_validation_data.sql",
|
|
166
|
+
"04_scenario_schema.sql"
|
|
167
|
+
]
|
|
168
|
+
|
|
169
|
+
# Verify all schema files exist
|
|
170
|
+
missing_files = []
|
|
171
|
+
for filename in schema_files:
|
|
172
|
+
schema_file = schema_dir / filename
|
|
173
|
+
if not schema_file.exists():
|
|
174
|
+
missing_files.append(filename)
|
|
175
|
+
|
|
176
|
+
if missing_files:
|
|
177
|
+
raise DatabaseError(f"Schema files not found: {', '.join(missing_files)}")
|
|
178
|
+
|
|
179
|
+
# Create connection and apply schemas
|
|
180
|
+
try:
|
|
181
|
+
conn = sqlite3.connect(db_path)
|
|
182
|
+
|
|
183
|
+
# Enable foreign key constraints
|
|
184
|
+
conn.execute("PRAGMA foreign_keys = ON")
|
|
185
|
+
|
|
186
|
+
# Execute schemas in order
|
|
187
|
+
for filename in schema_files:
|
|
188
|
+
schema_file = schema_dir / filename
|
|
189
|
+
with open(schema_file, 'r') as f:
|
|
190
|
+
conn.executescript(f.read())
|
|
191
|
+
|
|
192
|
+
conn.close()
|
|
193
|
+
|
|
194
|
+
except sqlite3.Error as e:
|
|
195
|
+
# Clean up partial database on error
|
|
196
|
+
if db_path_obj.exists():
|
|
197
|
+
db_path_obj.unlink()
|
|
198
|
+
raise DatabaseError(f"Failed to create database schema: {e}") from e
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def _find_schema_directory() -> Optional[Path]:
|
|
202
|
+
"""
|
|
203
|
+
Find the schema directory in various possible locations.
|
|
204
|
+
|
|
205
|
+
Returns:
|
|
206
|
+
Path to schema directory or None if not found
|
|
207
|
+
"""
|
|
208
|
+
# Try bundled location first (PyInstaller)
|
|
209
|
+
for p in sys.path:
|
|
210
|
+
candidate = Path(p) / "schema"
|
|
211
|
+
if candidate.exists() and candidate.is_dir():
|
|
212
|
+
return candidate
|
|
213
|
+
|
|
214
|
+
# Try relative to this file (development mode)
|
|
215
|
+
current_file = Path(__file__)
|
|
216
|
+
|
|
217
|
+
# Look for schema in the main project
|
|
218
|
+
# Assuming pyconvexity/src/pyconvexity/core/database.py
|
|
219
|
+
# and schema is at project_root/schema
|
|
220
|
+
project_root = current_file.parent.parent.parent.parent.parent
|
|
221
|
+
dev_schema_dir = project_root / "schema"
|
|
222
|
+
if dev_schema_dir.exists():
|
|
223
|
+
return dev_schema_dir
|
|
224
|
+
|
|
225
|
+
# Try package data location
|
|
226
|
+
try:
|
|
227
|
+
import importlib.resources
|
|
228
|
+
schema_path = importlib.resources.files('pyconvexity') / 'data' / 'schema'
|
|
229
|
+
if schema_path.is_dir():
|
|
230
|
+
return Path(str(schema_path))
|
|
231
|
+
except (ImportError, AttributeError):
|
|
232
|
+
pass
|
|
233
|
+
|
|
234
|
+
return None
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def get_database_info(conn: sqlite3.Connection) -> dict:
|
|
238
|
+
"""
|
|
239
|
+
Get information about the database structure and contents.
|
|
240
|
+
|
|
241
|
+
Args:
|
|
242
|
+
conn: Database connection
|
|
243
|
+
|
|
244
|
+
Returns:
|
|
245
|
+
Dictionary with database information
|
|
246
|
+
"""
|
|
247
|
+
info = {
|
|
248
|
+
"tables": [],
|
|
249
|
+
"networks": 0,
|
|
250
|
+
"components": 0,
|
|
251
|
+
"attributes": 0,
|
|
252
|
+
"scenarios": 0,
|
|
253
|
+
"carriers": 0
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
# Get table list
|
|
257
|
+
cursor = conn.execute(
|
|
258
|
+
"SELECT name FROM sqlite_master WHERE type='table' ORDER BY name"
|
|
259
|
+
)
|
|
260
|
+
info["tables"] = [row[0] for row in cursor.fetchall()]
|
|
261
|
+
|
|
262
|
+
# Get counts for main entities
|
|
263
|
+
count_queries = {
|
|
264
|
+
"networks": "SELECT COUNT(*) FROM networks",
|
|
265
|
+
"components": "SELECT COUNT(*) FROM components",
|
|
266
|
+
"attributes": "SELECT COUNT(*) FROM component_attributes",
|
|
267
|
+
"scenarios": "SELECT COUNT(*) FROM scenarios",
|
|
268
|
+
"carriers": "SELECT COUNT(*) FROM carriers"
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
for key, query in count_queries.items():
|
|
272
|
+
try:
|
|
273
|
+
cursor = conn.execute(query)
|
|
274
|
+
info[key] = cursor.fetchone()[0]
|
|
275
|
+
except sqlite3.Error:
|
|
276
|
+
# Table might not exist
|
|
277
|
+
info[key] = 0
|
|
278
|
+
|
|
279
|
+
return info
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
def check_database_compatibility(conn: sqlite3.Connection) -> dict:
|
|
283
|
+
"""
|
|
284
|
+
Check if database is compatible with current PyConvexity version.
|
|
285
|
+
|
|
286
|
+
Args:
|
|
287
|
+
conn: Database connection
|
|
288
|
+
|
|
289
|
+
Returns:
|
|
290
|
+
Dictionary with compatibility information
|
|
291
|
+
"""
|
|
292
|
+
result = {
|
|
293
|
+
"compatible": True,
|
|
294
|
+
"version": None,
|
|
295
|
+
"issues": [],
|
|
296
|
+
"warnings": []
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
try:
|
|
300
|
+
validate_database(conn)
|
|
301
|
+
except ValidationError as e:
|
|
302
|
+
result["compatible"] = False
|
|
303
|
+
result["issues"].append(str(e))
|
|
304
|
+
|
|
305
|
+
# Check for version information (if we add a version table later)
|
|
306
|
+
try:
|
|
307
|
+
cursor = conn.execute("SELECT version FROM database_version LIMIT 1")
|
|
308
|
+
row = cursor.fetchone()
|
|
309
|
+
if row:
|
|
310
|
+
result["version"] = row[0]
|
|
311
|
+
except sqlite3.Error:
|
|
312
|
+
result["warnings"].append("No version information found in database")
|
|
313
|
+
|
|
314
|
+
return result
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Error classes for PyConvexity.
|
|
3
|
+
|
|
4
|
+
These mirror the error handling from the original Rust implementation
|
|
5
|
+
while providing Python-specific enhancements.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import Optional
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class PyConvexityError(Exception):
|
|
12
|
+
"""Base exception for all PyConvexity errors"""
|
|
13
|
+
pass
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class DatabaseError(PyConvexityError):
|
|
17
|
+
"""Database-related errors"""
|
|
18
|
+
pass
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class ConnectionError(DatabaseError):
|
|
22
|
+
"""Database connection failed"""
|
|
23
|
+
pass
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class ValidationError(PyConvexityError):
|
|
27
|
+
"""Data validation error"""
|
|
28
|
+
pass
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class ComponentNotFound(PyConvexityError):
|
|
32
|
+
"""Component not found in database"""
|
|
33
|
+
|
|
34
|
+
def __init__(self, component_id: int, message: Optional[str] = None):
|
|
35
|
+
self.component_id = component_id
|
|
36
|
+
if message is None:
|
|
37
|
+
message = f"Component not found: {component_id}"
|
|
38
|
+
super().__init__(message)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class AttributeNotFound(PyConvexityError):
|
|
42
|
+
"""Attribute not found for component"""
|
|
43
|
+
|
|
44
|
+
def __init__(self, component_id: int, attribute_name: str, message: Optional[str] = None):
|
|
45
|
+
self.component_id = component_id
|
|
46
|
+
self.attribute_name = attribute_name
|
|
47
|
+
if message is None:
|
|
48
|
+
message = f"Attribute not found: component {component_id}, attribute '{attribute_name}'"
|
|
49
|
+
super().__init__(message)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class InvalidDataType(ValidationError):
|
|
53
|
+
"""Invalid data type for attribute"""
|
|
54
|
+
|
|
55
|
+
def __init__(self, expected: str, actual: str, message: Optional[str] = None):
|
|
56
|
+
self.expected = expected
|
|
57
|
+
self.actual = actual
|
|
58
|
+
if message is None:
|
|
59
|
+
message = f"Invalid data type: expected {expected}, got {actual}"
|
|
60
|
+
super().__init__(message)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class TimeseriesError(PyConvexityError):
|
|
64
|
+
"""Timeseries serialization/deserialization error"""
|
|
65
|
+
pass
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class NetworkNotFound(PyConvexityError):
|
|
69
|
+
"""Network not found in database"""
|
|
70
|
+
|
|
71
|
+
def __init__(self, network_id: int, message: Optional[str] = None):
|
|
72
|
+
self.network_id = network_id
|
|
73
|
+
if message is None:
|
|
74
|
+
message = f"Network not found: {network_id}"
|
|
75
|
+
super().__init__(message)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class ScenarioNotFound(PyConvexityError):
|
|
79
|
+
"""Scenario not found in database"""
|
|
80
|
+
|
|
81
|
+
def __init__(self, scenario_id: int, message: Optional[str] = None):
|
|
82
|
+
self.scenario_id = scenario_id
|
|
83
|
+
if message is None:
|
|
84
|
+
message = f"Scenario not found: {scenario_id}"
|
|
85
|
+
super().__init__(message)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class CarrierNotFound(PyConvexityError):
|
|
89
|
+
"""Carrier not found in database"""
|
|
90
|
+
|
|
91
|
+
def __init__(self, carrier_id: int, message: Optional[str] = None):
|
|
92
|
+
self.carrier_id = carrier_id
|
|
93
|
+
if message is None:
|
|
94
|
+
message = f"Carrier not found: {carrier_id}"
|
|
95
|
+
super().__init__(message)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
# Legacy aliases for backward compatibility with existing code
|
|
99
|
+
# These will be deprecated in future versions
|
|
100
|
+
DbError = PyConvexityError
|