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 125from typing import Any, Type, IO, Callable 126from dataclasses import dataclass, field 127from abc import abstractmethod, ABC 128 129import json 130 131from . import widgets as widgets_m 132from .parser import markup 133from .serializer import Serializer 134 135 136YAML_ERROR = None 137 138try: 139 import yaml 140except ImportError as import_error: 141 # yaml is explicitly checked to be None later 142 yaml = None # type: ignore 143 YAML_ERROR = import_error 144 145 146__all__ = ["WidgetNamespace", "FileLoader", "YamlLoader", "JsonLoader"] 147 148 149@dataclass 150class WidgetNamespace: 151 """Class to hold data on loaded namespace.""" 152 153 # No clue why `widgets` is seen as undefined here, 154 # but not in the code below. It only seems to happen 155 # in certain pylint configs as well. 156 config: dict[ 157 Type[widgets_m.Widget], dict[str, Any] # pylint: disable=undefined-variable 158 ] 159 widgets: dict[str, widgets_m.Widget] 160 boxes: dict[str, widgets_m.boxes.Box] = field(default_factory=dict) 161 162 @classmethod 163 def from_config(cls, data: dict[Any, Any], loader: FileLoader) -> WidgetNamespace: 164 """Creates a namespace from config data. 165 166 Args: 167 data: A dictionary of config data. 168 loader: The `FileLoader` instance that should be used. 169 170 Returns: 171 A new WidgetNamespace with the given config. 172 """ 173 174 namespace = WidgetNamespace({}, {}) 175 for name, config in data.items(): 176 obj = loader.serializer.known_widgets.get(name) 177 if obj is None: 178 raise KeyError(f"Unknown widget type {name}.") 179 180 namespace.config[obj] = { 181 "styles": obj.styles, 182 "chars": obj.chars.copy(), 183 } 184 185 for category, inner in config.items(): 186 value: str | widgets_m.styles.MarkupFormatter 187 188 if category not in namespace.config[obj]: 189 setattr(obj, category, inner) 190 continue 191 192 for key, value in inner.items(): 193 namespace.config[obj][category][key] = value 194 195 namespace.apply_config() 196 return namespace 197 198 @staticmethod 199 def _apply_section( 200 widget: Type[widgets_m.Widget], title: str, section: dict[str, str] 201 ) -> None: 202 """Applies configuration section to the widget.""" 203 204 for key, value in section.items(): 205 if title == "styles": 206 widget.set_style(key, value) 207 continue 208 209 widget.set_char(key, value) 210 211 def apply_to(self, widget: widgets_m.Widget) -> None: 212 """Applies namespace config to the widget. 213 214 Args: 215 widget: The widget in question. 216 """ 217 218 def _apply_sections( 219 data: dict[str, dict[str, str]], widget: widgets_m.Widget 220 ) -> None: 221 """Applies sections from data to the widget.""" 222 223 for title, section in data.items(): 224 self._apply_section(type(widget), title, section) 225 226 data = self.config.get(type(widget)) 227 if data is None: 228 return 229 230 _apply_sections(data, widget) 231 232 if hasattr(widget, "_widgets"): 233 for inner in widget: 234 inner_section = self.config.get(type(inner)) 235 236 if inner_section is None: 237 continue 238 239 _apply_sections(inner_section, inner) 240 241 def apply_config(self) -> None: 242 """Apply self.config to current namespace.""" 243 244 for widget, settings in self.config.items(): 245 for title, section in settings.items(): 246 self._apply_section(widget, title, section) 247 248 def __getattr__(self, attr: str) -> widgets_m.Widget: 249 """Get widget by name from widget list.""" 250 251 if attr in self.widgets: 252 return self.widgets[attr] 253 254 return self.__dict__[attr] 255 256 257class FileLoader(ABC): 258 """Base class for file loader objects. 259 260 These allow users to load pytermgui content from a specific filetype, 261 with each filetype having their own loaders. 262 263 To use custom widgets with children of this class, you need to call `FileLoader.register`.""" 264 265 serializer: Serializer 266 """Object-specific serializer instance. In order to use a specific, already created 267 instance you need to pass it on `FileLoader` construction.""" 268 269 @abstractmethod 270 def parse(self, data: str) -> dict[Any, Any]: 271 """Parses string into a dictionary used by `pytermgui.serializer.Serializer`. 272 273 This dictionary follows the structure defined above. 274 """ 275 276 def __init__(self, serializer: Serializer | None = None) -> None: 277 """Initialize FileLoader. 278 279 Args: 280 serializer: An optional `pytermgui.serializer.Serializer` instance. If not provided, one 281 is instantiated for every FileLoader instance. 282 """ 283 284 if serializer is None: 285 serializer = Serializer() 286 287 self.serializer = serializer 288 289 def __enter__(self) -> FileLoader: 290 """Starts context manager.""" 291 292 return self 293 294 def __exit__(self, _: Any, exception: Exception, __: Any) -> bool: 295 """Ends context manager.""" 296 297 if exception is not None: 298 raise exception 299 300 def register(self, cls: Type[widgets_m.Widget]) -> None: 301 """Registers a widget to the serializer. 302 303 Args: 304 cls: The widget type to register. 305 """ 306 307 self.serializer.register(cls) 308 309 def bind(self, name: str, method: Callable[..., Any]) -> None: 310 """Binds a name to a method. 311 312 Args: 313 name: The name of the method, as referenced in the loaded 314 files. 315 method: The callable to bind. 316 """ 317 318 self.serializer.bind(name, method) 319 320 def load_str(self, data: str) -> WidgetNamespace: 321 """Creates a `WidgetNamespace` from string data. 322 323 To parse the data, we use `FileLoader.parse`. To implement custom formats, 324 subclass `FileLoader` with your own `parse` implementation. 325 326 Args: 327 data: The data to parse. 328 329 Returns: 330 A WidgetNamespace created from the provided data. 331 """ 332 333 parsed = self.parse(data) 334 335 # Get & load config data 336 config_data = parsed.get("config") 337 if config_data is not None: 338 namespace = WidgetNamespace.from_config(config_data, loader=self) 339 else: 340 namespace = WidgetNamespace.from_config({}, loader=self) 341 342 # Create aliases 343 for key, value in (parsed.get("markup") or {}).items(): 344 markup.alias(key, value) 345 346 # Create boxes 347 for name, inner in (parsed.get("boxes") or {}).items(): 348 self.serializer.register_box(name, widgets_m.boxes.Box(inner)) 349 350 # Create widgets 351 for name, inner in (parsed.get("widgets") or {}).items(): 352 widget_type = inner.get("type") or name 353 354 box_name = inner.get("box") 355 356 box = None 357 if box_name is not None and box_name in namespace.boxes: 358 box = namespace.boxes[box_name] 359 del inner["box"] 360 361 try: 362 namespace.widgets[name] = self.serializer.from_dict( 363 inner, widget_type=widget_type 364 ) 365 except AttributeError as error: 366 raise ValueError( 367 f'Could not load "{name}" from data:\n{json.dumps(inner, indent=2)}' 368 ) from error 369 370 if box is not None: 371 namespace.widgets[name].box = box 372 373 return namespace 374 375 def load(self, data: str | IO) -> WidgetNamespace: 376 """Loads data from a string or a file. 377 378 When an IO object is passed, its data is extracted as a string. 379 This string can then be passed to `load_str`. 380 381 Args: 382 data: Either a string or file stream to load data from. 383 384 Returns: 385 A WidgetNamespace with the data loaded. 386 """ 387 388 if not isinstance(data, str): 389 data = data.read() 390 391 assert isinstance(data, str) 392 return self.load_str(data) 393 394 395class JsonLoader(FileLoader): 396 """JSON specific loader subclass.""" 397 398 def parse(self, data: str) -> dict[Any, Any]: 399 """Parse JSON str. 400 401 Args: 402 data: JSON formatted string. 403 404 Returns: 405 Loadable dictionary. 406 """ 407 408 return json.loads(data) 409 410 411class YamlLoader(FileLoader): 412 """YAML specific loader subclass.""" 413 414 def __init__(self, serializer: Serializer | None = None) -> None: 415 """Initialize object, check for installation of PyYAML.""" 416 417 if YAML_ERROR is not None: 418 raise RuntimeError( 419 "YAML implementation module not found. Please install `PyYAML` to use `YamlLoader`." 420 ) from YAML_ERROR 421 422 super().__init__() 423 424 def parse(self, data: str) -> dict[Any, Any]: 425 """Parse YAML str. 426 427 Args: 428 data: YAML formatted string. 429 430 Returns: 431 Loadable dictionary. 432 """ 433 434 assert yaml is not None 435 return yaml.safe_load(data)
150@dataclass 151class WidgetNamespace: 152 """Class to hold data on loaded namespace.""" 153 154 # No clue why `widgets` is seen as undefined here, 155 # but not in the code below. It only seems to happen 156 # in certain pylint configs as well. 157 config: dict[ 158 Type[widgets_m.Widget], dict[str, Any] # pylint: disable=undefined-variable 159 ] 160 widgets: dict[str, widgets_m.Widget] 161 boxes: dict[str, widgets_m.boxes.Box] = field(default_factory=dict) 162 163 @classmethod 164 def from_config(cls, data: dict[Any, Any], loader: FileLoader) -> WidgetNamespace: 165 """Creates a namespace from config data. 166 167 Args: 168 data: A dictionary of config data. 169 loader: The `FileLoader` instance that should be used. 170 171 Returns: 172 A new WidgetNamespace with the given config. 173 """ 174 175 namespace = WidgetNamespace({}, {}) 176 for name, config in data.items(): 177 obj = loader.serializer.known_widgets.get(name) 178 if obj is None: 179 raise KeyError(f"Unknown widget type {name}.") 180 181 namespace.config[obj] = { 182 "styles": obj.styles, 183 "chars": obj.chars.copy(), 184 } 185 186 for category, inner in config.items(): 187 value: str | widgets_m.styles.MarkupFormatter 188 189 if category not in namespace.config[obj]: 190 setattr(obj, category, inner) 191 continue 192 193 for key, value in inner.items(): 194 namespace.config[obj][category][key] = value 195 196 namespace.apply_config() 197 return namespace 198 199 @staticmethod 200 def _apply_section( 201 widget: Type[widgets_m.Widget], title: str, section: dict[str, str] 202 ) -> None: 203 """Applies configuration section to the widget.""" 204 205 for key, value in section.items(): 206 if title == "styles": 207 widget.set_style(key, value) 208 continue 209 210 widget.set_char(key, value) 211 212 def apply_to(self, widget: widgets_m.Widget) -> None: 213 """Applies namespace config to the widget. 214 215 Args: 216 widget: The widget in question. 217 """ 218 219 def _apply_sections( 220 data: dict[str, dict[str, str]], widget: widgets_m.Widget 221 ) -> None: 222 """Applies sections from data to the widget.""" 223 224 for title, section in data.items(): 225 self._apply_section(type(widget), title, section) 226 227 data = self.config.get(type(widget)) 228 if data is None: 229 return 230 231 _apply_sections(data, widget) 232 233 if hasattr(widget, "_widgets"): 234 for inner in widget: 235 inner_section = self.config.get(type(inner)) 236 237 if inner_section is None: 238 continue 239 240 _apply_sections(inner_section, inner) 241 242 def apply_config(self) -> None: 243 """Apply self.config to current namespace.""" 244 245 for widget, settings in self.config.items(): 246 for title, section in settings.items(): 247 self._apply_section(widget, title, section) 248 249 def __getattr__(self, attr: str) -> widgets_m.Widget: 250 """Get widget by name from widget list.""" 251 252 if attr in self.widgets: 253 return self.widgets[attr] 254 255 return self.__dict__[attr]
Class to hold data on loaded namespace.
163 @classmethod 164 def from_config(cls, data: dict[Any, Any], loader: FileLoader) -> WidgetNamespace: 165 """Creates a namespace from config data. 166 167 Args: 168 data: A dictionary of config data. 169 loader: The `FileLoader` instance that should be used. 170 171 Returns: 172 A new WidgetNamespace with the given config. 173 """ 174 175 namespace = WidgetNamespace({}, {}) 176 for name, config in data.items(): 177 obj = loader.serializer.known_widgets.get(name) 178 if obj is None: 179 raise KeyError(f"Unknown widget type {name}.") 180 181 namespace.config[obj] = { 182 "styles": obj.styles, 183 "chars": obj.chars.copy(), 184 } 185 186 for category, inner in config.items(): 187 value: str | widgets_m.styles.MarkupFormatter 188 189 if category not in namespace.config[obj]: 190 setattr(obj, category, inner) 191 continue 192 193 for key, value in inner.items(): 194 namespace.config[obj][category][key] = value 195 196 namespace.apply_config() 197 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.
212 def apply_to(self, widget: widgets_m.Widget) -> None: 213 """Applies namespace config to the widget. 214 215 Args: 216 widget: The widget in question. 217 """ 218 219 def _apply_sections( 220 data: dict[str, dict[str, str]], widget: widgets_m.Widget 221 ) -> None: 222 """Applies sections from data to the widget.""" 223 224 for title, section in data.items(): 225 self._apply_section(type(widget), title, section) 226 227 data = self.config.get(type(widget)) 228 if data is None: 229 return 230 231 _apply_sections(data, widget) 232 233 if hasattr(widget, "_widgets"): 234 for inner in widget: 235 inner_section = self.config.get(type(inner)) 236 237 if inner_section is None: 238 continue 239 240 _apply_sections(inner_section, inner)
Applies namespace config to the widget.
Args
- widget: The widget in question.
242 def apply_config(self) -> None: 243 """Apply self.config to current namespace.""" 244 245 for widget, settings in self.config.items(): 246 for title, section in settings.items(): 247 self._apply_section(widget, title, section)
Apply self.config to current namespace.
258class FileLoader(ABC): 259 """Base class for file loader objects. 260 261 These allow users to load pytermgui content from a specific filetype, 262 with each filetype having their own loaders. 263 264 To use custom widgets with children of this class, you need to call `FileLoader.register`.""" 265 266 serializer: Serializer 267 """Object-specific serializer instance. In order to use a specific, already created 268 instance you need to pass it on `FileLoader` construction.""" 269 270 @abstractmethod 271 def parse(self, data: str) -> dict[Any, Any]: 272 """Parses string into a dictionary used by `pytermgui.serializer.Serializer`. 273 274 This dictionary follows the structure defined above. 275 """ 276 277 def __init__(self, serializer: Serializer | None = None) -> None: 278 """Initialize FileLoader. 279 280 Args: 281 serializer: An optional `pytermgui.serializer.Serializer` instance. If not provided, one 282 is instantiated for every FileLoader instance. 283 """ 284 285 if serializer is None: 286 serializer = Serializer() 287 288 self.serializer = serializer 289 290 def __enter__(self) -> FileLoader: 291 """Starts context manager.""" 292 293 return self 294 295 def __exit__(self, _: Any, exception: Exception, __: Any) -> bool: 296 """Ends context manager.""" 297 298 if exception is not None: 299 raise exception 300 301 def register(self, cls: Type[widgets_m.Widget]) -> None: 302 """Registers a widget to the serializer. 303 304 Args: 305 cls: The widget type to register. 306 """ 307 308 self.serializer.register(cls) 309 310 def bind(self, name: str, method: Callable[..., Any]) -> None: 311 """Binds a name to a method. 312 313 Args: 314 name: The name of the method, as referenced in the loaded 315 files. 316 method: The callable to bind. 317 """ 318 319 self.serializer.bind(name, method) 320 321 def load_str(self, data: str) -> WidgetNamespace: 322 """Creates a `WidgetNamespace` from string data. 323 324 To parse the data, we use `FileLoader.parse`. To implement custom formats, 325 subclass `FileLoader` with your own `parse` implementation. 326 327 Args: 328 data: The data to parse. 329 330 Returns: 331 A WidgetNamespace created from the provided data. 332 """ 333 334 parsed = self.parse(data) 335 336 # Get & load config data 337 config_data = parsed.get("config") 338 if config_data is not None: 339 namespace = WidgetNamespace.from_config(config_data, loader=self) 340 else: 341 namespace = WidgetNamespace.from_config({}, loader=self) 342 343 # Create aliases 344 for key, value in (parsed.get("markup") or {}).items(): 345 markup.alias(key, value) 346 347 # Create boxes 348 for name, inner in (parsed.get("boxes") or {}).items(): 349 self.serializer.register_box(name, widgets_m.boxes.Box(inner)) 350 351 # Create widgets 352 for name, inner in (parsed.get("widgets") or {}).items(): 353 widget_type = inner.get("type") or name 354 355 box_name = inner.get("box") 356 357 box = None 358 if box_name is not None and box_name in namespace.boxes: 359 box = namespace.boxes[box_name] 360 del inner["box"] 361 362 try: 363 namespace.widgets[name] = self.serializer.from_dict( 364 inner, widget_type=widget_type 365 ) 366 except AttributeError as error: 367 raise ValueError( 368 f'Could not load "{name}" from data:\n{json.dumps(inner, indent=2)}' 369 ) from error 370 371 if box is not None: 372 namespace.widgets[name].box = box 373 374 return namespace 375 376 def load(self, data: str | IO) -> WidgetNamespace: 377 """Loads data from a string or a file. 378 379 When an IO object is passed, its data is extracted as a string. 380 This string can then be passed to `load_str`. 381 382 Args: 383 data: Either a string or file stream to load data from. 384 385 Returns: 386 A WidgetNamespace with the data loaded. 387 """ 388 389 if not isinstance(data, str): 390 data = data.read() 391 392 assert isinstance(data, str) 393 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
.
277 def __init__(self, serializer: Serializer | None = None) -> None: 278 """Initialize FileLoader. 279 280 Args: 281 serializer: An optional `pytermgui.serializer.Serializer` instance. If not provided, one 282 is instantiated for every FileLoader instance. 283 """ 284 285 if serializer is None: 286 serializer = Serializer() 287 288 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.
270 @abstractmethod 271 def parse(self, data: str) -> dict[Any, Any]: 272 """Parses string into a dictionary used by `pytermgui.serializer.Serializer`. 273 274 This dictionary follows the structure defined above. 275 """
Parses string into a dictionary used by pytermgui.serializer.Serializer
.
This dictionary follows the structure defined above.
301 def register(self, cls: Type[widgets_m.Widget]) -> None: 302 """Registers a widget to the serializer. 303 304 Args: 305 cls: The widget type to register. 306 """ 307 308 self.serializer.register(cls)
Registers a widget to the serializer.
Args
- cls: The widget type to register.
310 def bind(self, name: str, method: Callable[..., Any]) -> None: 311 """Binds a name to a method. 312 313 Args: 314 name: The name of the method, as referenced in the loaded 315 files. 316 method: The callable to bind. 317 """ 318 319 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.
321 def load_str(self, data: str) -> WidgetNamespace: 322 """Creates a `WidgetNamespace` from string data. 323 324 To parse the data, we use `FileLoader.parse`. To implement custom formats, 325 subclass `FileLoader` with your own `parse` implementation. 326 327 Args: 328 data: The data to parse. 329 330 Returns: 331 A WidgetNamespace created from the provided data. 332 """ 333 334 parsed = self.parse(data) 335 336 # Get & load config data 337 config_data = parsed.get("config") 338 if config_data is not None: 339 namespace = WidgetNamespace.from_config(config_data, loader=self) 340 else: 341 namespace = WidgetNamespace.from_config({}, loader=self) 342 343 # Create aliases 344 for key, value in (parsed.get("markup") or {}).items(): 345 markup.alias(key, value) 346 347 # Create boxes 348 for name, inner in (parsed.get("boxes") or {}).items(): 349 self.serializer.register_box(name, widgets_m.boxes.Box(inner)) 350 351 # Create widgets 352 for name, inner in (parsed.get("widgets") or {}).items(): 353 widget_type = inner.get("type") or name 354 355 box_name = inner.get("box") 356 357 box = None 358 if box_name is not None and box_name in namespace.boxes: 359 box = namespace.boxes[box_name] 360 del inner["box"] 361 362 try: 363 namespace.widgets[name] = self.serializer.from_dict( 364 inner, widget_type=widget_type 365 ) 366 except AttributeError as error: 367 raise ValueError( 368 f'Could not load "{name}" from data:\n{json.dumps(inner, indent=2)}' 369 ) from error 370 371 if box is not None: 372 namespace.widgets[name].box = box 373 374 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.
376 def load(self, data: str | IO) -> WidgetNamespace: 377 """Loads data from a string or a file. 378 379 When an IO object is passed, its data is extracted as a string. 380 This string can then be passed to `load_str`. 381 382 Args: 383 data: Either a string or file stream to load data from. 384 385 Returns: 386 A WidgetNamespace with the data loaded. 387 """ 388 389 if not isinstance(data, str): 390 data = data.read() 391 392 assert isinstance(data, str) 393 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.
412class YamlLoader(FileLoader): 413 """YAML specific loader subclass.""" 414 415 def __init__(self, serializer: Serializer | None = None) -> None: 416 """Initialize object, check for installation of PyYAML.""" 417 418 if YAML_ERROR is not None: 419 raise RuntimeError( 420 "YAML implementation module not found. Please install `PyYAML` to use `YamlLoader`." 421 ) from YAML_ERROR 422 423 super().__init__() 424 425 def parse(self, data: str) -> dict[Any, Any]: 426 """Parse YAML str. 427 428 Args: 429 data: YAML formatted string. 430 431 Returns: 432 Loadable dictionary. 433 """ 434 435 assert yaml is not None 436 return yaml.safe_load(data)
YAML specific loader subclass.
415 def __init__(self, serializer: Serializer | None = None) -> None: 416 """Initialize object, check for installation of PyYAML.""" 417 418 if YAML_ERROR is not None: 419 raise RuntimeError( 420 "YAML implementation module not found. Please install `PyYAML` to use `YamlLoader`." 421 ) from YAML_ERROR 422 423 super().__init__()
Initialize object, check for installation of PyYAML.
425 def parse(self, data: str) -> dict[Any, Any]: 426 """Parse YAML str. 427 428 Args: 429 data: YAML formatted string. 430 431 Returns: 432 Loadable dictionary. 433 """ 434 435 assert yaml is not None 436 return yaml.safe_load(data)
Parse YAML str.
Args
- data: YAML formatted string.
Returns
Loadable dictionary.
Inherited Members
396class JsonLoader(FileLoader): 397 """JSON specific loader subclass.""" 398 399 def parse(self, data: str) -> dict[Any, Any]: 400 """Parse JSON str. 401 402 Args: 403 data: JSON formatted string. 404 405 Returns: 406 Loadable dictionary. 407 """ 408 409 return json.loads(data)
JSON specific loader subclass.
399 def parse(self, data: str) -> dict[Any, Any]: 400 """Parse JSON str. 401 402 Args: 403 data: JSON formatted string. 404 405 Returns: 406 Loadable dictionary. 407 """ 408 409 return json.loads(data)
Parse JSON str.
Args
- data: JSON formatted string.
Returns
Loadable dictionary.