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`.
View Source
0""" 1Description 2=========== 3 4This module provides the library with the capability to load files into Widget-s. 5 6It provides a FileLoader base class, which is then subclassed by various filetype- 7specific parsers with their own `parse` method. The job of this method is to take 8the file contents as a string, and create a valid json tree out of it. 9 10You can "run" a PTG YAML file by calling `ptg -f <filename>` in your terminal. 11 12**To use any YAML related features, the optional dependency PyYAML is required.** 13 14 15Implementation details 16====================== 17 18The main method of these classes is `load`, which takes a file-like object or a string, 19parses it and returns a `WidgetNamespace` instance. This can then be used to access all 20custom `Widget` definitions in the datafile. 21 22This module highly depends on the `serializer` module. Each file loader uses its own 23`Serializer` instance, but optionally take a pre-instantiated Serializer at construction. 24As with that module, this one depends on it "knowing" all types of Widget-s you are loading. 25If you have custom Widget subclass you would like to use in file-based definitions, use the 26`FileLoader.register` method, passing in your custom class as the sole argument. 27 28 29File structure 30============== 31 32Regardless of filetype, all loaded files must follow a specific structure: 33 34``` 35root 36|- config 37| |_ custom global widget configuration 38| 39|- markup 40| |_ custom markup definitions 41| 42|- boxes 43| |_ custom box definitions 44| 45|_ widgets 46 |_ custom widget definitions 47``` 48 49The loading follows the order config -> markup -> boxes -> widgets. It is not necessary to 50provide all sections. 51 52 53Example of usage 54================ 55 56```yaml 57# -- data.yaml -- 58 59markup: 60 label-style: '141 @61 bold' 61 62boxes: 63 WINDOW_BOX: [ 64 "left --- right", 65 "left x right", 66 "left --- right", 67 ] 68 69config: 70 Window: 71 styles: 72 border: '[@79]{item}' 73 box: SINGLE 74 75 Label: 76 styles: 77 value: '[label-style]{item}' 78 79widgets: 80 MyWindow: 81 type: Window 82 box: WINDOW_BOX 83 widgets: 84 Label: 85 value: '[210 bold]This is a title' 86 87 Label: {} 88 89 Splitter: 90 widgets: 91 - Label: 92 parent_align: 0 93 value: 'This is an option' 94 95 - Button: 96 label: "Press me!" 97 98 Label: {} 99 Label: 100 value: '[label-style]{item}' 101``` 102 103 104```python3 105# -- loader.py -- 106 107import pytermgui as ptg 108 109with ptg.YamlLoader() as loader, open("data.yaml", "r") as datafile: 110 namespace = loader.load(datafile) 111 112with ptg.WindowManager() as manager: 113 manager.add(namespace.MyWindow) 114 manager.run() 115 116# Alternatively, one could run `ptg -f "data.yaml"` to display all widgets defined. 117# See `ptg -h`. 118``` 119 120""" 121 122from __future__ import annotations 123 124from typing import Any, Type, IO, Callable 125from dataclasses import dataclass, field 126from abc import abstractmethod, ABC 127 128import json 129 130from . import widgets as widgets_m 131from .parser import markup 132from .serializer import Serializer 133 134 135YAML_ERROR = None 136 137try: 138 import yaml 139except ImportError as import_error: 140 # yaml is explicitly checked to be None later 141 yaml = None # type: ignore 142 YAML_ERROR = import_error 143 144 145__all__ = ["WidgetNamespace", "FileLoader", "YamlLoader", "JsonLoader"] 146 147 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] 254 255 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 markup.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) 392 393 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) 408 409 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)
View Source
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]
Class to hold data on loaded namespace.
View Source
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
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.
View Source
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)
Applies namespace config to the widget.
Args
- widget: The widget in question.
View Source
Apply self.config to current namespace.
View Source
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)
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
.
View Source
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
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.
View Source
Parses string into a dictionary used by pytermgui.serializer.Serializer
.
This dictionary follows the structure defined above.
View Source
Registers a widget to the serializer.
Args
- cls: The widget type to register.
View Source
Binds a name to a method.
Args
- name: The name of the method, as referenced in the loaded files.
- method: The callable to bind.
View Source
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
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.
View Source
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)
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.
View Source
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)
YAML specific loader subclass.
View Source
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__()
Initialize object, check for installation of PyYAML.
View Source
Parse YAML str.
Args
- data: YAML formatted string.
Returns
Loadable dictionary.
Inherited Members
View Source
JSON specific loader subclass.
View Source
Parse JSON str.
Args
- data: JSON formatted string.
Returns
Loadable dictionary.