masworks 0.1.0.dev20260205__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.
Files changed (52) hide show
  1. masworks/MasDatabase/__init__.py +37 -0
  2. masworks/MasDatabase/base.py +116 -0
  3. masworks/MasDatabase/context.py +58 -0
  4. masworks/MasDatabase/decorators.py +37 -0
  5. masworks/MasDatabase/generic_session.py +228 -0
  6. masworks/MasDatabase/models.py +351 -0
  7. masworks/MasDatabase/operations.py +453 -0
  8. masworks/MasDatabase/time_series_session.py +512 -0
  9. masworks/MasDatabase/utilities.py +119 -0
  10. masworks/MasDatabase/versioned_models.py +876 -0
  11. masworks/MasDatabase/versioned_session.py +1043 -0
  12. masworks/__init__.py +28 -0
  13. masworks/analysis/__init__.py +14 -0
  14. masworks/analysis/backtesting.py +319 -0
  15. masworks/analysis/modeling.py +294 -0
  16. masworks/config.py +242 -0
  17. masworks/constants.py +49 -0
  18. masworks/core.py +67 -0
  19. masworks/enhanced_time_series_reader_sqlalchemy.py +737 -0
  20. masworks/feeds/__init__.py +23 -0
  21. masworks/feeds/base.py +110 -0
  22. masworks/feeds/bridge.py +1320 -0
  23. masworks/feeds/bridge_server_filter.py +171 -0
  24. masworks/feeds/bridge_service_manager.py +414 -0
  25. masworks/feeds/datastream.py +555 -0
  26. masworks/feeds/factory.py +135 -0
  27. masworks/feeds/fred.py +271 -0
  28. masworks/logging_config.py +139 -0
  29. masworks/mas_time_series_admin.py +1517 -0
  30. masworks/mas_time_series_base.py +40 -0
  31. masworks/mas_time_series_reader.py +857 -0
  32. masworks/test_versioned_models.py +86 -0
  33. masworks/types.py +20 -0
  34. masworks/utils/__init__.py +53 -0
  35. masworks/utils/bbg_ticker_detail.py +429 -0
  36. masworks/utils/data_utils.py +774 -0
  37. masworks/utils/database_connection.py +172 -0
  38. masworks/utils/database_operations.py +791 -0
  39. masworks/utils/date_utils.py +104 -0
  40. masworks/utils/general.py +348 -0
  41. masworks/utils/model_utils.py +41 -0
  42. masworks/utils/series_utils.py +133 -0
  43. masworks/utils/session_base.py +166 -0
  44. masworks/utils/time_series.py +323 -0
  45. masworks/versioned_enhanced_reader.py +537 -0
  46. masworks/versioned_time_series_admin.py +1166 -0
  47. masworks/versioned_time_series_base.py +359 -0
  48. masworks/versioned_time_series_reader.py +690 -0
  49. masworks-0.1.0.dev20260205.dist-info/METADATA +94 -0
  50. masworks-0.1.0.dev20260205.dist-info/RECORD +52 -0
  51. masworks-0.1.0.dev20260205.dist-info/WHEEL +5 -0
  52. masworks-0.1.0.dev20260205.dist-info/top_level.txt +1 -0
