ron.models
High-level utilities for traversing and manipulating Rusty Object Notation (RON) data.
This module features RonObject and semi-internal parser types (RonValue).
Although it is possible to use primitive syntax types directly, you will likely
prefer using RonObject's __getitem__ implementation, especially for nested
lookups. It abstracts away specific key and container types, allowing you to use
plain Python objects to access the data.
To access the raw syntax leaves, RonObject exposes a family of expect_
methods (e.g., expect_int, expect_map).
These methods assert that the value
matches the expected type, raising a ValueError if it does not (which also
makes type checkers happy).
1""" 2High-level utilities for traversing and manipulating Rusty Object Notation (RON) 3data. 4 5This module features `RonObject` and semi-internal parser types (`RonValue`). 6Although it is possible to use primitive syntax types directly, you will likely 7prefer using `RonObject`'s `__getitem__` implementation, especially for nested 8lookups. It abstracts away specific key and container types, allowing you to use 9plain Python objects to access the data. 10 11To access the raw syntax leaves, `RonObject` exposes a family of `expect_` 12methods (e.g., `expect_int`, `expect_map`). 13 14These methods assert that the value 15matches the expected type, raising a `ValueError` if it does not (which also 16makes type checkers happy). 17""" 18 19from __future__ import annotations 20 21from dataclasses import dataclass 22from typing import Any, Callable, Literal, TypeGuard, override 23 24from frozendict import frozendict 25 26type RonValue = ( 27 RonStruct 28 | RonSeq 29 | RonMap 30 | RonOptional 31 | RonChar 32 | int 33 | float 34 | str 35 | bool 36) 37""" 38@public Union of all supported RON types, including primitives and containers. 39""" 40 41 42def is_ron_value(val: Any) -> TypeGuard[RonValue]: 43 """ 44 Type-guard that checks that your value is indeed of `RonValue` 45 46 Use with typecheckers. 47 """ 48 # simple top-level check 49 if isinstance( 50 val, 51 ( 52 RonStruct, 53 RonSeq, 54 RonMap, 55 RonOptional, 56 RonChar, 57 int, 58 float, 59 str, 60 bool, 61 ), 62 ): 63 return True 64 65 return False 66 67 68@dataclass 69class RonObject: 70 """ 71 A wrapper around `RonValue` enabling convenient traversal. 72 73 It allows you to access nested fields using standard Python types as keys, 74 automatically handling the necessary conversions to RON-specific types. 75 76 Read `RonObject.__getitem__`() docs for more. 77 """ 78 79 v: RonValue 80 """@private field holding the internal value""" 81 82 def expect_map(self) -> RonMap: 83 """Returns an underlying map or **raises** a `ValueError`""" 84 if isinstance(self.v, RonMap): 85 return self.v 86 raise ValueError(f"Value '{self}' is not a map") 87 88 def expect_struct(self) -> RonStruct: 89 """Returns an underlying struct or **raises** a `ValueError`""" 90 if isinstance(self.v, RonStruct): 91 return self.v 92 raise ValueError(f"Value '{self}' is not a struct") 93 94 def expect_int(self) -> int: 95 """Returns an underlying int or **raises** a `ValueError`""" 96 if isinstance(self.v, int): 97 return self.v 98 raise ValueError(f"Value '{self}' is not an integer") 99 100 def expect_float(self) -> float: 101 """Returns an underlying float or **raises** a `ValueError`""" 102 if isinstance(self.v, float): 103 return self.v 104 raise ValueError(f"Value '{self}' is not a float") 105 106 def expect_str(self) -> str: 107 """Returns an underlying str or **raises** a `ValueError`""" 108 if isinstance(self.v, str): 109 return self.v 110 raise ValueError(f"Value '{self}' is not a string") 111 112 def expect_bool(self) -> bool: 113 """Returns an underlying bool or **raises** a `ValueError`""" 114 if isinstance(self.v, bool): 115 return self.v 116 raise ValueError(f"Value '{self}' is not a boolean") 117 118 def expect_tuple(self) -> RonSeq: 119 """Returns an underlying tuple or **raises** a `ValueError`""" 120 if isinstance(self.v, RonSeq) and self.v.kind == "tuple": 121 return self.v 122 raise ValueError(f"Value '{self}' is not a ron tuple") 123 124 def expect_list(self) -> RonSeq: 125 """Returns an underlying list or **raises** a `ValueError`""" 126 if isinstance(self.v, RonSeq) and self.v.kind == "list": 127 return self.v 128 raise ValueError(f"Value '{self}' is not a ron list") 129 130 def maybe(self) -> RonObject | None: 131 """ 132 Transposes a RON optional into Python `Optional`. 133 134 # Returns 135 `RonObject` | `None`: The inner value, if present. 136 137 # Raises 138 - `ValueError`: If the value is not an option. 139 140 Examples: 141 >>> from ron import parse_ron 142 >>> obj = parse_ron('Some(42)') 143 >>> result = obj.maybe() 144 >>> assert result is not None 145 >>> result.expect_int() 146 42 147 148 >>> obj = parse_ron('None') 149 >>> obj.maybe() is None 150 True 151 152 >>> # Raises error if called on non-option types 153 >>> obj = parse_ron('42') 154 >>> obj.maybe() 155 Traceback (most recent call last): 156 ... 157 ValueError: Value ... is not an option 158 """ 159 if isinstance(self.v, RonOptional): 160 return RonObject(self.v.value) if self.v.value is not None else None 161 raise ValueError(f"Value '{self}' is not an option") 162 163 def __getitem__( 164 self, 165 item: RonValue 166 | tuple[RonValue, ...] 167 | list[RonValue] 168 | dict[Any, Any] 169 | None 170 | RonObject, 171 ) -> RonObject: 172 """ 173 @public Escape hatch to traverse the RON object. 174 175 This method inspects the underlying container (Map, Struct, or Sequence) 176 and (if needed) automatically converts the provided `item` into the 177 correct RON type required for the lookup. 178 179 For example, passing a `str` to a struct will look up that field name, 180 and passing a `tuple` to a map will convert it to a `RonSeq` key. 181 182 # Returns 183 `RonObject`: A new wrapper around the retrieved value, enabling 184 chained access. 185 186 # Raises 187 - `TypeError`: If the value is not a container, or if the key type 188 is invalid for the current container. 189 - `KeyError`: If the item is missing (for Maps and Structs). 190 - `IndexError`: If the index is out of bounds (for Sequences). 191 192 Examples: 193 >>> from ron import parse_ron 194 195 >>> # Struct access: use string keys for field names 196 >>> obj = parse_ron('( id: 42, name: "foo" )') 197 >>> obj["id"].expect_int() 198 42 199 200 >>> # Map access: use standard Python literals 201 >>> obj = parse_ron('{ "key": "value" }') 202 >>> obj["key"].expect_str() 203 'value' 204 205 >>> # Sequence access: use integer indices 206 >>> obj = parse_ron('[10, 20, 30]') 207 >>> obj[1].expect_int() 208 20 209 210 >>> # Complex keys: Python tuples are automatically coerced to RON tuples 211 >>> obj = parse_ron('{ (1, "a"): "found" }') 212 >>> obj[(1, "a")].expect_str() 213 'found' 214 215 >>> # Nested chaining 216 >>> obj = parse_ron('( config: (version: 1) )') 217 >>> obj["config"]["version"].expect_int() 218 1 219 220 >>> # Unit structs 221 >>> obj = parse_ron('{ King: "Crown" }') 222 >>> obj["King"].expect_str() 223 'Crown' 224 225 >>> # And even optionals 226 >>> obj = parse_ron('{ Some("King"): "Crown", None: "Hat" }') 227 >>> obj["King"].expect_str() 228 'Crown' 229 >>> obj[None].expect_str() 230 'Hat' 231 232 >>> # Nested coercion doesn't work for now 233 >>> obj = parse_ron('{ Some(King): "Crown", None: "Hat" }') 234 >>> obj["King"].expect_str() # doctest: +IGNORE_EXCEPTION_DETAIL 235 Traceback (most recent call last): 236 ... 237 KeyError: ... coerced as Some("King"), not Some(King) 238 239 >>> # If you need nested keys, parse them 240 >>> obj[parse_ron("Some(King)")].expect_str() 241 'Crown' 242 """ 243 244 val = self.v 245 if isinstance(item, RonObject): 246 item = item.v 247 248 # 1. Pick a proper container (and its key type) 249 key = None 250 if isinstance(val, RonStruct): 251 container = val._fields 252 if type(container) is frozendict: 253 key = container.key(0) 254 elif isinstance(val, RonMap): 255 container = val.entries 256 key = container.key(0) 257 elif isinstance(val, RonSeq): 258 container = val.elements 259 elif isinstance(val, tuple): 260 container = val 261 else: 262 raise TypeError(f"{self}[{item}]") 263 264 # 2. Convert index to a proper ron type, if needed 265 if isinstance(item, tuple): 266 match key: 267 case RonSeq(): 268 item = RonSeq(elements=item, kind="tuple") 269 case RonStruct(name): 270 item = RonStruct(name, _fields=item, spans=None) 271 case _: 272 raise TypeError(f"{self}[{item}]: Unexpected index type") 273 if isinstance(item, list): 274 item = RonSeq(elements=tuple(item), kind="list") 275 if isinstance(item, dict): 276 match key: 277 case RonMap(): 278 item = RonMap(entries=frozendict(item)) 279 case RonStruct(name): 280 item = RonStruct(name, _fields=frozendict(item), spans=None) 281 case _: 282 raise TypeError(f"{self}[{item}]: Unexpected index type") 283 284 # Handle optionals and unit structs 285 if type(key) is RonOptional and type(item) is not RonOptional: 286 item = RonOptional(item) 287 elif type(key) is RonStruct and type(item) is str: 288 item = RonStruct(item, _fields=tuple(), spans=None) 289 elif item is None: 290 item = RonOptional(None) 291 292 # 3. Do the index magic and wrap in RonObject to prolong the chain 293 if isinstance(container, frozendict): 294 try: 295 result = container[item] 296 except (KeyError, TypeError): 297 raise KeyError( 298 f"container={container}, key={item}, heuristic_key={key}" 299 ) 300 return RonObject(result) 301 elif isinstance(container, tuple): 302 if not isinstance(item, int): 303 raise TypeError( 304 f"List indices must be integers, got {type(item).__name__}" 305 ) 306 result = container[item] 307 return RonObject(result) 308 else: 309 raise TypeError( 310 f"Value of type {type(val).__name__} is not subscriptable" 311 ) 312 313 314@dataclass(frozen=True) 315class SpanPoint: 316 """ 317 Represents span point, the beginning or the end of the element. 318 """ 319 320 ch: int 321 """ 322 Character index in a stream 323 """ 324 line: int 325 """ 326 Line number, starts at 1 327 """ 328 column: int 329 """ 330 Column number, starts at 1 331 """ 332 333 334type Span = tuple[SpanPoint, SpanPoint] 335""" 336@private 337""" 338 339 340@dataclass(frozen=True) 341class RonStruct: 342 """ 343 Represents RON structures. 344 345 Includes: 346 - named structs like `Weapon(str: 5)` 347 - anon structs like `(str: 5)` 348 - structs with named fields (see above) 349 - structs with unnamed fields like `Weapon(5)` 350 - unit structs like ... `Weapon`? 351 - enums like `RBG(r: 255, g: 0, b: 0)` (yes, same syntax as structs) 352 - unit enums like `Red` (more useful than unit structs) 353 354 Use `RonStruct.as_dict` or `RonStruct.as_tuple` to get fields. 355 356 **Careful**, they will panic if you try to get the wrong thing. 357 358 And if you, for some reason, need a name, there's `RonStruct.name` 359 (might be `None`). 360 361 362 Oh, and it has spans. See `RonStruct.spans`. 363 """ 364 365 name: str | None 366 """ 367 Struct's name, if present. Simple as that. 368 """ 369 _fields: frozendict[RonValue, RonValue] | tuple[RonValue, ...] 370 """ 371 @private 372 """ 373 374 spans: frozendict[RonValue, Span] | tuple[Span, ...] | None 375 """ 376 Represents spans for struct field *values* as (start, end). 377 See `SpanPoint`. 378 """ 379 380 @property 381 def as_dict(self) -> frozendict[RonValue, RonValue]: 382 """Returns the underlying dictionary for structs with named fields. 383 384 **Raises** a `ValueError` otherwise. 385 """ 386 if isinstance(self._fields, frozendict): 387 return self._fields 388 raise ValueError( 389 f"Struct '{self.name}' is a Tuple-Struct, not a Named-Struct" 390 ) 391 392 @property 393 def as_tuple(self) -> tuple[RonValue, ...]: 394 """Returns the underlying tuple for structs with unnamed fields. 395 396 **Raises** a ValueError otherwise. 397 """ 398 if isinstance(self._fields, tuple): 399 return self._fields 400 raise ValueError( 401 f"Struct '{self.name}' is a Named-Struct, not a Tuple-Struct" 402 ) 403 404 405@dataclass(frozen=True) 406class RonSeq: 407 """ 408 Represents all sort of sequences: tuple `()`, lists `[]`, sets `[]`. 409 410 Use `RonSeq.as_tuple` to get said sequence as a `tuple`. 411 412 If you need such info, for some reason, you can use `RonSeq.kind` to 413 find out whether it was parsed from `()` or `[]`. 414 """ 415 416 elements: tuple[RonValue, ...] 417 """ 418 @private 419 """ 420 421 kind: Literal["list", "tuple"] 422 423 @property 424 def as_tuple(self) -> tuple[RonValue, ...]: 425 """Returns the underlying sequence as a tuple.""" 426 return self.elements 427 428 429@dataclass(frozen=True) 430class RonMap: 431 """ 432 Represents a RON map, you can think of it as python's dict pretty much. 433 434 Use `RonMap.as_dict` to get said dict as `frozendict` 435 """ 436 437 entries: frozendict[RonValue, RonValue] 438 """ 439 @private 440 """ 441 442 @property 443 def as_dict(self) -> frozendict[RonValue, RonValue]: 444 """Returns the underlying mapping as a `frozendict`.""" 445 return self.entries 446 447 448@dataclass(frozen=True) 449class RonOptional: 450 """ 451 Represents things like `Some("value")` or None. 452 453 Unlike in Python, that's a real object, but feel free to snatch it 454 with `RonOptional.value`. 455 456 Or use fancy methods to work with the object like `RonOptional.map`. 457 """ 458 459 value: RonValue | None 460 461 def unwrap(self) -> RonValue: 462 """ 463 Asserts that value is present, and returns it. 464 465 **Raises** a `ValueError` otherwise. 466 """ 467 if self.value is None: 468 raise ValueError("tried to call unwrap() on None value") 469 return self.value 470 471 def unwrap_or(self, o: RonValue) -> RonValue: 472 """ 473 Return a value if present, or return a fallback. 474 """ 475 if self.value is None: 476 return o 477 return self.value 478 479 def map(self, f: Callable[[RonValue], RonValue]): 480 """ 481 Apply a function to the underlying value, if present. 482 483 If not, nothing happens. 484 """ 485 if self.value is None: 486 return None 487 return f(self.value) 488 489 490@dataclass(frozen=True) 491class RonChar: 492 """ 493 Represents characters, like 'a'. 494 """ 495 496 value: str 497 498 @override 499 def __str__(self) -> str: 500 return self.value
Union of all supported RON types, including primitives and containers.
43def is_ron_value(val: Any) -> TypeGuard[RonValue]: 44 """ 45 Type-guard that checks that your value is indeed of `RonValue` 46 47 Use with typecheckers. 48 """ 49 # simple top-level check 50 if isinstance( 51 val, 52 ( 53 RonStruct, 54 RonSeq, 55 RonMap, 56 RonOptional, 57 RonChar, 58 int, 59 float, 60 str, 61 bool, 62 ), 63 ): 64 return True 65 66 return False
Type-guard that checks that your value is indeed of RonValue
Use with typecheckers.
69@dataclass 70class RonObject: 71 """ 72 A wrapper around `RonValue` enabling convenient traversal. 73 74 It allows you to access nested fields using standard Python types as keys, 75 automatically handling the necessary conversions to RON-specific types. 76 77 Read `RonObject.__getitem__`() docs for more. 78 """ 79 80 v: RonValue 81 """@private field holding the internal value""" 82 83 def expect_map(self) -> RonMap: 84 """Returns an underlying map or **raises** a `ValueError`""" 85 if isinstance(self.v, RonMap): 86 return self.v 87 raise ValueError(f"Value '{self}' is not a map") 88 89 def expect_struct(self) -> RonStruct: 90 """Returns an underlying struct or **raises** a `ValueError`""" 91 if isinstance(self.v, RonStruct): 92 return self.v 93 raise ValueError(f"Value '{self}' is not a struct") 94 95 def expect_int(self) -> int: 96 """Returns an underlying int or **raises** a `ValueError`""" 97 if isinstance(self.v, int): 98 return self.v 99 raise ValueError(f"Value '{self}' is not an integer") 100 101 def expect_float(self) -> float: 102 """Returns an underlying float or **raises** a `ValueError`""" 103 if isinstance(self.v, float): 104 return self.v 105 raise ValueError(f"Value '{self}' is not a float") 106 107 def expect_str(self) -> str: 108 """Returns an underlying str or **raises** a `ValueError`""" 109 if isinstance(self.v, str): 110 return self.v 111 raise ValueError(f"Value '{self}' is not a string") 112 113 def expect_bool(self) -> bool: 114 """Returns an underlying bool or **raises** a `ValueError`""" 115 if isinstance(self.v, bool): 116 return self.v 117 raise ValueError(f"Value '{self}' is not a boolean") 118 119 def expect_tuple(self) -> RonSeq: 120 """Returns an underlying tuple or **raises** a `ValueError`""" 121 if isinstance(self.v, RonSeq) and self.v.kind == "tuple": 122 return self.v 123 raise ValueError(f"Value '{self}' is not a ron tuple") 124 125 def expect_list(self) -> RonSeq: 126 """Returns an underlying list or **raises** a `ValueError`""" 127 if isinstance(self.v, RonSeq) and self.v.kind == "list": 128 return self.v 129 raise ValueError(f"Value '{self}' is not a ron list") 130 131 def maybe(self) -> RonObject | None: 132 """ 133 Transposes a RON optional into Python `Optional`. 134 135 # Returns 136 `RonObject` | `None`: The inner value, if present. 137 138 # Raises 139 - `ValueError`: If the value is not an option. 140 141 Examples: 142 >>> from ron import parse_ron 143 >>> obj = parse_ron('Some(42)') 144 >>> result = obj.maybe() 145 >>> assert result is not None 146 >>> result.expect_int() 147 42 148 149 >>> obj = parse_ron('None') 150 >>> obj.maybe() is None 151 True 152 153 >>> # Raises error if called on non-option types 154 >>> obj = parse_ron('42') 155 >>> obj.maybe() 156 Traceback (most recent call last): 157 ... 158 ValueError: Value ... is not an option 159 """ 160 if isinstance(self.v, RonOptional): 161 return RonObject(self.v.value) if self.v.value is not None else None 162 raise ValueError(f"Value '{self}' is not an option") 163 164 def __getitem__( 165 self, 166 item: RonValue 167 | tuple[RonValue, ...] 168 | list[RonValue] 169 | dict[Any, Any] 170 | None 171 | RonObject, 172 ) -> RonObject: 173 """ 174 @public Escape hatch to traverse the RON object. 175 176 This method inspects the underlying container (Map, Struct, or Sequence) 177 and (if needed) automatically converts the provided `item` into the 178 correct RON type required for the lookup. 179 180 For example, passing a `str` to a struct will look up that field name, 181 and passing a `tuple` to a map will convert it to a `RonSeq` key. 182 183 # Returns 184 `RonObject`: A new wrapper around the retrieved value, enabling 185 chained access. 186 187 # Raises 188 - `TypeError`: If the value is not a container, or if the key type 189 is invalid for the current container. 190 - `KeyError`: If the item is missing (for Maps and Structs). 191 - `IndexError`: If the index is out of bounds (for Sequences). 192 193 Examples: 194 >>> from ron import parse_ron 195 196 >>> # Struct access: use string keys for field names 197 >>> obj = parse_ron('( id: 42, name: "foo" )') 198 >>> obj["id"].expect_int() 199 42 200 201 >>> # Map access: use standard Python literals 202 >>> obj = parse_ron('{ "key": "value" }') 203 >>> obj["key"].expect_str() 204 'value' 205 206 >>> # Sequence access: use integer indices 207 >>> obj = parse_ron('[10, 20, 30]') 208 >>> obj[1].expect_int() 209 20 210 211 >>> # Complex keys: Python tuples are automatically coerced to RON tuples 212 >>> obj = parse_ron('{ (1, "a"): "found" }') 213 >>> obj[(1, "a")].expect_str() 214 'found' 215 216 >>> # Nested chaining 217 >>> obj = parse_ron('( config: (version: 1) )') 218 >>> obj["config"]["version"].expect_int() 219 1 220 221 >>> # Unit structs 222 >>> obj = parse_ron('{ King: "Crown" }') 223 >>> obj["King"].expect_str() 224 'Crown' 225 226 >>> # And even optionals 227 >>> obj = parse_ron('{ Some("King"): "Crown", None: "Hat" }') 228 >>> obj["King"].expect_str() 229 'Crown' 230 >>> obj[None].expect_str() 231 'Hat' 232 233 >>> # Nested coercion doesn't work for now 234 >>> obj = parse_ron('{ Some(King): "Crown", None: "Hat" }') 235 >>> obj["King"].expect_str() # doctest: +IGNORE_EXCEPTION_DETAIL 236 Traceback (most recent call last): 237 ... 238 KeyError: ... coerced as Some("King"), not Some(King) 239 240 >>> # If you need nested keys, parse them 241 >>> obj[parse_ron("Some(King)")].expect_str() 242 'Crown' 243 """ 244 245 val = self.v 246 if isinstance(item, RonObject): 247 item = item.v 248 249 # 1. Pick a proper container (and its key type) 250 key = None 251 if isinstance(val, RonStruct): 252 container = val._fields 253 if type(container) is frozendict: 254 key = container.key(0) 255 elif isinstance(val, RonMap): 256 container = val.entries 257 key = container.key(0) 258 elif isinstance(val, RonSeq): 259 container = val.elements 260 elif isinstance(val, tuple): 261 container = val 262 else: 263 raise TypeError(f"{self}[{item}]") 264 265 # 2. Convert index to a proper ron type, if needed 266 if isinstance(item, tuple): 267 match key: 268 case RonSeq(): 269 item = RonSeq(elements=item, kind="tuple") 270 case RonStruct(name): 271 item = RonStruct(name, _fields=item, spans=None) 272 case _: 273 raise TypeError(f"{self}[{item}]: Unexpected index type") 274 if isinstance(item, list): 275 item = RonSeq(elements=tuple(item), kind="list") 276 if isinstance(item, dict): 277 match key: 278 case RonMap(): 279 item = RonMap(entries=frozendict(item)) 280 case RonStruct(name): 281 item = RonStruct(name, _fields=frozendict(item), spans=None) 282 case _: 283 raise TypeError(f"{self}[{item}]: Unexpected index type") 284 285 # Handle optionals and unit structs 286 if type(key) is RonOptional and type(item) is not RonOptional: 287 item = RonOptional(item) 288 elif type(key) is RonStruct and type(item) is str: 289 item = RonStruct(item, _fields=tuple(), spans=None) 290 elif item is None: 291 item = RonOptional(None) 292 293 # 3. Do the index magic and wrap in RonObject to prolong the chain 294 if isinstance(container, frozendict): 295 try: 296 result = container[item] 297 except (KeyError, TypeError): 298 raise KeyError( 299 f"container={container}, key={item}, heuristic_key={key}" 300 ) 301 return RonObject(result) 302 elif isinstance(container, tuple): 303 if not isinstance(item, int): 304 raise TypeError( 305 f"List indices must be integers, got {type(item).__name__}" 306 ) 307 result = container[item] 308 return RonObject(result) 309 else: 310 raise TypeError( 311 f"Value of type {type(val).__name__} is not subscriptable" 312 )
A wrapper around RonValue enabling convenient traversal.
It allows you to access nested fields using standard Python types as keys, automatically handling the necessary conversions to RON-specific types.
Read RonObject.__getitem__() docs for more.
83 def expect_map(self) -> RonMap: 84 """Returns an underlying map or **raises** a `ValueError`""" 85 if isinstance(self.v, RonMap): 86 return self.v 87 raise ValueError(f"Value '{self}' is not a map")
Returns an underlying map or raises a ValueError
89 def expect_struct(self) -> RonStruct: 90 """Returns an underlying struct or **raises** a `ValueError`""" 91 if isinstance(self.v, RonStruct): 92 return self.v 93 raise ValueError(f"Value '{self}' is not a struct")
Returns an underlying struct or raises a ValueError
95 def expect_int(self) -> int: 96 """Returns an underlying int or **raises** a `ValueError`""" 97 if isinstance(self.v, int): 98 return self.v 99 raise ValueError(f"Value '{self}' is not an integer")
Returns an underlying int or raises a ValueError
101 def expect_float(self) -> float: 102 """Returns an underlying float or **raises** a `ValueError`""" 103 if isinstance(self.v, float): 104 return self.v 105 raise ValueError(f"Value '{self}' is not a float")
Returns an underlying float or raises a ValueError
107 def expect_str(self) -> str: 108 """Returns an underlying str or **raises** a `ValueError`""" 109 if isinstance(self.v, str): 110 return self.v 111 raise ValueError(f"Value '{self}' is not a string")
Returns an underlying str or raises a ValueError
113 def expect_bool(self) -> bool: 114 """Returns an underlying bool or **raises** a `ValueError`""" 115 if isinstance(self.v, bool): 116 return self.v 117 raise ValueError(f"Value '{self}' is not a boolean")
Returns an underlying bool or raises a ValueError
119 def expect_tuple(self) -> RonSeq: 120 """Returns an underlying tuple or **raises** a `ValueError`""" 121 if isinstance(self.v, RonSeq) and self.v.kind == "tuple": 122 return self.v 123 raise ValueError(f"Value '{self}' is not a ron tuple")
Returns an underlying tuple or raises a ValueError
125 def expect_list(self) -> RonSeq: 126 """Returns an underlying list or **raises** a `ValueError`""" 127 if isinstance(self.v, RonSeq) and self.v.kind == "list": 128 return self.v 129 raise ValueError(f"Value '{self}' is not a ron list")
Returns an underlying list or raises a ValueError
131 def maybe(self) -> RonObject | None: 132 """ 133 Transposes a RON optional into Python `Optional`. 134 135 # Returns 136 `RonObject` | `None`: The inner value, if present. 137 138 # Raises 139 - `ValueError`: If the value is not an option. 140 141 Examples: 142 >>> from ron import parse_ron 143 >>> obj = parse_ron('Some(42)') 144 >>> result = obj.maybe() 145 >>> assert result is not None 146 >>> result.expect_int() 147 42 148 149 >>> obj = parse_ron('None') 150 >>> obj.maybe() is None 151 True 152 153 >>> # Raises error if called on non-option types 154 >>> obj = parse_ron('42') 155 >>> obj.maybe() 156 Traceback (most recent call last): 157 ... 158 ValueError: Value ... is not an option 159 """ 160 if isinstance(self.v, RonOptional): 161 return RonObject(self.v.value) if self.v.value is not None else None 162 raise ValueError(f"Value '{self}' is not an option")
Transposes a RON optional into Python Optional.
Returns
RonObject | None: The inner value, if present.
Raises
ValueError: If the value is not an option.
Examples:
>>> from ron import parse_ron
>>> obj = parse_ron('Some(42)')
>>> result = obj.maybe()
>>> assert result is not None
>>> result.expect_int()
42
>>> obj = parse_ron('None')
>>> obj.maybe() is None
True
>>> # Raises error if called on non-option types
>>> obj = parse_ron('42')
>>> obj.maybe()
Traceback (most recent call last):
...
ValueError: Value ... is not an option
164 def __getitem__( 165 self, 166 item: RonValue 167 | tuple[RonValue, ...] 168 | list[RonValue] 169 | dict[Any, Any] 170 | None 171 | RonObject, 172 ) -> RonObject: 173 """ 174 @public Escape hatch to traverse the RON object. 175 176 This method inspects the underlying container (Map, Struct, or Sequence) 177 and (if needed) automatically converts the provided `item` into the 178 correct RON type required for the lookup. 179 180 For example, passing a `str` to a struct will look up that field name, 181 and passing a `tuple` to a map will convert it to a `RonSeq` key. 182 183 # Returns 184 `RonObject`: A new wrapper around the retrieved value, enabling 185 chained access. 186 187 # Raises 188 - `TypeError`: If the value is not a container, or if the key type 189 is invalid for the current container. 190 - `KeyError`: If the item is missing (for Maps and Structs). 191 - `IndexError`: If the index is out of bounds (for Sequences). 192 193 Examples: 194 >>> from ron import parse_ron 195 196 >>> # Struct access: use string keys for field names 197 >>> obj = parse_ron('( id: 42, name: "foo" )') 198 >>> obj["id"].expect_int() 199 42 200 201 >>> # Map access: use standard Python literals 202 >>> obj = parse_ron('{ "key": "value" }') 203 >>> obj["key"].expect_str() 204 'value' 205 206 >>> # Sequence access: use integer indices 207 >>> obj = parse_ron('[10, 20, 30]') 208 >>> obj[1].expect_int() 209 20 210 211 >>> # Complex keys: Python tuples are automatically coerced to RON tuples 212 >>> obj = parse_ron('{ (1, "a"): "found" }') 213 >>> obj[(1, "a")].expect_str() 214 'found' 215 216 >>> # Nested chaining 217 >>> obj = parse_ron('( config: (version: 1) )') 218 >>> obj["config"]["version"].expect_int() 219 1 220 221 >>> # Unit structs 222 >>> obj = parse_ron('{ King: "Crown" }') 223 >>> obj["King"].expect_str() 224 'Crown' 225 226 >>> # And even optionals 227 >>> obj = parse_ron('{ Some("King"): "Crown", None: "Hat" }') 228 >>> obj["King"].expect_str() 229 'Crown' 230 >>> obj[None].expect_str() 231 'Hat' 232 233 >>> # Nested coercion doesn't work for now 234 >>> obj = parse_ron('{ Some(King): "Crown", None: "Hat" }') 235 >>> obj["King"].expect_str() # doctest: +IGNORE_EXCEPTION_DETAIL 236 Traceback (most recent call last): 237 ... 238 KeyError: ... coerced as Some("King"), not Some(King) 239 240 >>> # If you need nested keys, parse them 241 >>> obj[parse_ron("Some(King)")].expect_str() 242 'Crown' 243 """ 244 245 val = self.v 246 if isinstance(item, RonObject): 247 item = item.v 248 249 # 1. Pick a proper container (and its key type) 250 key = None 251 if isinstance(val, RonStruct): 252 container = val._fields 253 if type(container) is frozendict: 254 key = container.key(0) 255 elif isinstance(val, RonMap): 256 container = val.entries 257 key = container.key(0) 258 elif isinstance(val, RonSeq): 259 container = val.elements 260 elif isinstance(val, tuple): 261 container = val 262 else: 263 raise TypeError(f"{self}[{item}]") 264 265 # 2. Convert index to a proper ron type, if needed 266 if isinstance(item, tuple): 267 match key: 268 case RonSeq(): 269 item = RonSeq(elements=item, kind="tuple") 270 case RonStruct(name): 271 item = RonStruct(name, _fields=item, spans=None) 272 case _: 273 raise TypeError(f"{self}[{item}]: Unexpected index type") 274 if isinstance(item, list): 275 item = RonSeq(elements=tuple(item), kind="list") 276 if isinstance(item, dict): 277 match key: 278 case RonMap(): 279 item = RonMap(entries=frozendict(item)) 280 case RonStruct(name): 281 item = RonStruct(name, _fields=frozendict(item), spans=None) 282 case _: 283 raise TypeError(f"{self}[{item}]: Unexpected index type") 284 285 # Handle optionals and unit structs 286 if type(key) is RonOptional and type(item) is not RonOptional: 287 item = RonOptional(item) 288 elif type(key) is RonStruct and type(item) is str: 289 item = RonStruct(item, _fields=tuple(), spans=None) 290 elif item is None: 291 item = RonOptional(None) 292 293 # 3. Do the index magic and wrap in RonObject to prolong the chain 294 if isinstance(container, frozendict): 295 try: 296 result = container[item] 297 except (KeyError, TypeError): 298 raise KeyError( 299 f"container={container}, key={item}, heuristic_key={key}" 300 ) 301 return RonObject(result) 302 elif isinstance(container, tuple): 303 if not isinstance(item, int): 304 raise TypeError( 305 f"List indices must be integers, got {type(item).__name__}" 306 ) 307 result = container[item] 308 return RonObject(result) 309 else: 310 raise TypeError( 311 f"Value of type {type(val).__name__} is not subscriptable" 312 )
Escape hatch to traverse the RON object.
This method inspects the underlying container (Map, Struct, or Sequence)
and (if needed) automatically converts the provided item into the
correct RON type required for the lookup.
For example, passing a str to a struct will look up that field name,
and passing a tuple to a map will convert it to a RonSeq key.
Returns
RonObject: A new wrapper around the retrieved value, enabling
chained access.
Raises
TypeError: If the value is not a container, or if the key type is invalid for the current container.KeyError: If the item is missing (for Maps and Structs).IndexError: If the index is out of bounds (for Sequences).
Examples:
>>> from ron import parse_ron
>>> # Struct access: use string keys for field names
>>> obj = parse_ron('( id: 42, name: "foo" )')
>>> obj["id"].expect_int()
42
>>> # Map access: use standard Python literals
>>> obj = parse_ron('{ "key": "value" }')
>>> obj["key"].expect_str()
'value'
>>> # Sequence access: use integer indices
>>> obj = parse_ron('[10, 20, 30]')
>>> obj[1].expect_int()
20
>>> # Complex keys: Python tuples are automatically coerced to RON tuples
>>> obj = parse_ron('{ (1, "a"): "found" }')
>>> obj[(1, "a")].expect_str()
'found'
>>> # Nested chaining
>>> obj = parse_ron('( config: (version: 1) )')
>>> obj["config"]["version"].expect_int()
1
>>> # Unit structs
>>> obj = parse_ron('{ King: "Crown" }')
>>> obj["King"].expect_str()
'Crown'
>>> # And even optionals
>>> obj = parse_ron('{ Some("King"): "Crown", None: "Hat" }')
>>> obj["King"].expect_str()
'Crown'
>>> obj[None].expect_str()
'Hat'
>>> # Nested coercion doesn't work for now
>>> obj = parse_ron('{ Some(King): "Crown", None: "Hat" }')
>>> obj["King"].expect_str() # doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
...
KeyError: ... coerced as Some("King"), not Some(King)
>>> # If you need nested keys, parse them
>>> obj[parse_ron("Some(King)")].expect_str()
'Crown'
315@dataclass(frozen=True) 316class SpanPoint: 317 """ 318 Represents span point, the beginning or the end of the element. 319 """ 320 321 ch: int 322 """ 323 Character index in a stream 324 """ 325 line: int 326 """ 327 Line number, starts at 1 328 """ 329 column: int 330 """ 331 Column number, starts at 1 332 """
Represents span point, the beginning or the end of the element.
341@dataclass(frozen=True) 342class RonStruct: 343 """ 344 Represents RON structures. 345 346 Includes: 347 - named structs like `Weapon(str: 5)` 348 - anon structs like `(str: 5)` 349 - structs with named fields (see above) 350 - structs with unnamed fields like `Weapon(5)` 351 - unit structs like ... `Weapon`? 352 - enums like `RBG(r: 255, g: 0, b: 0)` (yes, same syntax as structs) 353 - unit enums like `Red` (more useful than unit structs) 354 355 Use `RonStruct.as_dict` or `RonStruct.as_tuple` to get fields. 356 357 **Careful**, they will panic if you try to get the wrong thing. 358 359 And if you, for some reason, need a name, there's `RonStruct.name` 360 (might be `None`). 361 362 363 Oh, and it has spans. See `RonStruct.spans`. 364 """ 365 366 name: str | None 367 """ 368 Struct's name, if present. Simple as that. 369 """ 370 _fields: frozendict[RonValue, RonValue] | tuple[RonValue, ...] 371 """ 372 @private 373 """ 374 375 spans: frozendict[RonValue, Span] | tuple[Span, ...] | None 376 """ 377 Represents spans for struct field *values* as (start, end). 378 See `SpanPoint`. 379 """ 380 381 @property 382 def as_dict(self) -> frozendict[RonValue, RonValue]: 383 """Returns the underlying dictionary for structs with named fields. 384 385 **Raises** a `ValueError` otherwise. 386 """ 387 if isinstance(self._fields, frozendict): 388 return self._fields 389 raise ValueError( 390 f"Struct '{self.name}' is a Tuple-Struct, not a Named-Struct" 391 ) 392 393 @property 394 def as_tuple(self) -> tuple[RonValue, ...]: 395 """Returns the underlying tuple for structs with unnamed fields. 396 397 **Raises** a ValueError otherwise. 398 """ 399 if isinstance(self._fields, tuple): 400 return self._fields 401 raise ValueError( 402 f"Struct '{self.name}' is a Named-Struct, not a Tuple-Struct" 403 )
Represents RON structures.
Includes:
- named structs like
Weapon(str: 5) - anon structs like
(str: 5) - structs with named fields (see above)
- structs with unnamed fields like
Weapon(5) - unit structs like ...
Weapon? - enums like
RBG(r: 255, g: 0, b: 0)(yes, same syntax as structs) - unit enums like
Red(more useful than unit structs)
Use RonStruct.as_dict or RonStruct.as_tuple to get fields.
Careful, they will panic if you try to get the wrong thing.
And if you, for some reason, need a name, there's RonStruct.name
(might be None).
Oh, and it has spans. See RonStruct.spans.
Represents spans for struct field values as (start, end).
See SpanPoint.
381 @property 382 def as_dict(self) -> frozendict[RonValue, RonValue]: 383 """Returns the underlying dictionary for structs with named fields. 384 385 **Raises** a `ValueError` otherwise. 386 """ 387 if isinstance(self._fields, frozendict): 388 return self._fields 389 raise ValueError( 390 f"Struct '{self.name}' is a Tuple-Struct, not a Named-Struct" 391 )
Returns the underlying dictionary for structs with named fields.
Raises a ValueError otherwise.
393 @property 394 def as_tuple(self) -> tuple[RonValue, ...]: 395 """Returns the underlying tuple for structs with unnamed fields. 396 397 **Raises** a ValueError otherwise. 398 """ 399 if isinstance(self._fields, tuple): 400 return self._fields 401 raise ValueError( 402 f"Struct '{self.name}' is a Named-Struct, not a Tuple-Struct" 403 )
Returns the underlying tuple for structs with unnamed fields.
Raises a ValueError otherwise.
406@dataclass(frozen=True) 407class RonSeq: 408 """ 409 Represents all sort of sequences: tuple `()`, lists `[]`, sets `[]`. 410 411 Use `RonSeq.as_tuple` to get said sequence as a `tuple`. 412 413 If you need such info, for some reason, you can use `RonSeq.kind` to 414 find out whether it was parsed from `()` or `[]`. 415 """ 416 417 elements: tuple[RonValue, ...] 418 """ 419 @private 420 """ 421 422 kind: Literal["list", "tuple"] 423 424 @property 425 def as_tuple(self) -> tuple[RonValue, ...]: 426 """Returns the underlying sequence as a tuple.""" 427 return self.elements
Represents all sort of sequences: tuple (), lists [], sets [].
Use RonSeq.as_tuple to get said sequence as a tuple.
If you need such info, for some reason, you can use RonSeq.kind to
find out whether it was parsed from () or [].
430@dataclass(frozen=True) 431class RonMap: 432 """ 433 Represents a RON map, you can think of it as python's dict pretty much. 434 435 Use `RonMap.as_dict` to get said dict as `frozendict` 436 """ 437 438 entries: frozendict[RonValue, RonValue] 439 """ 440 @private 441 """ 442 443 @property 444 def as_dict(self) -> frozendict[RonValue, RonValue]: 445 """Returns the underlying mapping as a `frozendict`.""" 446 return self.entries
Represents a RON map, you can think of it as python's dict pretty much.
Use RonMap.as_dict to get said dict as frozendict
449@dataclass(frozen=True) 450class RonOptional: 451 """ 452 Represents things like `Some("value")` or None. 453 454 Unlike in Python, that's a real object, but feel free to snatch it 455 with `RonOptional.value`. 456 457 Or use fancy methods to work with the object like `RonOptional.map`. 458 """ 459 460 value: RonValue | None 461 462 def unwrap(self) -> RonValue: 463 """ 464 Asserts that value is present, and returns it. 465 466 **Raises** a `ValueError` otherwise. 467 """ 468 if self.value is None: 469 raise ValueError("tried to call unwrap() on None value") 470 return self.value 471 472 def unwrap_or(self, o: RonValue) -> RonValue: 473 """ 474 Return a value if present, or return a fallback. 475 """ 476 if self.value is None: 477 return o 478 return self.value 479 480 def map(self, f: Callable[[RonValue], RonValue]): 481 """ 482 Apply a function to the underlying value, if present. 483 484 If not, nothing happens. 485 """ 486 if self.value is None: 487 return None 488 return f(self.value)
Represents things like Some("value") or None.
Unlike in Python, that's a real object, but feel free to snatch it
with RonOptional.value.
Or use fancy methods to work with the object like RonOptional.map.
462 def unwrap(self) -> RonValue: 463 """ 464 Asserts that value is present, and returns it. 465 466 **Raises** a `ValueError` otherwise. 467 """ 468 if self.value is None: 469 raise ValueError("tried to call unwrap() on None value") 470 return self.value
Asserts that value is present, and returns it.
Raises a ValueError otherwise.
472 def unwrap_or(self, o: RonValue) -> RonValue: 473 """ 474 Return a value if present, or return a fallback. 475 """ 476 if self.value is None: 477 return o 478 return self.value
Return a value if present, or return a fallback.
480 def map(self, f: Callable[[RonValue], RonValue]): 481 """ 482 Apply a function to the underlying value, if present. 483 484 If not, nothing happens. 485 """ 486 if self.value is None: 487 return None 488 return f(self.value)
Apply a function to the underlying value, if present.
If not, nothing happens.
491@dataclass(frozen=True) 492class RonChar: 493 """ 494 Represents characters, like 'a'. 495 """ 496 497 value: str 498 499 @override 500 def __str__(self) -> str: 501 return self.value
Represents characters, like 'a'.