pytermgui.file_loaders
Description
This module provides the library with the capability to load files into Widget-s.
It provides a FileLoader base class, which is then subclassed by various filetype-
specific parsers with their own parse
method. The job of this method is to take
the file contents as a string, and create a valid json tree out of it.
You can "run" a PTG YAML file by calling ptg -f <filename>
in your terminal.
To use any YAML related features, the optional dependency PyYAML is required.
Implementation details
The main method of these classes is load
, which takes a file-like object or a string,
parses it and returns a WidgetNamespace
instance. This can then be used to access all
custom Widget
definitions in the datafile.
This module highly depends on the serializer
module. Each file loader uses its own
Serializer
instance, but optionally take a pre-instantiated Serializer at construction.
As with that module, this one depends on it "knowing" all types of Widget-s you are loading.
If you have custom Widget subclass you would like to use in file-based definitions, use the
FileLoader.register
method, passing in your custom class as the sole argument.
File structure
Regardless of filetype, all loaded files must follow a specific structure:
root
|- config
| |_ custom global widget configuration
|
|- markup
| |_ custom markup definitions
|
|- boxes
| |_ custom box definitions
|
|_ widgets
|_ custom widget definitions
The loading follows the order config -> markup -> boxes -> widgets. It is not necessary to provide all sections.
Example of usage
# -- data.yaml --
markup:
label-style: '141 @61 bold'
boxes:
WINDOW_BOX: [
"left --- right",
"left x right",
"left --- right",
]
config:
Window:
styles:
border: '[@79]{item}'
box: SINGLE
Label:
styles:
value: '[label-style]{item}'
widgets:
MyWindow:
type: Window
box: WINDOW_BOX
widgets:
Label:
value: '[210 bold]This is a title'
Label: {}
Splitter:
widgets:
- Label:
parent_align: 0
value: 'This is an option'
- Button:
label: "Press me!"
Label: {}
Label:
value: '[label-style]{item}'
# -- loader.py --
import pytermgui as ptg
with ptg.YamlLoader() as loader, open("data.yaml", "r") as datafile:
namespace = loader.load(datafile)
with ptg.WindowManager() as manager:
manager.add(namespace.MyWindow)
manager.run()
# Alternatively, one could run `ptg -f "data.yaml"` to display all widgets defined.
# See `ptg -h`.
1""" 2Description 3=========== 4 5This module provides the library with the capability to load files into Widget-s. 6 7It provides a FileLoader base class, which is then subclassed by various filetype- 8specific parsers with their own `parse` method. The job of this method is to take 9the file contents as a string, and create a valid json tree out of it. 10 11You can "run" a PTG YAML file by calling `ptg -f <filename>` in your terminal. 12 13**To use any YAML related features, the optional dependency PyYAML is required.** 14 15 16Implementation details 17====================== 18 19The main method of these classes is `load`, which takes a file-like object or a string, 20parses it and returns a `WidgetNamespace` instance. This can then be used to access all 21custom `Widget` definitions in the datafile. 22 23This module highly depends on the `serializer` module. Each file loader uses its own 24`Serializer` instance, but optionally take a pre-instantiated Serializer at construction. 25As with that module, this one depends on it "knowing" all types of Widget-s you are loading. 26If you have custom Widget subclass you would like to use in file-based definitions, use the 27`FileLoader.register` method, passing in your custom class as the sole argument. 28 29 30File structure 31============== 32 33Regardless of filetype, all loaded files must follow a specific structure: 34 35``` 36root 37|- config 38| |_ custom global widget configuration 39| 40|- markup 41| |_ custom markup definitions 42| 43|- boxes 44| |_ custom box definitions 45| 46|_ widgets 47 |_ custom widget definitions 48``` 49 50The loading follows the order config -> markup -> boxes -> widgets. It is not necessary to 51provide all sections. 52 53 54Example of usage 55================ 56 57```yaml 58# -- data.yaml -- 59 60markup: 61 label-style: '141 @61 bold' 62 63boxes: 64 WINDOW_BOX: [ 65 "left --- right", 66 "left x right", 67 "left --- right", 68 ] 69 70config: 71 Window: 72 styles: 73 border: '[@79]{item}' 74 box: SINGLE 75 76 Label: 77 styles: 78 value: '[label-style]{item}' 79 80widgets: 81 MyWindow: 82 type: Window 83 box: WINDOW_BOX 84 widgets: 85 Label: 86 value: '[210 bold]This is a title' 87 88 Label: {} 89 90 Splitter: 91 widgets: 92 - Label: 93 parent_align: 0 94 value: 'This is an option' 95 96 - Button: 97 label: "Press me!" 98 99 Label: {} 100 Label: 101 value: '[label-style]{item}' 102``` 103 104 105```python3 106# -- loader.py -- 107 108import pytermgui as ptg 109 110with ptg.YamlLoader() as loader, open("data.yaml", "r") as datafile: 111 namespace = loader.load(datafile) 112 113with ptg.WindowManager() as manager: 114 manager.add(namespace.MyWindow) 115 manager.run() 116 117# Alternatively, one could run `ptg -f "data.yaml"` to display all widgets defined. 118# See `ptg -h`. 119``` 120 121""" 122 123from __future__ import annotations 124 125import json 126from abc import ABC, abstractmethod 127from dataclasses import dataclass, field 128from typing import IO, Any, Callable, Type 129 130from . import widgets as widgets_m 131from .markup import tim 132from .serializer import Serializer 133 134YAML_ERROR = None 135 136try: 137 import yaml 138except ImportError as import_error: 139 # yaml is explicitly checked to be None later 140 yaml = None # type: ignore 141 YAML_ERROR = import_error 142 143 144__all__ = ["WidgetNamespace", "FileLoader", "YamlLoader", "JsonLoader"] 145 146 147@dataclass 148class WidgetNamespace: 149 """Class to hold data on loaded namespace.""" 150 151 # No clue why `widgets` is seen as undefined here, 152 # but not in the code below. It only seems to happen 153 # in certain pylint configs as well. 154 config: dict[ 155 Type[widgets_m.Widget], dict[str, Any] # pylint: disable=undefined-variable 156 ] 157 widgets: dict[str, widgets_m.Widget] 158 boxes: dict[str, widgets_m.boxes.Box] = field(default_factory=dict) 159 160 @classmethod 161 def from_config(cls, data: dict[Any, Any], loader: FileLoader) -> WidgetNamespace: 162 """Creates a namespace from config data. 163 164 Args: 165 data: A dictionary of config data. 166 loader: The `FileLoader` instance that should be used. 167 168 Returns: 169 A new WidgetNamespace with the given config. 170 """ 171 172 namespace = WidgetNamespace({}, {}) 173 for name, config in data.items(): 174 obj = loader.serializer.known_widgets.get(name) 175 if obj is None: 176 raise KeyError(f"Unknown widget type {name}.") 177 178 namespace.config[obj] = { 179 "styles": obj.styles, 180 "chars": obj.chars.copy(), 181 } 182 183 for category, inner in config.items(): 184 value: str | widgets_m.styles.MarkupFormatter 185 186 if category not in namespace.config[obj]: 187 setattr(obj, category, inner) 188 continue 189 190 for key, value in inner.items(): 191 namespace.config[obj][category][key] = value 192 193 namespace.apply_config() 194 return namespace 195 196 @staticmethod 197 def _apply_section( 198 widget: Type[widgets_m.Widget], title: str, section: dict[str, str] 199 ) -> None: 200 """Applies configuration section to the widget.""" 201 202 for key, value in section.items(): 203 if title == "styles": 204 widget.set_style(key, value) 205 continue 206 207 widget.set_char(key, value) 208 209 def apply_to(self, widget: widgets_m.Widget) -> None: 210 """Applies namespace config to the widget. 211 212 Args: 213 widget: The widget in question. 214 """ 215 216 def _apply_sections( 217 data: dict[str, dict[str, str]], widget: widgets_m.Widget 218 ) -> None: 219 """Applies sections from data to the widget.""" 220 221 for title, section in data.items(): 222 self._apply_section(type(widget), title, section) 223 224 data = self.config.get(type(widget)) 225 if data is None: 226 return 227 228 _apply_sections(data, widget) 229 230 if hasattr(widget, "_widgets"): 231 for inner in widget: 232 inner_section = self.config.get(type(inner)) 233 234 if inner_section is None: 235 continue 236 237 _apply_sections(inner_section, inner) 238 239 def apply_config(self) -> None: 240 """Apply self.config to current namespace.""" 241 242 for widget, settings in self.config.items(): 243 for title, section in settings.items(): 244 self._apply_section(widget, title, section) 245 246 def __getattr__(self, attr: str) -> widgets_m.Widget: 247 """Get widget by name from widget list.""" 248 249 if attr in self.widgets: 250 return self.widgets[attr] 251 252 return self.__dict__[attr] 253 254 255class FileLoader(ABC): 256 """Base class for file loader objects. 257 258 These allow users to load pytermgui content from a specific filetype, 259 with each filetype having their own loaders. 260 261 To use custom widgets with children of this class, you need to call `FileLoader.register`.""" 262 263 serializer: Serializer 264 """Object-specific serializer instance. In order to use a specific, already created 265 instance you need to pass it on `FileLoader` construction.""" 266 267 @abstractmethod 268 def parse(self, data: str) -> dict[Any, Any]: 269 """Parses string into a dictionary used by `pytermgui.serializer.Serializer`. 270 271 This dictionary follows the structure defined above. 272 """ 273 274 def __init__(self, serializer: Serializer | None = None) -> None: 275 """Initialize FileLoader. 276 277 Args: 278 serializer: An optional `pytermgui.serializer.Serializer` instance. If not provided, one 279 is instantiated for every FileLoader instance. 280 """ 281 282 if serializer is None: 283 serializer = Serializer() 284 285 self.serializer = serializer 286 287 def __enter__(self) -> FileLoader: 288 """Starts context manager.""" 289 290 return self 291 292 def __exit__(self, _: Any, exception: Exception, __: Any) -> bool: 293 """Ends context manager.""" 294 295 if exception is not None: 296 raise exception 297 298 def register(self, cls: Type[widgets_m.Widget]) -> None: 299 """Registers a widget to the serializer. 300 301 Args: 302 cls: The widget type to register. 303 """ 304 305 self.serializer.register(cls) 306 307 def bind(self, name: str, method: Callable[..., Any]) -> None: 308 """Binds a name to a method. 309 310 Args: 311 name: The name of the method, as referenced in the loaded 312 files. 313 method: The callable to bind. 314 """ 315 316 self.serializer.bind(name, method) 317 318 def load_str(self, data: str) -> WidgetNamespace: 319 """Creates a `WidgetNamespace` from string data. 320 321 To parse the data, we use `FileLoader.parse`. To implement custom formats, 322 subclass `FileLoader` with your own `parse` implementation. 323 324 Args: 325 data: The data to parse. 326 327 Returns: 328 A WidgetNamespace created from the provided data. 329 """ 330 331 parsed = self.parse(data) 332 333 # Get & load config data 334 config_data = parsed.get("config") 335 if config_data is not None: 336 namespace = WidgetNamespace.from_config(config_data, loader=self) 337 else: 338 namespace = WidgetNamespace.from_config({}, loader=self) 339 340 # Create aliases 341 for key, value in (parsed.get("markup") or {}).items(): 342 tim.alias(key, value) 343 344 # Create boxes 345 for name, inner in (parsed.get("boxes") or {}).items(): 346 self.serializer.register_box(name, widgets_m.boxes.Box(inner)) 347 348 # Create widgets 349 for name, inner in (parsed.get("widgets") or {}).items(): 350 widget_type = inner.get("type") or name 351 352 box_name = inner.get("box") 353 354 box = None 355 if box_name is not None and box_name in namespace.boxes: 356 box = namespace.boxes[box_name] 357 del inner["box"] 358 359 try: 360 namespace.widgets[name] = self.serializer.from_dict( 361 inner, widget_type=widget_type 362 ) 363 except AttributeError as error: 364 raise ValueError( 365 f'Could not load "{name}" from data:\n{json.dumps(inner, indent=2)}' 366 ) from error 367 368 if box is not None: 369 namespace.widgets[name].box = box 370 371 return namespace 372 373 def load(self, data: str | IO) -> WidgetNamespace: 374 """Loads data from a string or a file. 375 376 When an IO object is passed, its data is extracted as a string. 377 This string can then be passed to `load_str`. 378 379 Args: 380 data: Either a string or file stream to load data from. 381 382 Returns: 383 A WidgetNamespace with the data loaded. 384 """ 385 386 if not isinstance(data, str): 387 data = data.read() 388 389 assert isinstance(data, str) 390 return self.load_str(data) 391 392 393class JsonLoader(FileLoader): 394 """JSON specific loader subclass.""" 395 396 def parse(self, data: str) -> dict[Any, Any]: 397 """Parse JSON str. 398 399 Args: 400 data: JSON formatted string. 401 402 Returns: 403 Loadable dictionary. 404 """ 405 406 return json.loads(data) 407 408 409class YamlLoader(FileLoader): 410 """YAML specific loader subclass.""" 411 412 def __init__(self, serializer: Serializer | None = None) -> None: 413 """Initialize object, check for installation of PyYAML.""" 414 415 if YAML_ERROR is not None: 416 raise RuntimeError( 417 "YAML implementation module not found. Please install `PyYAML` to use `YamlLoader`." 418 ) from YAML_ERROR 419 420 super().__init__() 421 422 def parse(self, data: str) -> dict[Any, Any]: 423 """Parse YAML str. 424 425 Args: 426 data: YAML formatted string. 427 428 Returns: 429 Loadable dictionary. 430 """ 431 432 assert yaml is not None 433 return yaml.safe_load(data)
148@dataclass 149class WidgetNamespace: 150 """Class to hold data on loaded namespace.""" 151 152 # No clue why `widgets` is seen as undefined here, 153 # but not in the code below. It only seems to happen 154 # in certain pylint configs as well. 155 config: dict[ 156 Type[widgets_m.Widget], dict[str, Any] # pylint: disable=undefined-variable 157 ] 158 widgets: dict[str, widgets_m.Widget] 159 boxes: dict[str, widgets_m.boxes.Box] = field(default_factory=dict) 160 161 @classmethod 162 def from_config(cls, data: dict[Any, Any], loader: FileLoader) -> WidgetNamespace: 163 """Creates a namespace from config data. 164 165 Args: 166 data: A dictionary of config data. 167 loader: The `FileLoader` instance that should be used. 168 169 Returns: 170 A new WidgetNamespace with the given config. 171 """ 172 173 namespace = WidgetNamespace({}, {}) 174 for name, config in data.items(): 175 obj = loader.serializer.known_widgets.get(name) 176 if obj is None: 177 raise KeyError(f"Unknown widget type {name}.") 178 179 namespace.config[obj] = { 180 "styles": obj.styles, 181 "chars": obj.chars.copy(), 182 } 183 184 for category, inner in config.items(): 185 value: str | widgets_m.styles.MarkupFormatter 186 187 if category not in namespace.config[obj]: 188 setattr(obj, category, inner) 189 continue 190 191 for key, value in inner.items(): 192 namespace.config[obj][category][key] = value 193 194 namespace.apply_config() 195 return namespace 196 197 @staticmethod 198 def _apply_section( 199 widget: Type[widgets_m.Widget], title: str, section: dict[str, str] 200 ) -> None: 201 """Applies configuration section to the widget.""" 202 203 for key, value in section.items(): 204 if title == "styles": 205 widget.set_style(key, value) 206 continue 207 208 widget.set_char(key, value) 209 210 def apply_to(self, widget: widgets_m.Widget) -> None: 211 """Applies namespace config to the widget. 212 213 Args: 214 widget: The widget in question. 215 """ 216 217 def _apply_sections( 218 data: dict[str, dict[str, str]], widget: widgets_m.Widget 219 ) -> None: 220 """Applies sections from data to the widget.""" 221 222 for title, section in data.items(): 223 self._apply_section(type(widget), title, section) 224 225 data = self.config.get(type(widget)) 226 if data is None: 227 return 228 229 _apply_sections(data, widget) 230 231 if hasattr(widget, "_widgets"): 232 for inner in widget: 233 inner_section = self.config.get(type(inner)) 234 235 if inner_section is None: 236 continue 237 238 _apply_sections(inner_section, inner) 239 240 def apply_config(self) -> None: 241 """Apply self.config to current namespace.""" 242 243 for widget, settings in self.config.items(): 244 for title, section in settings.items(): 245 self._apply_section(widget, title, section) 246 247 def __getattr__(self, attr: str) -> widgets_m.Widget: 248 """Get widget by name from widget list.""" 249 250 if attr in self.widgets: 251 return self.widgets[attr] 252 253 return self.__dict__[attr]
Class to hold data on loaded namespace.
161 @classmethod 162 def from_config(cls, data: dict[Any, Any], loader: FileLoader) -> WidgetNamespace: 163 """Creates a namespace from config data. 164 165 Args: 166 data: A dictionary of config data. 167 loader: The `FileLoader` instance that should be used. 168 169 Returns: 170 A new WidgetNamespace with the given config. 171 """ 172 173 namespace = WidgetNamespace({}, {}) 174 for name, config in data.items(): 175 obj = loader.serializer.known_widgets.get(name) 176 if obj is None: 177 raise KeyError(f"Unknown widget type {name}.") 178 179 namespace.config[obj] = { 180 "styles": obj.styles, 181 "chars": obj.chars.copy(), 182 } 183 184 for category, inner in config.items(): 185 value: str | widgets_m.styles.MarkupFormatter 186 187 if category not in namespace.config[obj]: 188 setattr(obj, category, inner) 189 continue 190 191 for key, value in inner.items(): 192 namespace.config[obj][category][key] = value 193 194 namespace.apply_config() 195 return namespace
Creates a namespace from config data.
Args
- data: A dictionary of config data.
- loader: The
FileLoader
instance that should be used.
Returns
A new WidgetNamespace with the given config.
210 def apply_to(self, widget: widgets_m.Widget) -> None: 211 """Applies namespace config to the widget. 212 213 Args: 214 widget: The widget in question. 215 """ 216 217 def _apply_sections( 218 data: dict[str, dict[str, str]], widget: widgets_m.Widget 219 ) -> None: 220 """Applies sections from data to the widget.""" 221 222 for title, section in data.items(): 223 self._apply_section(type(widget), title, section) 224 225 data = self.config.get(type(widget)) 226 if data is None: 227 return 228 229 _apply_sections(data, widget) 230 231 if hasattr(widget, "_widgets"): 232 for inner in widget: 233 inner_section = self.config.get(type(inner)) 234 235 if inner_section is None: 236 continue 237 238 _apply_sections(inner_section, inner)
Applies namespace config to the widget.
Args
- widget: The widget in question.
240 def apply_config(self) -> None: 241 """Apply self.config to current namespace.""" 242 243 for widget, settings in self.config.items(): 244 for title, section in settings.items(): 245 self._apply_section(widget, title, section)
Apply self.config to current namespace.
256class FileLoader(ABC): 257 """Base class for file loader objects. 258 259 These allow users to load pytermgui content from a specific filetype, 260 with each filetype having their own loaders. 261 262 To use custom widgets with children of this class, you need to call `FileLoader.register`.""" 263 264 serializer: Serializer 265 """Object-specific serializer instance. In order to use a specific, already created 266 instance you need to pass it on `FileLoader` construction.""" 267 268 @abstractmethod 269 def parse(self, data: str) -> dict[Any, Any]: 270 """Parses string into a dictionary used by `pytermgui.serializer.Serializer`. 271 272 This dictionary follows the structure defined above. 273 """ 274 275 def __init__(self, serializer: Serializer | None = None) -> None: 276 """Initialize FileLoader. 277 278 Args: 279 serializer: An optional `pytermgui.serializer.Serializer` instance. If not provided, one 280 is instantiated for every FileLoader instance. 281 """ 282 283 if serializer is None: 284 serializer = Serializer() 285 286 self.serializer = serializer 287 288 def __enter__(self) -> FileLoader: 289 """Starts context manager.""" 290 291 return self 292 293 def __exit__(self, _: Any, exception: Exception, __: Any) -> bool: 294 """Ends context manager.""" 295 296 if exception is not None: 297 raise exception 298 299 def register(self, cls: Type[widgets_m.Widget]) -> None: 300 """Registers a widget to the serializer. 301 302 Args: 303 cls: The widget type to register. 304 """ 305 306 self.serializer.register(cls) 307 308 def bind(self, name: str, method: Callable[..., Any]) -> None: 309 """Binds a name to a method. 310 311 Args: 312 name: The name of the method, as referenced in the loaded 313 files. 314 method: The callable to bind. 315 """ 316 317 self.serializer.bind(name, method) 318 319 def load_str(self, data: str) -> WidgetNamespace: 320 """Creates a `WidgetNamespace` from string data. 321 322 To parse the data, we use `FileLoader.parse`. To implement custom formats, 323 subclass `FileLoader` with your own `parse` implementation. 324 325 Args: 326 data: The data to parse. 327 328 Returns: 329 A WidgetNamespace created from the provided data. 330 """ 331 332 parsed = self.parse(data) 333 334 # Get & load config data 335 config_data = parsed.get("config") 336 if config_data is not None: 337 namespace = WidgetNamespace.from_config(config_data, loader=self) 338 else: 339 namespace = WidgetNamespace.from_config({}, loader=self) 340 341 # Create aliases 342 for key, value in (parsed.get("markup") or {}).items(): 343 tim.alias(key, value) 344 345 # Create boxes 346 for name, inner in (parsed.get("boxes") or {}).items(): 347 self.serializer.register_box(name, widgets_m.boxes.Box(inner)) 348 349 # Create widgets 350 for name, inner in (parsed.get("widgets") or {}).items(): 351 widget_type = inner.get("type") or name 352 353 box_name = inner.get("box") 354 355 box = None 356 if box_name is not None and box_name in namespace.boxes: 357 box = namespace.boxes[box_name] 358 del inner["box"] 359 360 try: 361 namespace.widgets[name] = self.serializer.from_dict( 362 inner, widget_type=widget_type 363 ) 364 except AttributeError as error: 365 raise ValueError( 366 f'Could not load "{name}" from data:\n{json.dumps(inner, indent=2)}' 367 ) from error 368 369 if box is not None: 370 namespace.widgets[name].box = box 371 372 return namespace 373 374 def load(self, data: str | IO) -> WidgetNamespace: 375 """Loads data from a string or a file. 376 377 When an IO object is passed, its data is extracted as a string. 378 This string can then be passed to `load_str`. 379 380 Args: 381 data: Either a string or file stream to load data from. 382 383 Returns: 384 A WidgetNamespace with the data loaded. 385 """ 386 387 if not isinstance(data, str): 388 data = data.read() 389 390 assert isinstance(data, str) 391 return self.load_str(data)
Base class for file loader objects.
These allow users to load pytermgui content from a specific filetype, with each filetype having their own loaders.
To use custom widgets with children of this class, you need to call FileLoader.register
.
275 def __init__(self, serializer: Serializer | None = None) -> None: 276 """Initialize FileLoader. 277 278 Args: 279 serializer: An optional `pytermgui.serializer.Serializer` instance. If not provided, one 280 is instantiated for every FileLoader instance. 281 """ 282 283 if serializer is None: 284 serializer = Serializer() 285 286 self.serializer = serializer
Initialize FileLoader.
Args
- serializer: An optional
pytermgui.serializer.Serializer
instance. If not provided, one is instantiated for every FileLoader instance.
Object-specific serializer instance. In order to use a specific, already created
instance you need to pass it on FileLoader
construction.
268 @abstractmethod 269 def parse(self, data: str) -> dict[Any, Any]: 270 """Parses string into a dictionary used by `pytermgui.serializer.Serializer`. 271 272 This dictionary follows the structure defined above. 273 """
Parses string into a dictionary used by pytermgui.serializer.Serializer
.
This dictionary follows the structure defined above.
299 def register(self, cls: Type[widgets_m.Widget]) -> None: 300 """Registers a widget to the serializer. 301 302 Args: 303 cls: The widget type to register. 304 """ 305 306 self.serializer.register(cls)
Registers a widget to the serializer.
Args
- cls: The widget type to register.
308 def bind(self, name: str, method: Callable[..., Any]) -> None: 309 """Binds a name to a method. 310 311 Args: 312 name: The name of the method, as referenced in the loaded 313 files. 314 method: The callable to bind. 315 """ 316 317 self.serializer.bind(name, method)
Binds a name to a method.
Args
- name: The name of the method, as referenced in the loaded files.
- method: The callable to bind.
319 def load_str(self, data: str) -> WidgetNamespace: 320 """Creates a `WidgetNamespace` from string data. 321 322 To parse the data, we use `FileLoader.parse`. To implement custom formats, 323 subclass `FileLoader` with your own `parse` implementation. 324 325 Args: 326 data: The data to parse. 327 328 Returns: 329 A WidgetNamespace created from the provided data. 330 """ 331 332 parsed = self.parse(data) 333 334 # Get & load config data 335 config_data = parsed.get("config") 336 if config_data is not None: 337 namespace = WidgetNamespace.from_config(config_data, loader=self) 338 else: 339 namespace = WidgetNamespace.from_config({}, loader=self) 340 341 # Create aliases 342 for key, value in (parsed.get("markup") or {}).items(): 343 tim.alias(key, value) 344 345 # Create boxes 346 for name, inner in (parsed.get("boxes") or {}).items(): 347 self.serializer.register_box(name, widgets_m.boxes.Box(inner)) 348 349 # Create widgets 350 for name, inner in (parsed.get("widgets") or {}).items(): 351 widget_type = inner.get("type") or name 352 353 box_name = inner.get("box") 354 355 box = None 356 if box_name is not None and box_name in namespace.boxes: 357 box = namespace.boxes[box_name] 358 del inner["box"] 359 360 try: 361 namespace.widgets[name] = self.serializer.from_dict( 362 inner, widget_type=widget_type 363 ) 364 except AttributeError as error: 365 raise ValueError( 366 f'Could not load "{name}" from data:\n{json.dumps(inner, indent=2)}' 367 ) from error 368 369 if box is not None: 370 namespace.widgets[name].box = box 371 372 return namespace
Creates a WidgetNamespace
from string data.
To parse the data, we use FileLoader.parse
. To implement custom formats,
subclass FileLoader
with your own parse
implementation.
Args
- data: The data to parse.
Returns
A WidgetNamespace created from the provided data.
374 def load(self, data: str | IO) -> WidgetNamespace: 375 """Loads data from a string or a file. 376 377 When an IO object is passed, its data is extracted as a string. 378 This string can then be passed to `load_str`. 379 380 Args: 381 data: Either a string or file stream to load data from. 382 383 Returns: 384 A WidgetNamespace with the data loaded. 385 """ 386 387 if not isinstance(data, str): 388 data = data.read() 389 390 assert isinstance(data, str) 391 return self.load_str(data)
Loads data from a string or a file.
When an IO object is passed, its data is extracted as a string.
This string can then be passed to load_str
.
Args
- data: Either a string or file stream to load data from.
Returns
A WidgetNamespace with the data loaded.
410class YamlLoader(FileLoader): 411 """YAML specific loader subclass.""" 412 413 def __init__(self, serializer: Serializer | None = None) -> None: 414 """Initialize object, check for installation of PyYAML.""" 415 416 if YAML_ERROR is not None: 417 raise RuntimeError( 418 "YAML implementation module not found. Please install `PyYAML` to use `YamlLoader`." 419 ) from YAML_ERROR 420 421 super().__init__() 422 423 def parse(self, data: str) -> dict[Any, Any]: 424 """Parse YAML str. 425 426 Args: 427 data: YAML formatted string. 428 429 Returns: 430 Loadable dictionary. 431 """ 432 433 assert yaml is not None 434 return yaml.safe_load(data)
YAML specific loader subclass.
413 def __init__(self, serializer: Serializer | None = None) -> None: 414 """Initialize object, check for installation of PyYAML.""" 415 416 if YAML_ERROR is not None: 417 raise RuntimeError( 418 "YAML implementation module not found. Please install `PyYAML` to use `YamlLoader`." 419 ) from YAML_ERROR 420 421 super().__init__()
Initialize object, check for installation of PyYAML.
423 def parse(self, data: str) -> dict[Any, Any]: 424 """Parse YAML str. 425 426 Args: 427 data: YAML formatted string. 428 429 Returns: 430 Loadable dictionary. 431 """ 432 433 assert yaml is not None 434 return yaml.safe_load(data)
Parse YAML str.
Args
- data: YAML formatted string.
Returns
Loadable dictionary.
Inherited Members
394class JsonLoader(FileLoader): 395 """JSON specific loader subclass.""" 396 397 def parse(self, data: str) -> dict[Any, Any]: 398 """Parse JSON str. 399 400 Args: 401 data: JSON formatted string. 402 403 Returns: 404 Loadable dictionary. 405 """ 406 407 return json.loads(data)
JSON specific loader subclass.
397 def parse(self, data: str) -> dict[Any, Any]: 398 """Parse JSON str. 399 400 Args: 401 data: JSON formatted string. 402 403 Returns: 404 Loadable dictionary. 405 """ 406 407 return json.loads(data)
Parse JSON str.
Args
- data: JSON formatted string.
Returns
Loadable dictionary.