Human-readable text syntax
The preserves.text module implements the Preserves human-readable text syntax.
The main entry points are functions stringify, parse, and parse_with_annotations.
>>> stringify(Record(Symbol('hi'), [1, [2, 3]]))
'<hi 1 [2 3]>'
>>> parse('<hi 1 [2 3]>')
#hi(1, (2, 3))
Annotated(item)
Bases: object
A Preserves Value
along with a sequence of Value
s annotating it. Compares equal to
the underlying Value
, ignoring the annotations. See the specification document for more
about annotations.
>>> import preserves
>>> a = preserves.parse('''
... # A comment
... [1 2 3]
... ''', include_annotations=True)
>>> a
@'A comment' (1, 2, 3)
>>> a.item
(1, 2, 3)
>>> a.annotations
['A comment']
>>> a == (1, 2, 3)
True
>>> a == preserves.parse('@xyz [1 2 3]', include_annotations=True)
True
>>> a[0]
Traceback (most recent call last):
...
TypeError: 'Annotated' object is not subscriptable
>>> a.item[0]
1
>>> type(a.item[0])
<class 'preserves.values.Annotated'>
>>> a.item[0].annotations
[]
>>> print(preserves.stringify(a))
@"A comment" [1 2 3]
>>> print(preserves.stringify(a, include_annotations=False))
[1 2 3]
Attributes:
Name | Type | Description |
---|---|---|
item |
Value
|
the underlying annotated |
annotations |
list[Value]
|
the annotations attached to |
Source code in preserves/values.py
456 457 458 |
|
peel()
Calls strip_annotations on self
with depth=1
.
Source code in preserves/values.py
479 480 481 |
|
strip(depth=inf)
Calls strip_annotations on self
and depth
.
Source code in preserves/values.py
475 476 477 |
|
DecodeError
Bases: ValueError
Raised whenever preserves.binary.Decoder or preserves.text.Parser detect invalid input.
Embedded(embeddedValue)
Representation of a Preserves Embedded
value. For more on the meaning and use of
embedded values, see the specification.
>>> import io
>>> e = Embedded(io.StringIO('some text'))
>>> e # doctest: +ELLIPSIS
#:<_io.StringIO object at ...>
>>> e.embeddedValue # doctest: +ELLIPSIS
<_io.StringIO object at ...>
>>> import preserves
>>> print(preserves.stringify(Embedded(None)))
Traceback (most recent call last):
...
TypeError: Cannot preserves-format: None
>>> print(preserves.stringify(Embedded(None), format_embedded=lambda x: 'abcdef'))
#:"abcdef"
Attributes:
Name | Type | Description |
---|---|---|
embeddedValue |
any Python value; could be a platform object, could be a representation of a
Preserves |
Source code in preserves/values.py
601 602 |
|
EncodeError
Bases: ValueError
Raised whenever preserves.binary.Encoder or preserves.text.Formatter are unable to proceed.
Formatter(format_embedded=lambda x: x, indent=None, with_commas=False, trailing_comma=False, include_annotations=True)
Bases: TextCodec
Printer (and indenting pretty-printer) for producing human-readable syntax from
Preserves Value
s.
>>> f = Formatter()
>>> f.append({'a': 1, 'b': 2})
>>> f.append(Record(Symbol('label'), ['field1', ['field2item1', 'field2item2']]))
>>> print(f.contents())
{"a": 1 "b": 2} <label "field1" ["field2item1" "field2item2"]>
>>> f = Formatter(indent=4)
>>> f.append({'a': 1, 'b': 2})
>>> f.append(Record(Symbol('label'), ['field1', ['field2item1', 'field2item2']]))
>>> print(f.contents())
{
"a": 1
"b": 2
}
<label "field1" [
"field2item1"
"field2item2"
]>
Parameters:
Name | Type | Description | Default |
---|---|---|---|
format_embedded |
function accepting an Embedded.embeddedValue and
returning a |
lambda x: x
|
|
indent |
int | None
|
|
None
|
with_commas |
bool
|
|
False
|
trailing_comma |
bool
|
|
False
|
include_annotations |
bool
|
|
True
|
Attributes:
Name | Type | Description |
---|---|---|
indent_delta |
int
|
indentation per nesting-level |
chunks |
list[str]
|
fragments of output |
Source code in preserves/text.py
471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 |
|
append(v)
Extend self.chunks
with at least one chunk, together making up the text
representation of v
.
Source code in preserves/text.py
541 542 543 544 545 546 547 548 549 550 |
|
contents()
Returns a str
constructed from the join of the chunks in self.chunks
.
Source code in preserves/text.py
492 493 494 |
|
is_indenting()
Returns True
iff this Formatter is in pretty-printing
indenting mode.
Source code in preserves/text.py
496 497 498 499 |
|
ImmutableDict(*args, **kwargs)
Bases: dict
A subclass of Python's built-in dict
that overrides methods that could mutate the
dictionary, causing them to raise TypeError('Immutable')
if called.
Implements the __hash__
method, allowing ImmutableDict
instances to be used whereever immutable data are permitted; in particular, as keys in
other dictionaries.
>>> d = ImmutableDict([('a', 1), ('b', 2)])
>>> d
{'a': 1, 'b': 2}
>>> d['c'] = 3
Traceback (most recent call last):
...
TypeError: Immutable
>>> del d['b']
Traceback (most recent call last):
...
TypeError: Immutable
Source code in preserves/values.py
340 341 342 343 |
|
from_kvs(kvs)
staticmethod
Constructs an ImmutableDict from a sequence of alternating keys and values; compare to the ImmutableDict constructor, which takes a sequence of key-value pairs.
>>> ImmutableDict.from_kvs(['a', 1, 'b', 2])
{'a': 1, 'b': 2}
>>> ImmutableDict.from_kvs(['a', 1, 'b', 2])['c'] = 3
Traceback (most recent call last):
...
TypeError: Immutable
Source code in preserves/values.py
361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 |
|
Parser(input_buffer='', include_annotations=False, parse_embedded=lambda x: x)
Bases: TextCodec
Parser for the human-readable Preserves text syntax.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
input_buffer |
str
|
initial contents of the input buffer; may subsequently be extended by calling extend. |
''
|
include_annotations |
bool
|
if |
False
|
parse_embedded |
function accepting a |
lambda x: x
|
Normal usage is to supply input text, and keep calling next until a ShortPacket exception is raised:
>>> d = Parser('123 "hello" @x []')
>>> d.next()
123
>>> d.next()
'hello'
>>> d.next()
()
>>> d.next()
Traceback (most recent call last):
...
preserves.error.ShortPacket: Short input buffer
Alternatively, keep calling try_next until it yields
None
, which is not in the domain of Preserves Value
s:
>>> d = Parser('123 "hello" @x []')
>>> d.try_next()
123
>>> d.try_next()
'hello'
>>> d.try_next()
()
>>> d.try_next()
For convenience, Parser implements the iterator interface, backing it with try_next, so you can simply iterate over all complete values in an input:
>>> d = Parser('123 "hello" @x []')
>>> list(d)
[123, 'hello', ()]
>>> for v in Parser('123 "hello" @x []'):
... print(repr(v))
123
'hello'
()
Supply include_annotations=True
to read annotations alongside the annotated values:
>>> d = Parser('123 "hello" @x []', include_annotations=True)
>>> list(d)
[123, 'hello', @#x ()]
If you are incrementally reading from, say, a socket, you can use extend to add new input as if comes available:
>>> d = Parser('123 "he')
>>> d.try_next()
123
>>> d.try_next() # returns None because the input is incomplete
>>> d.extend('llo"')
>>> d.try_next()
'hello'
>>> d.try_next()
Attributes:
Name | Type | Description |
---|---|---|
input_buffer |
str
|
buffered input waiting to be processed |
index |
int
|
read position within |
Source code in preserves/text.py
132 133 134 135 136 137 |
|
extend(text)
Appends text
to the remaining contents of self.input_buffer
, trimming already-processed
text from the front of self.input_buffer
and resetting self.index
to zero.
Source code in preserves/text.py
139 140 141 142 143 |
|
next()
Reads the next complete Value
from the internal buffer, raising
ShortPacket if too few bytes are available, or
DecodeError if the input is invalid somehow.
Source code in preserves/text.py
323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 |
|
try_next()
Like next, but returns None
instead of raising
ShortPacket.
Source code in preserves/text.py
385 386 387 388 389 390 391 392 393 |
|
Record(key, fields)
Bases: object
Representation of Preserves Record
s, which are a pair of a label Value
and a sequence of field Value
s.
>>> r = Record(Symbol('label'), ['field1', ['field2item1', 'field2item2']])
>>> r
#label('field1', ['field2item1', 'field2item2'])
>>> r.key
#label
>>> r.fields
('field1', ['field2item1', 'field2item2'])
>>> import preserves
>>> preserves.stringify(r)
'<label "field1" ["field2item1" "field2item2"]>'
>>> r == preserves.parse('<label "field1" ["field2item1" "field2item2"]>')
True
Parameters:
Name | Type | Description | Default |
---|---|---|---|
key |
Value
|
the |
required |
fields |
iterable[Value]
|
the fields of the |
required |
Attributes:
Name | Type | Description |
---|---|---|
key |
Value
|
the |
fields |
tuple[Value]
|
the fields of the |
Source code in preserves/values.py
152 153 154 155 |
|
makeBasicConstructor(label, fieldNames)
staticmethod
Constructs and returns a "constructor" for Record
s having a certain label
and
number of fields.
Deprecated
Use preserves.schema definitions instead.
The "constructor" is a callable function that accepts len(fields)
arguments and
returns a Record with label
as its label and the arguments
to the constructor as field values.
In addition, the "constructor" has a constructorInfo
attribute holding a
RecordConstructorInfo object, an isClassOf
attribute holding a unary function that returns True
iff its argument is a
Record with label label
and arity len(fieldNames)
, and
an ensureClassOf
attribute that raises an Exception
if isClassOf
returns false on
its argument and returns the argument otherwise.
Finally, for each field name f
in fieldNames
, the "constructor" object has an
attribute _f
that is a unary function that retrieves the f
field from the passed in
argument.
>>> c = Record.makeBasicConstructor(Symbol('date'), 'year month day')
>>> c(1969, 7, 16)
#date(1969, 7, 16)
>>> c.constructorInfo
#date/3
>>> c.isClassOf(c(1969, 7, 16))
True
>>> c.isClassOf(Record(Symbol('date'), [1969, 7, 16]))
True
>>> c.isClassOf(Record(Symbol('date'), [1969]))
False
>>> c.ensureClassOf(c(1969, 7, 16))
#date(1969, 7, 16)
>>> c.ensureClassOf(Record(Symbol('date'), [1969]))
Traceback (most recent call last):
...
TypeError: Record: expected #date/3, got #date(1969)
>>> c._year(c(1969, 7, 16))
1969
>>> c._month(c(1969, 7, 16))
7
>>> c._day(c(1969, 7, 16))
16
Parameters:
Name | Type | Description | Default |
---|---|---|---|
label |
Value
|
Label to use for constructed/matched |
required |
fieldNames |
tuple[str] | list[str] | str
|
Names of the |
required |
Source code in preserves/values.py
200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 |
|
makeConstructor(labelSymbolText, fieldNames)
staticmethod
Equivalent to Record.makeBasicConstructor(Symbol(labelSymbolText), fieldNames)
.
Deprecated
Use preserves.schema definitions instead.
Source code in preserves/values.py
190 191 192 193 194 195 196 197 198 |
|
RecordConstructorInfo(key, arity)
Bases: object
Describes the shape of a Record
constructor, namely its label and its arity (field
count).
>>> RecordConstructorInfo(Symbol('label'), 3)
#label/3
Attributes:
Name | Type | Description |
---|---|---|
key |
Value
|
the label of matching |
arity |
int
|
the number of fields in matching |
Source code in preserves/values.py
295 296 297 |
|
ShortPacket
Bases: DecodeError
Raised whenever preserves.binary.Decoder or preserves.text.Parser discover that they want to read beyond the end of the currently-available input buffer in order to completely read an encoded value.
Symbol(name)
Bases: object
Representation of Preserves Symbol
s.
>>> Symbol('xyz')
#xyz
>>> Symbol('xyz').name
'xyz'
>>> repr(Symbol('xyz'))
'#xyz'
>>> str(Symbol('xyz'))
'xyz'
>>> import preserves
>>> preserves.stringify(Symbol('xyz'))
'xyz'
>>> preserves.stringify(Symbol('hello world'))
"'hello world'"
>>> preserves.parse('xyz')
#xyz
>>> preserves.parse("'hello world'")
#hello world
Attributes:
Name | Type | Description |
---|---|---|
name |
str | Symbol
|
The symbol's text label. If an existing Symbol is passed
in, the existing Symbol's |
Source code in preserves/values.py
78 79 |
|
annotate(v, *anns)
Wraps v
in an Annotated object, if it isn't already
wrapped, and appends each of the anns
to the Annotated's
annotations
sequence. NOTE: Does not recursively ensure that any parts of the argument
v
are themselves wrapped in Annotated objects!
>>> import preserves
>>> print(preserves.stringify(annotate(123, "A comment", "Another comment")))
@"A comment" @"Another comment" 123
Source code in preserves/values.py
545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 |
|
cmp_floats(a, b)
Implements the totalOrder
predicate defined in section 5.10 of IEEE Std
754-2008.
Source code in preserves/values.py
31 32 33 34 35 36 37 38 39 40 |
|
dict_kvs(d)
Generator function yielding a sequence of alternating keys and values from d
. In some
sense the inverse of ImmutableDict.from_kvs.
>>> list(dict_kvs({'a': 1, 'b': 2}))
['a', 1, 'b', 2]
Source code in preserves/values.py
397 398 399 400 401 402 403 404 405 406 407 408 409 |
|
is_annotated(v)
True
iff v
is an instance of Annotated.
Source code in preserves/values.py
495 496 497 |
|
parse(text, **kwargs)
Yields the first complete encoded value from text
, passing kwargs
through to the
Parser constructor. Raises exceptions as per
next.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
text |
str
|
encoded data to decode |
required |
Source code in preserves/text.py
404 405 406 407 408 409 410 411 412 413 |
|
parse_with_annotations(bs, **kwargs)
Like parse, but supplying include_annotations=True
to the
Parser constructor.
Source code in preserves/text.py
415 416 417 418 |
|
preserve(v)
Converts v
to a representation of a Preserves Value
by (repeatedly) setting
v = v.__preserve__()
while v
has a __preserve__
method. Parsed Schema
values are able to render themselves to their serialized representations this way.
Source code in preserves/values.py
13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
|
stringify(v, **kwargs)
Convert a single Value
v
to a string. Any supplied kwargs
are passed on to the
underlying Formatter constructor.
Source code in preserves/text.py
602 603 604 605 606 607 |
|
strip_annotations(v, depth=inf)
Exposes depth
layers of raw structure of
potentially-Annotated Value
s. If depth==0
or v
is not
Annotated, just returns v
. Otherwise, descends recursively
into the structure of v.item
.
>>> import preserves
>>> a = preserves.parse('@"A comment" [@a 1 @b 2 @c 3]', include_annotations=True)
>>> is_annotated(a)
True
>>> print(preserves.stringify(a))
@"A comment" [@a 1 @b 2 @c 3]
>>> print(preserves.stringify(strip_annotations(a)))
[1 2 3]
>>> print(preserves.stringify(strip_annotations(a, depth=1)))
[@a 1 @b 2 @c 3]
Source code in preserves/values.py
499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 |
|