File size: 9,453 Bytes
27762e4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
"""
Application Context Module

This module provides a centralized context object for the StingrayExplorer dashboard
that encapsulates all UI containers and state management.

The AppContext class solves the "container passing anti-pattern" by providing a
single object that contains references to all UI containers and the state manager,
eliminating the need to pass 7-9 container parameters to every function.

Benefits:
- Reduces function signatures from 9 parameters to 1
- Centralizes UI container management
- Makes code more maintainable and testable
- Easier to add new containers without updating all function signatures
- Provides a clean API for container updates

Example:
    >>> from utils.app_context import AppContext
    >>> context = AppContext()
    >>> context.update_container('output_box', create_output("Loading..."))
    >>> data = context.state.get_event_data()
"""

import panel as pn
from typing import Dict, Any, Optional
from utils.state_manager import state_manager
from services import ServiceRegistry


class AppContext:
    """
    Application context that encapsulates all UI containers, state management, and services.

    This class provides a centralized way to access and update UI containers,
    application state, and business logic services throughout the dashboard.

    Attributes:
        state (StateManager): The application state manager
        services (ServiceRegistry): Registry of all business logic services
        containers (Dict[str, pn.viewable.Viewable]): Dictionary of UI containers

    Example:
        >>> context = AppContext()
        >>> context.update_container('header', create_home_header())
        >>> result = context.services.data.load_event_list(...)
        >>> header = context.get_container('header')
    """

    def __init__(self):
        """Initialize the AppContext with state manager, services, and empty containers."""
        # Reference to the global state manager
        self.state = state_manager

        # Initialize service registry with state manager
        # Services provide all business logic operations
        self.services = ServiceRegistry(state_manager)

        # Dictionary of all UI containers
        # Keys are container names, values are Panel viewable objects
        self.containers: Dict[str, Any] = {}

        # Container metadata for debugging and validation
        self._container_metadata: Dict[str, Dict[str, Any]] = {}

    def register_container(self, name: str, container: Any, metadata: Optional[Dict[str, Any]] = None) -> None:
        """
        Register a UI container with the context.

        Args:
            name (str): Unique identifier for the container
            container: Panel viewable object (Column, Row, FlexBox, etc.)
            metadata (Dict, optional): Additional metadata about the container

        Raises:
            ValueError: If container name is empty or already exists

        Example:
            >>> context.register_container('header', pn.Column())
            >>> context.register_container('main_area', pn.Column(), {'purpose': 'main workspace'})
        """
        if not name or not name.strip():
            raise ValueError("Container name cannot be empty")

        if name in self.containers:
            raise ValueError(f"Container '{name}' is already registered")

        self.containers[name] = container

        # Store metadata
        if metadata is None:
            metadata = {}
        metadata['registered_at'] = pn.state.as_cached('current_time', lambda: str(pn.pane.Markdown('')))
        self._container_metadata[name] = metadata

    def get_container(self, name: str) -> Optional[Any]:
        """
        Get a container by name.

        Args:
            name (str): Name of the container to retrieve

        Returns:
            Panel viewable object or None if not found

        Example:
            >>> header = context.get_container('header')
            >>> if header is not None:
            ...     header.objects = [new_content]
        """
        return self.containers.get(name)

    def update_container(self, name: str, content: Any) -> bool:
        """
        Update the content of a container.

        This is the preferred method for updating containers as it uses
        Panel's official API (container.objects = [content]) instead of
        the slice assignment pattern (container[:] = [content]).

        Args:
            name (str): Name of the container to update
            content: New content to set (can be single item or list)

        Returns:
            bool: True if updated successfully, False if container not found

        Example:
            >>> context.update_container('output_box', create_output("Success!"))
            >>> context.update_container('plots', [plot1, plot2])
        """
        container = self.containers.get(name)

        if container is None:
            return False

        # Ensure content is a list
        if not isinstance(content, list):
            content = [content]

        # Use Panel's official API for updating container content
        container.objects = content

        return True

    def append_to_container(self, name: str, content: Any) -> bool:
        """
        Append content to a container without replacing existing content.

        Args:
            name (str): Name of the container
            content: Content to append

        Returns:
            bool: True if appended successfully, False if container not found

        Example:
            >>> context.append_to_container('plots', new_plot)
        """
        container = self.containers.get(name)

        if container is None:
            return False

        container.append(content)
        return True

    def clear_container(self, name: str) -> bool:
        """
        Clear all content from a container.

        Args:
            name (str): Name of the container to clear

        Returns:
            bool: True if cleared successfully, False if container not found

        Example:
            >>> context.clear_container('output_box')
        """
        container = self.containers.get(name)

        if container is None:
            return False

        container.clear()
        return True

    def has_container(self, name: str) -> bool:
        """
        Check if a container exists.

        Args:
            name (str): Name of the container

        Returns:
            bool: True if container exists, False otherwise

        Example:
            >>> if context.has_container('header'):
            ...     print("Header container exists")
        """
        return name in self.containers

    def get_container_names(self) -> list:
        """
        Get list of all registered container names.

        Returns:
            List[str]: List of container names

        Example:
            >>> names = context.get_container_names()
            >>> print(names)  # ['header', 'main_area', 'output_box', ...]
        """
        return list(self.containers.keys())

    def get_container_metadata(self, name: str) -> Optional[Dict[str, Any]]:
        """
        Get metadata for a container.

        Args:
            name (str): Name of the container

        Returns:
            Dict with metadata or None if not found

        Example:
            >>> metadata = context.get_container_metadata('header')
        """
        return self._container_metadata.get(name)

    def unregister_container(self, name: str) -> bool:
        """
        Unregister a container from the context.

        Args:
            name (str): Name of the container to unregister

        Returns:
            bool: True if unregistered, False if not found

        Example:
            >>> context.unregister_container('old_container')
        """
        if name in self.containers:
            del self.containers[name]
            if name in self._container_metadata:
                del self._container_metadata[name]
            return True
        return False

    def get_info(self) -> Dict[str, Any]:
        """
        Get information about the application context.

        Returns:
            Dict with context information including container count, state stats, services, etc.

        Example:
            >>> info = context.get_info()
            >>> print(f"Total containers: {info['container_count']}")
        """
        return {
            'container_count': len(self.containers),
            'container_names': self.get_container_names(),
            'state_stats': self.state.get_stats(),
            'services': {
                'data': 'DataService',
                'lightcurve': 'LightcurveService',
                'spectrum': 'SpectrumService',
                'timing': 'TimingService',
                'export': 'ExportService',
            }
        }

    def __repr__(self) -> str:
        """String representation of AppContext."""
        return (
            f"AppContext(containers={len(self.containers)}, "
            f"state={repr(self.state)})"
        )


# =============================================================================
# Singleton Instance (Optional)
# =============================================================================

# You can create a singleton instance if needed, but for testing purposes
# it's often better to create instances explicitly in the main application
# app_context = AppContext()