@@ -0,0 +1,37 @@
1
+ """
2
+ MasDatabase module for MasWorks library.
3
+
4
+ Provides MasWorks-specific database functionality including session management
5
+ and ORM models for time series data. This module contains domain-specific
6
+ database operations, while generic database utilities are in utils/.
7
+ """
8
+
9
+ from ..utils.database_connection import DatabaseManager
10
+ from .models import TimeSeriesData, TimeSeriesMeta, TimeSeriesSelector, RefData, TickerMapping, TimeSeriesScheduler
11
+ # Old SessionManager moved to recycle/ - use TimeSeriesSessionManager instead
12
+ from .generic_session import GenericSessionManager
13
+ from .time_series_session import TimeSeriesSessionManager
14
+ from .base import DatabaseBase
15
+ from .operations import SeriesOperationMixin, BloombergOperationsMixin
16
+ from .decorators import handle_series_operation_errors
17
+ from .utilities import update_threshold_daily_close, create_update_filter, parse_time_string
18
+ # TimeSeriesContext imported on demand to avoid circular imports
19
+
20
+ __all__ = [
21
+ "DatabaseManager",
22
+ "GenericSessionManager",
23
+ "TimeSeriesSessionManager",
24
+ "TimeSeriesData",
25
+ "TimeSeriesMeta",
26
+ "TimeSeriesSelector",
27
+ "RefData",
28
+ "TickerMapping",
29
+ "TimeSeriesScheduler",
30
+ "DatabaseBase",
31
+ "SeriesOperationMixin",
32
+ "BloombergOperationsMixin",
33
+ "handle_series_operation_errors",
34
+ "update_threshold_daily_close",
35
+ "create_update_filter",
36
+ "parse_time_string",
37
+ ]
@@ -0,0 +1,116 @@
1
+ """
2
+ Database base functionality for MasWorks library.
3
+
4
+ Provides shared database infrastructure including connection management,
5
+ feed initialization, and common utilities used by both reader and admin classes.
6
+ """
7
+ import logging
8
+ from typing import Dict, Any
9
+
10
+ from masworks.config import Config
11
+ from masworks.utils.database_connection import DatabaseManager
12
+ from .time_series_session import TimeSeriesSessionManager
13
+ from masworks.feeds import BridgeFeed, FredFeed, DatastreamFeed
14
+ from masworks.constants import SOURCE_ALIASES
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ class DatabaseBase:
20
+ """
21
+ Base class for database operations in MasWorks.
22
+
23
+ Provides shared infrastructure including database connections and common utilities.
24
+ This class is not meant to be used directly - use MasTimeSeriesReader for read
25
+ operations or MasTimeSeriesAdmin for write operations.
26
+
27
+ Note: Feed operations are only available in MasTimeSeriesAdmin.
28
+ """
29
+
30
+ def __init__(self, config: Config, auto_create_tables: bool = True):
31
+ """
32
+ Initialize DatabaseBase.
33
+
34
+ Args:
35
+ config: Configuration object with database and feed settings
36
+ auto_create_tables: If True, automatically create database tables if they don't exist
37
+ """
38
+ self.config = config
39
+
40
+ # Initialize database components
41
+ self.db_manager = DatabaseManager(config.database)
42
+
43
+ # Check if this is a versioned class
44
+ # Versioned classes should use create_version() for versioned tables,
45
+ # not auto-create current tables
46
+ is_versioned = self._is_versioned_class()
47
+
48
+ # Create tables if requested and they don't exist
49
+ # Skip for versioned classes (they should use create_version() explicitly)
50
+ if auto_create_tables and not is_versioned:
51
+ self.db_manager.create_tables_if_not_exist()
52
+
53
+ # Session manager will be attached by subclasses as needed
54
+ self.session_manager = None
55
+
56
+ def _is_versioned_class(self) -> bool:
57
+ """
58
+ Check if this instance is a versioned class.
59
+
60
+ Uses lazy import to avoid circular dependency issues.
61
+
62
+ Returns:
63
+ True if this is a VersionedTimeSeriesBase subclass, False otherwise
64
+ """
65
+ try:
66
+ # Lazy import to avoid circular dependency
67
+ # Use absolute import path to avoid issues
68
+ from masworks.versioned_time_series_base import VersionedTimeSeriesBase
69
+ return isinstance(self, VersionedTimeSeriesBase)
70
+ except (ImportError, AttributeError):
71
+ # Fallback: check class name if import fails
72
+ # This handles edge cases but shouldn't normally be needed
73
+ class_name = self.__class__.__name__
74
+ return 'Versioned' in class_name and 'TimeSeries' in class_name
75
+
76
+ # Note: Feed initialization moved to TimeSeriesAdmin
77
+ # Feeds are only needed for admin operations, not read-only operations
78
+
79
+ def test_connection(self) -> bool:
80
+ """
81
+ Test database connection.
82
+
83
+ Returns:
84
+ True if connection is successful, False otherwise
85
+ """
86
+ return self.db_manager.test_connection()
87
+
88
+ def create_tables(self) -> bool:
89
+ """
90
+ Manually create all database tables.
91
+
92
+ Returns:
93
+ True if tables were created successfully, False otherwise
94
+ """
95
+ return self.db_manager.create_tables()
96
+
97
+ def create_tables_if_not_exist(self) -> bool:
98
+ """
99
+ Create database tables only if they don't already exist.
100
+
101
+ Returns:
102
+ True if tables exist or were created successfully, False otherwise
103
+ """
104
+ return self.db_manager.create_tables_if_not_exist()
105
+
106
+ def close(self):
107
+ """
108
+ Close database connections and cleanup resources.
109
+
110
+ Should be called when done with the instance to properly cleanup resources.
111
+ """
112
+ self.db_manager.close()
113
+
114
+ # Note: Feed-related methods (get_available_feeds, get_feed_info)
115
+ # have been moved to TimeSeriesAdmin where feeds are actually available.
116
+ # DatabaseBase only provides database operations, not feed operations.
@@ -0,0 +1,58 @@
1
+ """
2
+ Context manager for MasWorks time series operations.
3
+
4
+ This module provides TimeSeriesContext which is a convenient context manager
5
+ for time series operations, specifically designed for MasWorks time series
6
+ database operations.
7
+ """
8
+ from typing import Optional, Union
9
+
10
+ from masworks.config import Config
11
+
12
+
13
+ class TimeSeriesContext:
14
+ """
15
+ Context manager wrapper for time series operations.
16
+
17
+ Provides a convenient way to use TimeSeriesReader or TimeSeriesAdmin with automatic
18
+ resource cleanup using Python's 'with' statement.
19
+
20
+ Example:
21
+ # For read operations
22
+ with TimeSeriesContext(config, mode="reader") as reader:
23
+ data = reader.get_series_data(ticker='SPX Index', field='PX_LAST')
24
+
25
+ # For admin operations
26
+ with TimeSeriesContext(config, mode="admin") as admin:
27
+ result = admin.upsert_series('SPX Index', 'PX_LAST')
28
+ """
29
+
30
+ def __init__(self, config: Config, mode: str = "admin"):
31
+ """
32
+ Initialize TimeSeriesContext.
33
+
34
+ Args:
35
+ config: Configuration object with database and feed settings
36
+ mode: Operation mode - "reader" for read-only, "admin" for full access
37
+ """
38
+ self.config = config
39
+ self.mode = mode
40
+ self._instance: Optional[Union['MasTimeSeriesReader', 'MasTimeSeriesAdmin']] = None
41
+
42
+ def __enter__(self) -> Union['MasTimeSeriesReader', 'MasTimeSeriesAdmin']:
43
+ """Enter context manager and initialize the appropriate class."""
44
+ # Import here to avoid circular imports
45
+ from masworks.mas_time_series_reader import MasTimeSeriesReader
46
+ from masworks.mas_time_series_admin import MasTimeSeriesAdmin
47
+
48
+ if self.mode == "reader":
49
+ self._instance = MasTimeSeriesReader(self.config)
50
+ else:
51
+ self._instance = MasTimeSeriesAdmin(self.config)
52
+ return self._instance
53
+
54
+ def __exit__(self, exc_type, exc_val, exc_tb) -> bool:
55
+ """Exit context manager and cleanup resources."""
56
+ if self._instance:
57
+ self._instance.close()
58
+ return False
@@ -0,0 +1,37 @@
1
+ """
2
+ Database decorators for MasWorks library.
3
+ """
4
+ import logging
5
+ from functools import wraps
6
+ from typing import Any, Callable
7
+
8
+ from ..feeds import DataFeedError
9
+ from ..constants import STATUS_CODES
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+
14
+ def handle_series_operation_errors(func: Callable) -> Callable:
15
+ """
16
+ Decorator to standardize error handling for series operations.
17
+
18
+ Args:
19
+ func: The function to wrap
20
+
21
+ Returns:
22
+ Wrapped function with error handling
23
+ """
24
+ @wraps(func)
25
+ def wrapper(self, *args, **kwargs) -> Any:
26
+ try:
27
+ return func(self, *args, **kwargs)
28
+ except DataFeedError as e:
29
+ ticker = kwargs.get('ticker', args[0] if args else 'unknown')
30
+ logger.error(f"Data feed error for {ticker}: {e}")
31
+ return {"status": STATUS_CODES['FEED_ERROR'], "error": str(e)}
32
+ except Exception as e:
33
+ ticker = kwargs.get('ticker', args[0] if args else 'unknown')
34
+ operation = func.__name__.replace('_series', '')
35
+ logger.error(f"{operation.title()} failed for {ticker}: {e}")
36
+ return {"status": STATUS_CODES['ERROR'], "error": str(e)}
37
+ return wrapper
@@ -0,0 +1,228 @@
1
+ """
2
+ Generic session management for database operations.
3
+
4
+ This module provides a generic SessionManager that can work with any database
5
+ schema and table structure, without hardcoded dependencies.
6
+ """
7
+
8
+ import logging
9
+ from typing import Optional, List, Dict, Any
10
+ import pandas as pd
11
+ from sqlalchemy import text
12
+ from sqlalchemy.exc import SQLAlchemyError
13
+ from contextlib import contextmanager
14
+
15
+ from ..utils.session_base import BaseSessionManager
16
+ from ..utils.database_connection import DatabaseManager
17
+
18
+ logger = logging.getLogger(__name__)
19
+
20
+
21
+ class GenericSessionManager(BaseSessionManager):
22
+ """
23
+ Generic session manager for database operations.
24
+
25
+ Provides generic database session management that can work with any
26
+ database schema and table structure without hardcoded dependencies.
27
+ """
28
+
29
+ def __init__(self, db_manager: DatabaseManager, chunk_size: int = 5000):
30
+ """
31
+ Initialize GenericSessionManager.
32
+
33
+ Args:
34
+ db_manager: DatabaseManager instance
35
+ chunk_size: Size of chunks for bulk operations
36
+ """
37
+ super().__init__(db_manager, chunk_size)
38
+ # Track temp tables for reuse within session
39
+ self._temp_tables = {} # {table_name: connection_id}
40
+ self._conn = None
41
+
42
+ def _get_connection(self):
43
+ """
44
+ Get the current connection or create a new one.
45
+
46
+ Returns:
47
+ Database connection object
48
+ """
49
+ if self._conn is None:
50
+ self._conn = self.db_manager.engine.connect()
51
+ return self._conn
52
+
53
+ def _close_connection(self):
54
+ """Close the current connection if it exists."""
55
+ if self._conn is not None:
56
+ self._conn.close()
57
+ self._conn = None
58
+
59
+ def _get_schema(self) -> str:
60
+ """Get the database schema from config."""
61
+ return self.db_manager.config.schema
62
+
63
+ def _get_or_create_temp_table(self, temp_table: str, source_table: str = None, schema: str = None) -> str:
64
+ """
65
+ Get existing temp table or create new one for reuse within session.
66
+
67
+ Args:
68
+ temp_table: Name of temp table to create/reuse
69
+ source_table: Source table to copy structure from (if None, defaults to "TS_Data")
70
+ schema: Database schema name (if None, uses config schema)
71
+
72
+ Returns:
73
+ Name of temp table to use
74
+ """
75
+ # Generate temp table name if not provided
76
+ if temp_table is None:
77
+ import time
78
+ import random
79
+ timestamp = int(time.time() * 1000) % 10000
80
+ random_suffix = random.randint(10, 99)
81
+ temp_table = f"#tmp_{timestamp}_{random_suffix}"
82
+
83
+ # Get connection
84
+ conn = self._get_connection()
85
+
86
+ # Get connection ID for tracking
87
+ conn_id = id(conn)
88
+
89
+ # Check if we can reuse an existing temp table
90
+ if temp_table in self._temp_tables and self._temp_tables[temp_table] == conn_id:
91
+ # Reuse existing temp table - just truncate it
92
+ try:
93
+ conn.execute(text(f"TRUNCATE TABLE {temp_table}"))
94
+ logger.debug(f"Reusing existing temp table: {temp_table}")
95
+ return temp_table
96
+ except Exception as e:
97
+ logger.warning(f"Failed to truncate temp table {temp_table}, will recreate: {e}")
98
+ # Fall through to create new table
99
+
100
+ # Get table name and schema from model if not provided
101
+ if source_table is None or schema is None:
102
+ # Default to TimeSeriesData table info, but allow override
103
+ if source_table is None:
104
+ source_table = "TS_Data" # Default table name
105
+
106
+ if schema is None:
107
+ schema = self._get_schema() # Use config schema
108
+
109
+ # Create new temp table
110
+ self.create_temp_table(conn, temp_table, source_table, schema)
111
+ self._temp_tables[temp_table] = conn_id
112
+ logger.debug(f"Created new temp table: {temp_table}")
113
+ return temp_table
114
+
115
+ def _get_or_create_temp_table_from_model(self, temp_table: str, model_class) -> str:
116
+ """
117
+ Get existing temp table or create new one using SQLAlchemy model information.
118
+
119
+ Args:
120
+ temp_table: Name of temp table to create/reuse
121
+ model_class: SQLAlchemy model class to get table name and schema from
122
+
123
+ Returns:
124
+ Name of temp table to use
125
+ """
126
+ # Extract table name and schema from model
127
+ source_table = model_class.__tablename__
128
+ schema = model_class.__table_args__["schema"]
129
+
130
+ # Use the main method with extracted parameters
131
+ return self._get_or_create_temp_table(temp_table, source_table, schema)
132
+
133
+ def _cleanup_temp_table(self, temp_table: str):
134
+ """
135
+ Clean up temp table, but keep it for reuse if possible.
136
+
137
+ Args:
138
+ temp_table: Name of temp table to clean up
139
+ """
140
+ try:
141
+ # Get connection
142
+ conn = self._get_connection()
143
+
144
+ # Debug: Check connection type
145
+ logger.debug(f"Cleaning up temp table {temp_table}, connection type: {type(conn)}")
146
+
147
+ # Check if connection has execute method
148
+ if not hasattr(conn, 'execute'):
149
+ logger.warning(f"Connection object {type(conn)} does not have execute method, skipping cleanup for {temp_table}")
150
+ return
151
+
152
+ # Try to truncate first (for reuse)
153
+ conn.execute(text(f"TRUNCATE TABLE {temp_table}"))
154
+ logger.debug(f"Truncated temp table for reuse: {temp_table}")
155
+ except Exception as e:
156
+ # If truncate fails, try to drop
157
+ try:
158
+ logger.debug(f"Truncate failed for {temp_table}: {e}, trying drop")
159
+ conn.execute(text(f"DROP TABLE {temp_table}"))
160
+ logger.debug(f"Dropped temp table: {temp_table}")
161
+ # Remove from tracking
162
+ if temp_table in self._temp_tables:
163
+ del self._temp_tables[temp_table]
164
+ except Exception as drop_error:
165
+ logger.warning(f"Could not clean up {temp_table}: {drop_error}")
166
+
167
+ def _cleanup_all_temp_tables(self):
168
+ """
169
+ Clean up all temp tables for the current connection.
170
+ """
171
+ # Get connection
172
+ conn = self._get_connection()
173
+ conn_id = id(conn)
174
+
175
+ tables_to_remove = []
176
+
177
+ for temp_table, tracked_conn_id in self._temp_tables.items():
178
+ if tracked_conn_id == conn_id:
179
+ try:
180
+ conn.execute(text(f"DROP TABLE {temp_table}"))
181
+ logger.debug(f"Dropped temp table: {temp_table}")
182
+ tables_to_remove.append(temp_table)
183
+ except Exception as e:
184
+ logger.warning(f"Failed to clean up temp table {temp_table}: {e}")
185
+
186
+ # Remove from tracking
187
+ for temp_table in tables_to_remove:
188
+ del self._temp_tables[temp_table]
189
+
190
+ @contextmanager
191
+ def bulk_operations_context(self):
192
+ """
193
+ Context manager for bulk operations that reuses connections and temp tables.
194
+
195
+ This reuses an existing connection if available, or creates a new one and keeps it open.
196
+ Optimized for sequences of operations where you want to reuse temp tables for better performance.
197
+
198
+ Example:
199
+ with session_manager.bulk_operations_context() as conn:
200
+ result1 = session_manager.some_operation(df1, conn=conn)
201
+ result2 = session_manager.some_operation(df2, conn=conn)
202
+ """
203
+ # If we already have a connection, use it
204
+ if self._conn is not None:
205
+ yield self._conn
206
+ else:
207
+ # Create new connection and keep it open
208
+ conn = self.db_manager.engine.connect()
209
+ try:
210
+ self._conn = conn
211
+ yield conn
212
+ finally:
213
+ # Don't close - let it stay in self._conn for future use
214
+ # Just clean up temp tables
215
+ self._cleanup_all_temp_tables()
216
+
217
+ def close(self):
218
+ """Close database connections and cleanup resources."""
219
+ # Clean up any remaining temp tables
220
+ if hasattr(self, '_temp_tables'):
221
+ self._cleanup_all_temp_tables()
222
+ self._temp_tables.clear()
223
+
224
+ # Close current connection
225
+ self._close_connection()
226
+
227
+ # Close base class resources
228
+ super().close()