0},e}(),Xn=typeof WeakMap!="undefined"?new WeakMap:new Gn,Zn=function(){function e(t){if(!(this instanceof e))throw new TypeError("Cannot call a class as a function.");if(!arguments.length)throw new TypeError("1 argument required, but only 0 present.");var r=ya.getInstance(),n=new Aa(t,r,this);Xn.set(this,n)}return e}();["observe","unobserve","disconnect"].forEach(function(e){Zn.prototype[e]=function(){var t;return(t=Xn.get(this))[e].apply(t,arguments)}});var Ca=function(){return typeof rr.ResizeObserver!="undefined"?rr.ResizeObserver:Zn}(),eo=Ca;var to=new x,Ra=$(()=>k(new eo(e=>{for(let t of e)to.next(t)}))).pipe(g(e=>C(ze,k(e)).pipe(R(()=>e.disconnect()))),J(1));function he(e){return{width:e.offsetWidth,height:e.offsetHeight}}function ye(e){return Ra.pipe(S(t=>t.observe(e)),g(t=>to.pipe(L(({target:r})=>r===e),R(()=>t.unobserve(e)),l(()=>he(e)))),V(he(e)))}function bt(e){return{width:e.scrollWidth,height:e.scrollHeight}}function ir(e){let t=e.parentElement;for(;t&&(e.scrollWidth<=t.scrollWidth&&e.scrollHeight<=t.scrollHeight);)t=(e=t).parentElement;return t?e:void 0}var ro=new x,ka=$(()=>k(new IntersectionObserver(e=>{for(let t of e)ro.next(t)},{threshold:0}))).pipe(g(e=>C(ze,k(e)).pipe(R(()=>e.disconnect()))),J(1));function ar(e){return ka.pipe(S(t=>t.observe(e)),g(t=>ro.pipe(L(({target:r})=>r===e),R(()=>t.unobserve(e)),l(({isIntersecting:r})=>r))))}function no(e,t=16){return dt(e).pipe(l(({y:r})=>{let n=he(e),o=bt(e);return r>=o.height-n.height-t}),B())}var sr={drawer:z("[data-md-toggle=drawer]"),search:z("[data-md-toggle=search]")};function oo(e){return sr[e].checked}function Ke(e,t){sr[e].checked!==t&&sr[e].click()}function Ue(e){let t=sr[e];return b(t,"change").pipe(l(()=>t.checked),V(t.checked))}function Ha(e,t){switch(e.constructor){case HTMLInputElement:return e.type==="radio"?/^Arrow/.test(t):!0;case HTMLSelectElement:case HTMLTextAreaElement:return!0;default:return e.isContentEditable}}function Pa(){return C(b(window,"compositionstart").pipe(l(()=>!0)),b(window,"compositionend").pipe(l(()=>!1))).pipe(V(!1))}function io(){let e=b(window,"keydown").pipe(L(t=>!(t.metaKey||t.ctrlKey)),l(t=>({mode:oo("search")?"search":"global",type:t.key,claim(){t.preventDefault(),t.stopPropagation()}})),L(({mode:t,type:r})=>{if(t==="global"){let n=_e();if(typeof n!="undefined")return!Ha(n,r)}return!0}),pe());return Pa().pipe(g(t=>t?_:e))}function le(){return new URL(location.href)}function ot(e){location.href=e.href}function ao(){return new x}function so(e,t){if(typeof t=="string"||typeof t=="number")e.innerHTML+=t.toString();else if(t instanceof Node)e.appendChild(t);else if(Array.isArray(t))for(let r of t)so(e,r)}function M(e,t,...r){let n=document.createElement(e);if(t)for(let o of Object.keys(t))typeof t[o]!="undefined"&&(typeof t[o]!="boolean"?n.setAttribute(o,t[o]):n.setAttribute(o,""));for(let o of r)so(n,o);return n}function cr(e){if(e>999){let t=+((e-950)%1e3>99);return`${((e+1e-6)/1e3).toFixed(t)}k`}else return e.toString()}function co(){return location.hash.substring(1)}function Vr(e){let t=M("a",{href:e});t.addEventListener("click",r=>r.stopPropagation()),t.click()}function $a(){return b(window,"hashchange").pipe(l(co),V(co()),L(e=>e.length>0),J(1))}function fo(){return $a().pipe(l(e=>ce(`[id="${e}"]`)),L(e=>typeof e!="undefined"))}function zr(e){let t=matchMedia(e);return Zt(r=>t.addListener(()=>r(t.matches))).pipe(V(t.matches))}function uo(){let e=matchMedia("print");return C(b(window,"beforeprint").pipe(l(()=>!0)),b(window,"afterprint").pipe(l(()=>!1))).pipe(V(e.matches))}function Nr(e,t){return e.pipe(g(r=>r?t():_))}function fr(e,t={credentials:"same-origin"}){return ue(fetch(`${e}`,t)).pipe(fe(()=>_),g(r=>r.status!==200?Tt(()=>new Error(r.statusText)):k(r)))}function We(e,t){return fr(e,t).pipe(g(r=>r.json()),J(1))}function po(e,t){let r=new DOMParser;return fr(e,t).pipe(g(n=>n.text()),l(n=>r.parseFromString(n,"text/xml")),J(1))}function ur(e){let t=M("script",{src:e});return $(()=>(document.head.appendChild(t),C(b(t,"load"),b(t,"error").pipe(g(()=>Tt(()=>new ReferenceError(`Invalid script: ${e}`))))).pipe(l(()=>{}),R(()=>document.head.removeChild(t)),ge(1))))}function lo(){return{x:Math.max(0,scrollX),y:Math.max(0,scrollY)}}function mo(){return C(b(window,"scroll",{passive:!0}),b(window,"resize",{passive:!0})).pipe(l(lo),V(lo()))}function ho(){return{width:innerWidth,height:innerHeight}}function bo(){return b(window,"resize",{passive:!0}).pipe(l(ho),V(ho()))}function vo(){return Q([mo(),bo()]).pipe(l(([e,t])=>({offset:e,size:t})),J(1))}function pr(e,{viewport$:t,header$:r}){let n=t.pipe(Z("size")),o=Q([n,r]).pipe(l(()=>Xe(e)));return Q([r,t,o]).pipe(l(([{height:i},{offset:s,size:a},{x:c,y:f}])=>({offset:{x:s.x-c,y:s.y-f+i},size:a})))}(()=>{function e(n,o){parent.postMessage(n,o||"*")}function t(...n){return n.reduce((o,i)=>o.then(()=>new Promise(s=>{let a=document.createElement("script");a.src=i,a.onload=s,document.body.appendChild(a)})),Promise.resolve())}var r=class extends EventTarget{constructor(n){super(),this.url=n,this.m=i=>{i.source===this.w&&(this.dispatchEvent(new MessageEvent("message",{data:i.data})),this.onmessage&&this.onmessage(i))},this.e=(i,s,a,c,f)=>{if(s===`${this.url}`){let u=new ErrorEvent("error",{message:i,filename:s,lineno:a,colno:c,error:f});this.dispatchEvent(u),this.onerror&&this.onerror(u)}};let o=document.createElement("iframe");o.hidden=!0,document.body.appendChild(this.iframe=o),this.w.document.open(),this.w.document.write(`
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Machine-oriented binary syntax
+
+
+
+
+
+
+
+
+
The preserves.binary module implements the Preserves machine-oriented binary
+syntax .
+
The main entry points are functions encode ,
+canonicalize , decode , and
+decode_with_annotations .
+
>>> encode ( Record ( Symbol ( 'hi' ), []))
+b ' \xb4\xb3\x02 hi \x84 '
+>>> decode ( b ' \xb4\xb3\x02 hi \x84 ' )
+#hi()
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Decoder ( packet = b '' , include_annotations = False , decode_embedded = lambda x : x )
+
+
+
+
+
+
+ Bases: BinaryCodec
+
+
+
Implementation of a decoder for the machine-oriented binary Preserves syntax.
+
+
Parameters:
+
+
+
+ Name
+ Type
+ Description
+ Default
+
+
+
+
+ packet
+
+ bytes
+
+ initial contents of the input buffer; may subsequently be extended by calling
+extend .
+
+ b''
+
+
+
+ include_annotations
+
+ bool
+
+ if True
, wrap each value and subvalue in an
+Annotated object.
+
+ False
+
+
+
+ decode_embedded
+
+
+ function accepting a Value
and returning a possibly-decoded form of that value
+suitable for placing into an Embedded object.
+
+ lambda x: x
+
+
+
+
+
Normal usage is to supply a buffer, and keep calling next
+until a ShortPacket exception is raised:
+
>>> d = Decoder ( b ' \xa0 { \xb1\x05 hello \x85\xb3\x01 x \xb5\x84 ' )
+>>> d . next ()
+123
+>>> d . next ()
+'hello'
+>>> d . next ()
+()
+>>> d . next ()
+Traceback ( most recent call last ):
+ ...
+preserves . error . ShortPacket : Short packet
+
+
Alternatively, keep calling try_next until it yields
+None
, which is not in the domain of Preserves Value
s:
+
>>> d = Decoder ( b ' \xa0 { \xb1\x05 hello \x85\xb3\x01 x \xb5\x84 ' )
+>>> d . try_next ()
+123
+>>> d . try_next ()
+'hello'
+>>> d . try_next ()
+()
+>>> d . try_next ()
+
+
For convenience, Decoder implements the iterator interface,
+backing it with try_next , so you can simply iterate
+over all complete values in an input:
+
>>> d = Decoder ( b ' \xa0 { \xb1\x05 hello \x85\xb3\x01 x \xb5\x84 ' )
+>>> list ( d )
+[ 123 , 'hello' , ()]
+
+
>>> for v in Decoder ( b ' \xa0 { \xb1\x05 hello \x85\xb3\x01 x \xb5\x84 ' ):
+... print ( repr ( v ))
+123
+'hello'
+()
+
+
Supply include_annotations=True
to read annotations alongside the annotated values:
+
>>> d = Decoder ( b ' \xa0 { \xb1\x05 hello \x85\xb3\x01 x \xb5\x84 ' , 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 = Decoder ( b ' \xa0 { \xb1\x05 he' )
+>>> d . try_next ()
+123
+>>> d . try_next () # returns None because the input is incomplete
+>>> d . extend ( b 'llo' )
+>>> d . try_next ()
+'hello'
+>>> d . try_next ()
+
+
+
Attributes:
+
+
+
+ Name
+ Type
+ Description
+
+
+
+
+ packet
+
+ bytes
+
+ buffered input waiting to be processed
+
+
+ index
+
+ int
+
+ read position within packet
+
+
+
+
+
+
+ Source code in preserves/binary.py
+ 127
+128
+129
+130
+131
+132 def __init__ ( self , packet = b '' , include_annotations = False , decode_embedded = lambda x : x ):
+ super ( Decoder , self ) . __init__ ()
+ self . packet = packet
+ self . index = 0
+ self . include_annotations = include_annotations
+ self . decode_embedded = decode_embedded
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+extend ( data )
+
+
+
+
+
+
+
Appends data
to the remaining bytes in self.packet
, trimming already-processed
+bytes from the front of self.packet
and resetting self.index
to zero.
+
+
+ Source code in preserves/binary.py
+ def extend ( self , data ):
+ """Appends `data` to the remaining bytes in `self.packet`, trimming already-processed
+ bytes from the front of `self.packet` and resetting `self.index` to zero."""
+ self . packet = self . packet [ self . index :] + data
+ self . index = 0
+
+
+
+
+
+
+
+
+
+
+
+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/binary.py
+ 189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222 def next ( self ):
+ """Reads the next complete `Value` from the internal buffer, raising
+ [ShortPacket][preserves.error.ShortPacket] if too few bytes are available, or
+ [DecodeError][preserves.error.DecodeError] if the input is invalid somehow.
+
+ """
+ tag = self . nextbyte ()
+ if tag == 0x80 : return self . wrap ( False )
+ if tag == 0x81 : return self . wrap ( True )
+ if tag == 0x82 : return self . wrap ( Float . from_bytes ( self . nextbytes ( 4 )))
+ if tag == 0x83 : return self . wrap ( struct . unpack ( '>d' , self . nextbytes ( 8 ))[ 0 ])
+ if tag == 0x84 : raise DecodeError ( 'Unexpected end-of-stream marker' )
+ if tag == 0x85 :
+ a = self . next ()
+ v = self . next ()
+ return self . unshift_annotation ( a , v )
+ if tag == 0x86 :
+ if self . decode_embedded is None :
+ raise DecodeError ( 'No decode_embedded function supplied' )
+ return self . wrap ( Embedded ( self . decode_embedded ( self . next ())))
+ if tag >= 0x90 and tag <= 0x9f : return self . wrap ( tag - ( 0xa0 if tag > 0x9c else 0x90 ))
+ if tag >= 0xa0 and tag <= 0xaf : return self . wrap ( self . nextint ( tag - 0xa0 + 1 ))
+ if tag == 0xb0 : return self . wrap ( self . nextint ( self . varint ()))
+ if tag == 0xb1 : return self . wrap ( self . nextbytes ( self . varint ()) . decode ( 'utf-8' ))
+ if tag == 0xb2 : return self . wrap ( self . nextbytes ( self . varint ()))
+ if tag == 0xb3 : return self . wrap ( Symbol ( self . nextbytes ( self . varint ()) . decode ( 'utf-8' )))
+ if tag == 0xb4 :
+ vs = self . nextvalues ()
+ if not vs : raise DecodeError ( 'Too few elements in encoded record' )
+ return self . wrap ( Record ( vs [ 0 ], vs [ 1 :]))
+ if tag == 0xb5 : return self . wrap ( tuple ( self . nextvalues ()))
+ if tag == 0xb6 : return self . wrap ( frozenset ( self . nextvalues ()))
+ if tag == 0xb7 : return self . wrap ( ImmutableDict . from_kvs ( self . nextvalues ()))
+ raise DecodeError ( 'Invalid tag: ' + hex ( tag ))
+
+
+
+
+
+
+
+
+
+
+
+try_next ()
+
+
+
+
+
+
+
Like next , but returns None
instead of raising
+ShortPacket .
+
+
+ Source code in preserves/binary.py
+ 224
+225
+226
+227
+228
+229
+230
+231
+232 def try_next ( self ):
+ """Like [next][preserves.binary.Decoder.next], but returns `None` instead of raising
+ [ShortPacket][preserves.error.ShortPacket]."""
+ start = self . index
+ try :
+ return self . next ()
+ except ShortPacket :
+ self . index = start
+ return None
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Encoder ( encode_embedded = lambda x : x , canonicalize = False , include_annotations = None )
+
+
+
+
+
+
+ Bases: BinaryCodec
+
+
+
Implementation of an encoder for the machine-oriented binary Preserves syntax.
+
>>> e = Encoder ()
+>>> e . append ( 123 )
+>>> e . append ( 'hello' )
+>>> e . append ( annotate ([], Symbol ( 'x' )))
+>>> e . contents ()
+b ' \xa0 { \xb1\x05 hello \x85\xb3\x01 x \xb5\x84 '
+
+
+
Parameters:
+
+
+
+ Name
+ Type
+ Description
+ Default
+
+
+
+
+ encode_embedded
+
+
+ function accepting an Embedded .embeddedValue and
+returning a Value
for serialization.
+
+ lambda x: x
+
+
+
+ canonicalize
+
+ bool
+
+ if True
, ensures the serialized data are in canonical
+form . This is slightly more work than
+producing potentially-non-canonical output.
+
+ False
+
+
+
+ include_annotations
+
+ bool | None
+
+ if None
, includes annotations in the output only when canonicalize
is False
,
+because canonical serialization of values demands omission of
+annotations . If explicitly True
or
+False
, however, annotations will be included resp. excluded no matter the
+canonicalize
setting. This can be used to get canonical ordering
+(canonicalize=True
) and annotations (include_annotations=True
).
+
+ None
+
+
+
+
+
+
Attributes:
+
+
+
+ Name
+ Type
+ Description
+
+
+
+
+ buffer
+
+ bytearray
+
+ accumulator for the output of the encoder
+
+
+
+
+
+
+ Source code in preserves/binary.py
+ 294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305 def __init__ ( self ,
+ encode_embedded = lambda x : x ,
+ canonicalize = False ,
+ include_annotations = None ):
+ super ( Encoder , self ) . __init__ ()
+ self . buffer = bytearray ()
+ self . _encode_embedded = encode_embedded
+ self . _canonicalize = canonicalize
+ if include_annotations is None :
+ self . include_annotations = not self . _canonicalize
+ else :
+ self . include_annotations = include_annotations
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+append ( v )
+
+
+
+
+
+
+
Extend self.buffer
with an encoding of v
.
+
+
+ Source code in preserves/binary.py
+ 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
+396
+397
+398
+399
+400
+401
+402
+403
+404
+405
+406 def append ( self , v ):
+ """Extend `self.buffer` with an encoding of `v`."""
+ v = preserve ( v )
+ if hasattr ( v , '__preserve_write_binary__' ):
+ v . __preserve_write_binary__ ( self )
+ elif v is False :
+ self . buffer . append ( 0x80 )
+ elif v is True :
+ self . buffer . append ( 0x81 )
+ elif isinstance ( v , float ):
+ self . buffer . append ( 0x83 )
+ self . buffer . extend ( struct . pack ( '>d' , v ))
+ elif isinstance ( v , numbers . Number ):
+ if v >= - 3 and v <= 12 :
+ self . buffer . append ( 0x90 + ( v if v >= 0 else v + 16 ))
+ else :
+ self . encodeint ( v )
+ elif isinstance ( v , bytes ):
+ self . encodebytes ( 2 , v )
+ elif isinstance ( v , basestring_ ):
+ self . encodebytes ( 1 , v . encode ( 'utf-8' ))
+ elif isinstance ( v , list ):
+ self . encodevalues ( 5 , v )
+ elif isinstance ( v , tuple ):
+ self . encodevalues ( 5 , v )
+ elif isinstance ( v , set ):
+ self . encodeset ( v )
+ elif isinstance ( v , frozenset ):
+ self . encodeset ( v )
+ elif isinstance ( v , dict ):
+ self . encodedict ( v )
+ else :
+ try :
+ i = iter ( v )
+ except TypeError :
+ i = None
+ if i is None :
+ self . cannot_encode ( v )
+ else :
+ self . encodevalues ( 5 , i )
+
+
+
+
+
+
+
+
+
+
+
+contents ()
+
+
+
+
+
+
+
Returns a bytes
constructed from the contents of self.buffer
.
+
+
+ Source code in preserves/binary.py
+ def contents ( self ):
+ """Returns a `bytes` constructed from the contents of `self.buffer`."""
+ return bytes ( self . buffer )
+
+
+
+
+
+
+
+
+
+
+
+reset ()
+
+
+
+
+
+
+
Clears self.buffer
to a fresh empty bytearray
.
+
+
+ Source code in preserves/binary.py
+ def reset ( self ):
+ """Clears `self.buffer` to a fresh empty `bytearray`."""
+ self . buffer = bytearray ()
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+canonicalize ( v , ** kwargs )
+
+
+
+
+
+
+
As encode , but sets canonicalize=True
in the
+Encoder constructor.
+
+
+ Source code in preserves/binary.py
+ 434
+435
+436
+437
+438
+439 def canonicalize ( v , ** kwargs ):
+ """As [encode][preserves.binary.encode], but sets `canonicalize=True` in the
+ [Encoder][preserves.binary.Encoder] constructor.
+
+ """
+ return encode ( v , canonicalize = True , ** kwargs )
+
+
+
+
+
+
+
+
+
+
+
+decode ( bs , ** kwargs )
+
+
+
+
+
+
+
Yields the first complete encoded value from bs
, passing kwargs
through to the
+Decoder constructor. Raises exceptions as per
+next .
+
+
Parameters:
+
+
+
+ Name
+ Type
+ Description
+ Default
+
+
+
+
+ bs
+
+ bytes
+
+ encoded data to decode
+
+ required
+
+
+
+
+
+
+ Source code in preserves/binary.py
+ 243
+244
+245
+246
+247
+248
+249
+250
+251
+252 def decode ( bs , ** kwargs ):
+ """Yields the first complete encoded value from `bs`, passing `kwargs` through to the
+ [Decoder][preserves.binary.Decoder] constructor. Raises exceptions as per
+ [next][preserves.binary.Decoder.next].
+
+ Args:
+ bs (bytes): encoded data to decode
+
+ """
+ return Decoder ( packet = bs , ** kwargs ) . next ()
+
+
+
+
+
+
+
+
+
+
+
+decode_with_annotations ( bs , ** kwargs )
+
+
+
+
+
+
+
Like decode , but supplying include_annotations=True
to the
+Decoder constructor.
+
+
+ Source code in preserves/binary.py
+ def decode_with_annotations ( bs , ** kwargs ):
+ """Like [decode][preserves.binary.decode], but supplying `include_annotations=True` to the
+ [Decoder][preserves.binary.Decoder] constructor."""
+ return Decoder ( packet = bs , include_annotations = True , ** kwargs ) . next ()
+
+
+
+
+
+
+
+
+
+
+
+encode ( v , ** kwargs )
+
+
+
+
+
+
+
Encode a single Value
v
to a byte string. Any supplied kwargs
are passed on to the
+underlying Encoder constructor.
+
+
+ Source code in preserves/binary.py
+ 427
+428
+429
+430
+431
+432 def encode ( v , ** kwargs ):
+ """Encode a single `Value` `v` to a byte string. Any supplied `kwargs` are passed on to the
+ underlying [Encoder][preserves.binary.Encoder] constructor."""
+ e = Encoder ( ** kwargs )
+ e . append ( v )
+ return e . contents ()
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Last update:
+ March 16, 2023
+
+
+ Created:
+ March 16, 2023
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/python/0.18.1/compare/index.html b/python/0.18.1/compare/index.html
new file mode 100644
index 0000000..1b5d5d4
--- /dev/null
+++ b/python/0.18.1/compare/index.html
@@ -0,0 +1,858 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Comparing Values - Python Preserves
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Comparing Values
+
+
+
+
+
+
+
+
+
Preserves specifies a total ordering and
+an equivalence between terms. The
+preserves.compare module implements the ordering and equivalence relations.
+
>>> cmp ( "bzz" , "c" )
+- 1
+>>> cmp ( True , [])
+- 1
+>>> lt ( "bzz" , "c" )
+True
+>>> eq ( "bzz" , "c" )
+False
+
+
Note that the ordering relates more values than Python's built-in ordering:
+
>>> [ 1 , 2 , 2 ] < [ 1 , 2 , "3" ]
+Traceback ( most recent call last ):
+ ..
+TypeError : '<' not supported between instances of 'int' and 'str'
+
+>>> lt ([ 1 , 2 , 2 ], [ 1 , 2 , "3" ])
+True
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+cmp ( a , b )
+
+
+
+
+
+
+
Returns -1
if a
< b
, or 0
if a
= b
, or 1
if a
> b
according to the
+Preserves total order .
+
+
+ Source code in preserves/compare.py
+ def cmp ( a , b ):
+ """Returns `-1` if `a` < `b`, or `0` if `a` = `b`, or `1` if `a` > `b` according to the
+ [Preserves total order](https://preserves.dev/preserves.html#total-order)."""
+ return _cmp ( preserve ( a ), preserve ( b ))
+
+
+
+
+
+
+
+
+
+
+
+eq ( a , b )
+
+
+
+
+
+
+
Returns True
iff a
= b
according to the Preserves equivalence
+relation .
+
+
+ Source code in preserves/compare.py
+ def eq ( a , b ):
+ """Returns `True` iff `a` = `b` according to the [Preserves equivalence
+ relation](https://preserves.dev/preserves.html#equivalence)."""
+ return _eq ( preserve ( a ), preserve ( b ))
+
+
+
+
+
+
+
+
+
+
+
+le ( a , b )
+
+
+
+
+
+
+
Returns True
iff a
≤ b
according to the Preserves total
+order .
+
+
+ Source code in preserves/compare.py
+ def le ( a , b ):
+ """Returns `True` iff `a` ≤ `b` according to the [Preserves total
+ order](https://preserves.dev/preserves.html#total-order)."""
+ return cmp ( a , b ) <= 0
+
+
+
+
+
+
+
+
+
+
+
+lt ( a , b )
+
+
+
+
+
+
+
Returns True
iff a
< b
according to the Preserves total
+order .
+
+
+ Source code in preserves/compare.py
+ def lt ( a , b ):
+ """Returns `True` iff `a` < `b` according to the [Preserves total
+ order](https://preserves.dev/preserves.html#total-order)."""
+ return cmp ( a , b ) < 0
+
+
+
+
+
+
+
+
+
+
+
+sorted ( iterable , * , key = lambda x : x , reverse = False )
+
+
+
+
+
+
+
Returns a sorted list built from iterable
, extracting a sort key using key
, and
+ordering according to the Preserves total
+order . Directly analogous to the
+built-in Python sorted
+routine , except uses the
+Preserves order instead of Python's less-than relation.
+
+
+ Source code in preserves/compare.py
+ 107
+108
+109
+110
+111
+112
+113
+114
+115
+116 def sorted ( iterable , * , key = lambda x : x , reverse = False ):
+ """Returns a sorted list built from `iterable`, extracting a sort key using `key`, and
+ ordering according to the [Preserves total
+ order](https://preserves.dev/preserves.html#total-order). Directly analogous to the
+ [built-in Python `sorted`
+ routine](https://docs.python.org/3/library/functions.html#sorted), except uses the
+ Preserves order instead of Python's less-than relation.
+
+ """
+ return _sorted ( iterable , key = lambda x : _key ( key ( x )), reverse = reverse )
+
+
+
+
+
+
+
+
+
+
+
+sorted_items ( d )
+
+
+
+
+
+
+
Given a dictionary d
, yields a list of (key, value)
tuples sorted by key
.
+
+
+ Source code in preserves/compare.py
+ def sorted_items ( d ):
+ """Given a dictionary `d`, yields a list of `(key, value)` tuples sorted by `key`."""
+ return sorted ( d . items (), key = _item_key )
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Last update:
+ March 16, 2023
+
+
+ Created:
+ March 16, 2023
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/python/0.18.1/error/index.html b/python/0.18.1/error/index.html
new file mode 100644
index 0000000..da4525e
--- /dev/null
+++ b/python/0.18.1/error/index.html
@@ -0,0 +1,678 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Codec errors - Python Preserves
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Codec errors
+
+
+
+
+
+
+
+
+
The preserves.error module exports various Error
subclasses.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Last update:
+ March 16, 2023
+
+
+ Created:
+ March 16, 2023
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/python/0.18.1/fold/index.html b/python/0.18.1/fold/index.html
new file mode 100644
index 0000000..55d673d
--- /dev/null
+++ b/python/0.18.1/fold/index.html
@@ -0,0 +1,651 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Traversing values - Python Preserves
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Traversing values
+
+
+
+
+
+
+
+
+
The preserves.fold module exports various utilities for traversing compound Value
s.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+map_embeddeds ( f , v )
+
+
+
+
+
+
+
Returns an equivalent copy of v
, except where each contained
+Embedded value is replaced by f
applied to the Embedded's
+embeddedValue
attribute.
+
>>> map_embeddeds ( lambda w : Embedded ( f 'w= { w } ' ), [ 'a' , Embedded ( 123 ), { 'z' : 6.0 }])
+( 'a' , #!'w=123', {'z': 6.0})
+
+
+
+ Source code in preserves/fold.py
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29 def map_embeddeds ( f , v ):
+ """Returns an [equivalent][preserves.compare.eq] copy of `v`, except where each contained
+ [Embedded][preserves.values.Embedded] value is replaced by `f` applied to the Embedded's
+ `embeddedValue` attribute.
+
+ ```python
+ >>> map_embeddeds(lambda w: Embedded(f'w={w}'), ['a', Embedded(123), {'z': 6.0}])
+ ('a', #!'w=123', {'z': 6.0})
+
+ ```
+ """
+ def walk ( v ):
+ if isinstance ( v , Embedded ):
+ return f ( v . embeddedValue )
+ elif isinstance ( v , ( list , tuple )):
+ return tuple ( walk ( w ) for w in v )
+ elif isinstance ( v , ( set , frozenset )):
+ return frozenset ( walk ( w ) for w in v )
+ elif isinstance ( v , dict ):
+ return ImmutableDict . from_kvs ( walk ( w ) for w in dict_kvs ( v ))
+ elif isinstance ( v , Record ):
+ return Record ( walk ( v . key ), walk ( v . fields ))
+ else :
+ return v
+ return walk ( v )
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Last update:
+ March 16, 2023
+
+
+ Created:
+ March 16, 2023
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/python/0.18.1/index.html b/python/0.18.1/index.html
new file mode 100644
index 0000000..d022a46
--- /dev/null
+++ b/python/0.18.1/index.html
@@ -0,0 +1,592 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Python Preserves
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Overview
+
+This package (preserves
on pypi.org ) implements
+Preserves for Python 3.x. It provides the core semantics as well
+as both the human-readable text syntax (a superset of JSON) and machine-oriented
+binary format (including
+canonicalization ) for Preserves. It also
+implements Preserves Schema and Preserves Path .
+
+What is Preserves?
+Preserves is a data model, with associated serialization formats.
+It supports records with user-defined labels , embedded
+references , and the usual suite of atomic and compound data types,
+including binary data as a distinct type from text strings. Its
+annotations allow separation of data from metadata such as comments,
+trace information, and provenance information.
+Preserves departs from many other data languages in defining how to
+compare two values. Comparison is based on the data model, not on
+syntax or on data structures of any particular implementation
+language.
+Mapping between Preserves values and Python values
+Preserves Value
s are categorized in the following way:
+ Value = Atom
+ | Compound
+ | Embedded
+
+ Atom = Boolean
+ | Float
+ | Double
+ | SignedInteger
+ | String
+ | ByteString
+ | Symbol
+
+ Compound = Record
+ | Sequence
+ | Set
+ | Dictionary
+
+Python's strings, byte strings, integers, booleans, and double-precision floats stand directly
+for their Preserves counterparts. Wrapper objects for Float and
+Symbol complete the suite of atomic types.
+Python's lists and tuples correspond to Preserves Sequence
s, and dicts and sets to
+Dictionary
and Set
values, respectively. Preserves Record
s are represented by
+Record objects. Finally, embedded values are represented by
+Embedded objects.
+
+
+
+
+
+ Last update:
+ March 16, 2023
+
+
+ Created:
+ March 16, 2023
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/python/0.18.1/merge/index.html b/python/0.18.1/merge/index.html
new file mode 100644
index 0000000..8197ab6
--- /dev/null
+++ b/python/0.18.1/merge/index.html
@@ -0,0 +1,795 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Merging values - Python Preserves
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Merging values
+
+
+
+
+
+
+
+
+
The preserves.merge module exports various utilities for merging Value
s.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+merge ( v0 , * vs , merge_embedded = None )
+
+
+
+
+
+
+
Repeatedly merges v0
with each element in vs
using merge2 ,
+returning the final result. The merge_embedded
parameter is passed on to merge2.
+
+
+ Source code in preserves/merge.py
+ 8
+ 9
+10
+11
+12
+13
+14 def merge ( v0 , * vs , merge_embedded = None ):
+ """Repeatedly merges `v0` with each element in `vs` using [merge2][preserves.merge.merge2],
+ returning the final result. The `merge_embedded` parameter is passed on to merge2."""
+ v = v0
+ for vN in vs :
+ v = merge2 ( v , vN , merge_embedded = merge_embedded )
+ return v
+
+
+
+
+
+
+
+
+
+
+
+merge2 ( a , b , merge_embedded = None )
+
+
+
+
+
+
+
Merges a
and b
, returning the result. Raises ValueError
if, during the merge, a
+pair of incompatible values is discovered.
+
If a
and b
are Embedded objects, their embeddedValue
s
+are merged using merge_embedded
, and the result is again wrapped in an
+Embedded object.
+
>>> merge2 ( 123 , 234 )
+Traceback ( most recent call last ):
+ ...
+ValueError : Cannot merge items
+>>> merge2 ( 123 , 123 )
+123
+>>> merge2 ( 'hi' , 0 )
+Traceback ( most recent call last ):
+ ...
+ValueError : Cannot merge items
+>>> merge2 ([ 1 , 2 ], [ 1 , 2 ])
+[ 1 , 2 ]
+>>> merge2 ([ 1 , 2 ], [ 1 , 3 ])
+Traceback ( most recent call last ):
+ ...
+ValueError : Cannot merge items
+>>> merge2 ({ 'a' : 1 , 'b' : 2 }, { 'a' : 1 , 'c' : 3 })
+{ 'a' : 1 , 'b' : 2 , 'c' : 3 }
+>>> merge2 ({ 'a' : 1 , 'b' : 2 }, { 'a' : 10 , 'c' : 3 })
+Traceback ( most recent call last ):
+ ...
+ValueError : Cannot merge items
+>>> merge2 ( Record ( 'a' , [ 1 , { 'x' : 2 }]), Record ( 'a' , [ 1 , { 'y' : 3 }]))
+a ( 1 , { 'x' : 2 , 'y' : 3 })
+
+
+
+ Source code in preserves/merge.py
+ 23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82 def merge2 ( a , b , merge_embedded = None ):
+ """Merges `a` and `b`, returning the result. Raises `ValueError` if, during the merge, a
+ pair of incompatible values is discovered.
+
+ If `a` and `b` are [Embedded][preserves.values.Embedded] objects, their `embeddedValue`s
+ are merged using `merge_embedded`, and the result is again wrapped in an
+ [Embedded][preserves.values.Embedded] object.
+
+ ```python
+ >>> merge2(123, 234)
+ Traceback (most recent call last):
+ ...
+ ValueError: Cannot merge items
+ >>> merge2(123, 123)
+ 123
+ >>> merge2('hi', 0)
+ Traceback (most recent call last):
+ ...
+ ValueError: Cannot merge items
+ >>> merge2([1, 2], [1, 2])
+ [1, 2]
+ >>> merge2([1, 2], [1, 3])
+ Traceback (most recent call last):
+ ...
+ ValueError: Cannot merge items
+ >>> merge2({'a': 1, 'b': 2}, {'a': 1, 'c': 3})
+ {'a': 1, 'b': 2, 'c': 3}
+ >>> merge2({'a': 1, 'b': 2}, {'a': 10, 'c': 3})
+ Traceback (most recent call last):
+ ...
+ ValueError: Cannot merge items
+ >>> merge2(Record('a', [1, {'x': 2}]), Record('a', [1, {'y': 3}]))
+ a(1, {'x': 2, 'y': 3})
+
+ ```
+
+ """
+ if a == b :
+ return a
+ if isinstance ( a , ( list , tuple )) and isinstance ( b , ( list , tuple )):
+ return merge_seq ( a , b )
+ if isinstance ( a , ( set , frozenset )) and isinstance ( b , ( set , frozenset )):
+ _die ()
+ if isinstance ( a , dict ) and isinstance ( b , dict ):
+ r = {}
+ for ( ak , av ) in a . items ():
+ bv = b . get ( ak , None )
+ r [ ak ] = av if bv is None else merge2 ( av , bv , merge_embedded = merge_embedded )
+ for ( bk , bv ) in b . items ():
+ if bk not in r :
+ r [ bk ] = bv
+ return r
+ if isinstance ( a , Record ) and isinstance ( b , Record ):
+ return Record ( merge2 ( a . key , b . key , merge_embedded = merge_embedded ),
+ merge_seq ( a . fields , b . fields , merge_embedded = merge_embedded ))
+ if isinstance ( a , Embedded ) and isinstance ( b , Embedded ):
+ m = ( merge_embedded or merge_embedded_id )( a . embeddedValue , b . embeddedValue )
+ if m is None : _die ()
+ return Embedded ( m )
+ _die ()
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Last update:
+ March 16, 2023
+
+
+ Created:
+ March 16, 2023
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/python/0.18.1/objects.inv b/python/0.18.1/objects.inv
new file mode 100644
index 0000000..774441a
Binary files /dev/null and b/python/0.18.1/objects.inv differ
diff --git a/python/0.18.1/path/index.html b/python/0.18.1/path/index.html
new file mode 100644
index 0000000..3d4980f
--- /dev/null
+++ b/python/0.18.1/path/index.html
@@ -0,0 +1,977 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Preserves Path - Python Preserves
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Preserves Path
+
+
+
+
+
+
+
+
+
The preserves.path module implements Preserves
+Path .
+
Preserves Path is roughly analogous to
+XPath , but for Preserves values: just as
+XPath selects portions of an XML document, a Preserves Path uses path expressions to select
+portions of a Value
.
+
Use parse to compile a path expression, and then use the
+exec method on the result to apply it to a given input:
+
parse ( PATH_EXPRESSION_STRING ) . exec ( PRESERVES_VALUE )
+ -> SEQUENCE_OF_PRESERVES_VALUES
+
+
Command-line usage
+
When preserves.path is run as a __main__
module, sys.argv[1]
is
+parsed , interpreted as a path expression, and
+run against human-readable values read from standard
+input. Each matching result is passed to stringify and printed to
+standard output.
+
Examples
+
Setup: Loading test data
+
The following examples use testdata
:
+
>>> with open ( 'tests/samples.bin' , 'rb' ) as f :
+... testdata = decode_with_annotations ( f . read ())
+
+
Recall that samples.bin
contains a binary-syntax form of the human-readable
+[samples.pr](https://preserves.dev/tests/samples.pr) test data file, intended to exercise most
+of the features of Preserves. In particular, the root
Value` in the file has a number of
+annotations (for documentation and other purposes).
+
Example 1: Selecting string-valued documentation annotations
+
The path expression .annotations ^ Documentation . 0 / string
proceeds in five steps:
+
+.annotations
selects each annotation on the root document
+^ Documentation
retains only those values (each an annotation of the root) that are Record
s with label equal to the symbol Documentation
+. 0
moves into the first child (the first field) of each such Record
, which in our case is a list of other Value
s
+/
selects all immediate children of these lists
+string
retains only those values that are strings
+
+
The result of evaluating it on testdata
is as follows:
+
>>> selector = parse ( '.annotations ^ Documentation . 0 / string' )
+>>> for result in selector . exec ( testdata ):
+... print ( stringify ( result ))
+"Individual test cases may be any of the following record types:"
+"In each test, let value = strip(annotatedValue),"
+" forward = value,"
+" back = value,"
+"except where test-case-specific values of `forward` and/or `back`"
+"are provided by the executing harness, and check the following"
+"numbered expectations according to the table above:"
+"Implementations may vary in their treatment of the difference between expectations"
+"13/14 and 16/17, depending on how they wish to treat end-of-stream conditions."
+
+
Example 2: Selecting tests with Records as their annotatedValues
+
The path expression // [.^ [= Test + = NondeterministicTest]] [. 1 rec]
proceeds in three steps:
+
+
+//
recursively decomposes the input, yielding all direct and indirect descendants of each input value
+
+
+[.^ [= Test + = NondeterministicTest]]
retains only those inputs (each a descendant of the root) that yield more than zero results when executed against the expression within the brackets:
+
+.^
selects only labels of values that are Records
, filtering by type and transforming in a single step
+[= Test + = NondeterministicTest]
again filters by a path expression:
+the infix +
operator takes the union of matches of its arguments
+the left-hand argument, = Test
selects values (remember, record labels) equal to the symbol Test
+the right-hand argument = NondeterministicTest
selects values equal to NondeterministicTest
+
+
+
+The result is thus all Record
s anywhere inside testdata
that have either Test
or NondeterministicTest
as their labels.
+
+
+[. 1 rec]
filters these Record
s by another path expression:
+
+. 1
selects their second field (fields are numbered from 0)
+rec
retains only values that are Record
s
+
+
+
+
Evaluating the expression against testdata
yields the following:
+
>>> selector = parse ( '// [.^ [= Test + = NondeterministicTest]] [. 1 rec]' )
+>>> for result in selector . exec ( testdata ):
+... print ( stringify ( result ))
+< Test #[tLMHY2FwdHVyZbSzB2Rpc2NhcmSEhA==] <capture <discard>>>
+< Test #[tLMHb2JzZXJ2ZbSzBXNwZWFrtLMHZGlzY2FyZIS0swdjYXB0dXJltLMHZGlzY2FyZISEhIQ=] <observe <speak <discard> <capture <discard>>>>>
+< Test #[tLWzBnRpdGxlZLMGcGVyc29ukrMFdGhpbmeRhKBlsQlCbGFja3dlbGy0swRkYXRloQcdkpOEsQJEcoQ=] <[titled person 2 thing 1] 101 "Blackwell" <date 1821 2 3> "Dr">>
+< Test #[tLMHZGlzY2FyZIQ=] <discard>>
+< Test #[tJe1hIQ=] <7 []>>
+< Test #[tLMHZGlzY2FyZLMIc3VycHJpc2WE] <discard surprise>>
+< Test #[tLEHYVN0cmluZ5OUhA==] <"aString" 3 4>>
+< Test #[tLSzB2Rpc2NhcmSEk5SE] <<discard> 3 4>>
+< Test #[hbMCYXK0swFShbMCYWazAWaE] @ar <R @af f>>
+< Test #[tIWzAmFyswFShbMCYWazAWaE] <@ar R @af f>>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Predicate = syntax . Predicate
+
+
+ module-attribute
+
+
+
+
+
+
+
+
Schema definition for representing a Preserves Path Predicate
.
+
+
+
+
+
+
+
+
+
+Selector = syntax . Selector
+
+
+ module-attribute
+
+
+
+
+
+
+
+
Schema definition for representing a sequence of Preserves Path Step
s.
+
+
+
+
+
+
+
+
+
+syntax = load_schema_file ( pathlib . Path ( __file__ ) . parent / 'path.prb' ) . path
+
+
+ module-attribute
+
+
+
+
+
+
+
+
This value is a Python representation of a Preserves Schema definition
+for the Preserves Path expression language. The language is defined in the file
+path.prs .
+
+
+
+
+
+
+
+
+
+
+
+exec ( self , v )
+
+
+
+
+
+
+
WARNING: This is not a function : it is a method on
+Selector , Predicate , and so on.
+
>>> sel = parse ( '/ [.length gt 1]' )
+>>> sel . exec ([ '' , 'a' , 'ab' , 'abc' , 'abcd' , 'bcd' , 'cd' , 'd' , '' ])
+( 'ab' , 'abc' , 'abcd' , 'bcd' , 'cd' )
+
+
+
+ Source code in preserves/path.py
+ 521
+522
+523
+524
+525
+526
+527
+528
+529
+530
+531
+532
+533
+534 @extend ( syntax . Function )
+def exec ( self , v ):
+ """WARNING: This is not a *function*: it is a *method* on
+ [Selector][preserves.path.Selector], [Predicate][preserves.path.Predicate], and so on.
+
+ ```python
+ >>> sel = parse('/ [.length gt 1]')
+ >>> sel.exec(['', 'a', 'ab', 'abc', 'abcd', 'bcd', 'cd', 'd', ''])
+ ('ab', 'abc', 'abcd', 'bcd', 'cd')
+
+ ```
+
+ """
+ return ( len ( self . selector . exec ( v )),)
+
+
+
+
+
+
+
+
+
+
+
+parse ( s )
+
+
+
+
+
+
+
Parse s
as a Preserves Path path expression, yielding a
+Selector object. Selectors (and Predicates etc.) have an
+exec method defined on them.
+
Raises ValueError
if s
is not a valid path expression.
+
+
+ Source code in preserves/path.py
+ 129
+130
+131
+132
+133
+134
+135
+136
+137 def parse ( s ):
+ """Parse `s` as a Preserves Path path expression, yielding a
+ [Selector][preserves.path.Selector] object. Selectors (and Predicates etc.) have an
+ [exec][preserves.path.exec] method defined on them.
+
+ Raises `ValueError` if `s` is not a valid path expression.
+
+ """
+ return parse_selector ( Parser ( s ))
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Last update:
+ March 16, 2023
+
+
+ Created:
+ March 16, 2023
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/python/0.18.1/schema/index.html b/python/0.18.1/schema/index.html
new file mode 100644
index 0000000..ff546da
--- /dev/null
+++ b/python/0.18.1/schema/index.html
@@ -0,0 +1,2378 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Preserves Schema - Python Preserves
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Preserves Schema
+A Preserves schema connects Preserves Value
s to host-language data
+structures. Each definition within a schema can be processed by a
+compiler to produce
+
+
+a simple host-language type definition ;
+
+
+a partial parsing function from Value
s to instances of the
+ produced type; and
+
+
+a total serialization function from instances of the type to
+ Value
s.
+
+
+Every parsed Value
retains enough information to always be able to
+be serialized again, and every instance of a host-language data
+structure contains, by construction, enough information to be
+successfully serialized.
+Schema support in Python
+
+
+
+
+
+
+
+
+
The preserves.schema module implements Preserves
+Schema for Python.
+
A Schema source file (like this one ) is first
+compiled using preserves-schemac
to
+produce a binary-syntax schema bundle containing schema module definitons (like this
+one ). Python code
+then loads the bundle, exposing its contents as Namespace s
+ultimately containing SchemaObject s.
+
Examples
+
Setup: Loading a schema bundle
+
For our running example, we will use schemas associated with the Syndicated Actor
+Model . (The schema bundle
+is a copy of this
+file
+from the syndicate-protocols
repository.)
+
To load a schema bundle, use load_schema_file (or,
+alternatively, use Compiler directly):
+
>>> bundle = load_schema_file ( 'docs/syndicate-protocols-schema-bundle.bin' )
+>>> type ( bundle )
+< class ' preserves . schema . Namespace '>
+
+
The top-level entries in the loaded bundle are schema modules. Let's examine the stream
+schema module, whose source
+code
+indicates that it should contain definitions for Mode
, Source
, Sink
, etc.:
+
>>> bundle . stream # doctest: +ELLIPSIS
+{ 'Mode' : < class ' stream . Mode '>, ' Sink ': <class ' stream . Sink '>, ...}
+
+
Example 1: stream.StreamListenerError, a product type
+
Drilling down further, let's consider the definition of
+StreamListenerError , which appears in the source as
+
StreamListenerError = <stream-listener-error @spec any @message string> .
+
+
This reads, in the Preserves Schema
+language , as the
+definition of a simple product type (record, class, object) with two named fields spec
and
+message
. Parsing a value into a StreamListenerError
will only succeed if it's a record, if
+the label matches, the second field (message
) is a string, and it has exactly two fields.
+
>>> bundle . stream . StreamListenerError
+< class ' stream . StreamListenerError '>
+
+
The StreamListenerError
class includes a decode
+method that analyzes an input value:
+
>>> bundle . stream . StreamListenerError . decode (
+... parse ( '<stream-listener-error <xyz> "an error">' ))
+StreamListenerError { 'spec' : #xyz(), 'message': 'an error'}
+
+
If invalid input is supplied, decode will raise
+SchemaDecodeFailed , which includes helpful information
+for diagnosing the problem (as we will see below, this is especially useful for parsers for sum
+types):
+
>>> bundle . stream . StreamListenerError . decode (
+... parse ( '<i-am-invalid>' ))
+Traceback ( most recent call last ):
+ ...
+preserves . schema . SchemaDecodeFailed : Could not decode i - am - invalid using < class ' stream . StreamListenerError '>
+Most likely reason : in stream . StreamListenerError : < lit stream - listener - error > didn 't match i-am-invalid
+Full explanation :
+ in stream . StreamListenerError : < lit stream - listener - error > didn 't match i-am-invalid
+
+
Alternatively, the try_decode method catches
+SchemaDecodeFailed , transforming it into None
:
+
>>> bundle . stream . StreamListenerError . try_decode (
+... parse ( '<stream-listener-error <xyz> "an error">' ))
+StreamListenerError { 'spec' : #xyz(), 'message': 'an error'}
+>>> bundle . stream . StreamListenerError . try_decode (
+... parse ( '<i-am-invalid>' ))
+
+
The class can also be instantiated directly:
+
>>> err = bundle . stream . StreamListenerError ( Record ( Symbol ( 'xyz' ), []), 'an error' )
+>>> err
+StreamListenerError { 'spec' : #xyz(), 'message': 'an error'}
+
+
The fields and contents of instances can be queried:
+
>>> err . spec
+#xyz()
+>>> err . message
+'an error'
+
+
And finally, instances can of course be serialized and encoded:
+
>>> print ( stringify ( err ))
+< stream - listener - error < xyz > "an error" >
+>>> canonicalize ( err )
+b ' \xb4\xb3\x15 stream-listener-error \xb4\xb3\x03 xyz \x84\xb1\x08 an error \x84 '
+
+
Example 2: stream.Mode, a sum type
+
Now let's consider the definition of
+Mode ,
+which appears in the source as
+
Mode = =bytes / @lines LineMode / <packet @size int> / <object @description any> .
+
+
This reads, in the Preserves Schema
+language , as an
+alternation (disjoint union, variant, sum type) of four possible kinds of value: the symbol
+bytes
; a LineMode
value; a record with packet
as its label and an integer as its only
+field; or a record with object
as its label and any kind of value as its only field. In
+Python, this becomes:
+
>>> bundle . stream . Mode . bytes
+< class ' stream . Mode . bytes '>
+>>> bundle . stream . Mode . lines
+< class ' stream . Mode . lines '>
+>>> bundle . stream . Mode . packet
+< class ' stream . Mode . packet '>
+>>> bundle . stream . Mode . object
+< class ' stream . Mode . object '>
+
+
As before, Mode
includes a decode method that analyzes
+an input value:
+
>>> bundle . stream . Mode . decode ( parse ( 'bytes' ))
+Mode . bytes ()
+>>> bundle . stream . Mode . decode ( parse ( 'lf' ))
+Mode . lines ( LineMode . lf ())
+>>> bundle . stream . Mode . decode ( parse ( '<packet 123>' ))
+Mode . packet { 'size' : 123 }
+>>> bundle . stream . Mode . decode ( parse ( '<object "?">' ))
+Mode . object { 'description' : '?' }
+
+
Invalid input causes SchemaDecodeFailed to be raised:
+
>>> bundle . stream . Mode . decode ( parse ( '<i-am-not-a-valid-mode>' ))
+Traceback ( most recent call last ):
+ ...
+preserves . schema . SchemaDecodeFailed : Could not decode < i - am - not - a - valid - mode > using < class ' stream . Mode '>
+Most likely reason : in stream . LineMode . crlf : < lit crlf > didn 't match <i-am-not-a-valid-mode>
+Full explanation :
+ in stream . Mode : matching < i - am - not - a - valid - mode >
+ in stream . Mode . bytes : < lit bytes > didn 't match <i-am-not-a-valid-mode>
+ in stream . Mode . lines : < ref [] LineMode > didn 't match <i-am-not-a-valid-mode>
+ in stream . LineMode : matching < i - am - not - a - valid - mode >
+ in stream . LineMode . lf : < lit lf > didn 't match <i-am-not-a-valid-mode>
+ in stream . LineMode . crlf : < lit crlf > didn 't match <i-am-not-a-valid-mode>
+ in stream . Mode . packet : < lit packet > didn 't match i-am-not-a-valid-mode
+ in stream . Mode . object : < lit object > didn 't match i-am-not-a-valid-mode
+
+
The "full explanation" includes details on which parses were attempted, and why they failed.
+
Again, the try_decode method catches
+SchemaDecodeFailed , transforming it into None
:
+
>>> bundle . stream . Mode . try_decode ( parse ( 'bytes' ))
+Mode . bytes ()
+>>> bundle . stream . Mode . try_decode ( parse ( '<i-am-not-a-valid-mode>' ))
+
+
Direct instantiation is done with the variant classes, not with Mode
itself:
+
>>> bundle . stream . Mode . bytes ()
+Mode . bytes ()
+>>> bundle . stream . Mode . lines ( bundle . stream . LineMode . lf ())
+Mode . lines ( LineMode . lf ())
+>>> bundle . stream . Mode . packet ( 123 )
+Mode . packet { 'size' : 123 }
+>>> bundle . stream . Mode . object ( '?' )
+Mode . object { 'description' : '?' }
+
+
Fields and contents can be queried as usual:
+
>>> bundle . stream . Mode . lines ( bundle . stream . LineMode . lf ()) . value
+LineMode . lf ()
+>>> bundle . stream . Mode . packet ( 123 ) . size
+123
+>>> bundle . stream . Mode . object ( '?' ) . description
+'?'
+
+
And serialization and encoding are also as expected:
+
>>> print ( stringify ( bundle . stream . Mode . bytes ()))
+bytes
+>>> print ( stringify ( bundle . stream . Mode . lines ( bundle . stream . LineMode . lf ())))
+lf
+>>> print ( stringify ( bundle . stream . Mode . packet ( 123 )))
+< packet 123 >
+>>> print ( stringify ( bundle . stream . Mode . object ( '?' )))
+< object "?" >
+>>> canonicalize ( bundle . stream . Mode . object ( '?' ))
+b ' \xb4\xb3\x06 object \xb1\x01 ? \x84 '
+
+
Finally, the VARIANT attribute of instances
+allows code to dispatch on what kind of data it is handling at a given moment:
+
>>> bundle . stream . Mode . bytes () . VARIANT
+#bytes
+>>> bundle . stream . Mode . lines ( bundle . stream . LineMode . lf ()) . VARIANT
+#lines
+>>> bundle . stream . Mode . packet ( 123 ) . VARIANT
+#packet
+>>> bundle . stream . Mode . object ( '?' ) . VARIANT
+#object
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Compiler ()
+
+
+
+
+
+
+
+
Instances of Compiler populate an initially-empty
+Namespace by loading and compiling schema bundle files.
+
>>> c = Compiler ()
+>>> c . load ( 'docs/syndicate-protocols-schema-bundle.bin' )
+>>> type ( c . root )
+< class ' preserves . schema . Namespace '>
+
+
+
Attributes:
+
+
+
+ Name
+ Type
+ Description
+
+
+
+
+ root
+
+ Namespace
+
+ the root namespace into which top-level schema modules are installed.
+
+
+
+
+
+
+ Source code in preserves/schema.py
+ def __init__ ( self ):
+ self . root = Namespace (())
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+load ( filename )
+
+
+
+
+
+
+
Opens the file at filename
, passing the resulting file object to
+load_filelike .
+
+
+ Source code in preserves/schema.py
+ 936
+937
+938
+939
+940
+941 def load ( self , filename ):
+ """Opens the file at `filename`, passing the resulting file object to
+ [load_filelike][preserves.schema.Compiler.load_filelike]."""
+ filename = pathlib . Path ( filename )
+ with open ( filename , 'rb' ) as f :
+ self . load_filelike ( f , filename . stem )
+
+
+
+
+
+
+
+
+
+
+
+load_filelike ( f , module_name = None )
+
+
+
+
+
+
+
Reads a meta.Bundle
or meta.Schema
from the filelike object f
, compiling and
+installing it in self.root
. If f
contains a bundle, module_name
is not used,
+since the schema modules in the bundle know their own names; if f
contains a plain
+schema module, however, module_name
is used directly if it is a string, and if it is
+None
, a suitable module name is computed from the name
attribute of f
, if it is
+present. If name
is absent in that case, ValueError
is raised.
+
+
+ Source code in preserves/schema.py
+ 915
+916
+917
+918
+919
+920
+921
+922
+923
+924
+925
+926
+927
+928
+929
+930
+931
+932
+933
+934 def load_filelike ( self , f , module_name = None ):
+ """Reads a `meta.Bundle` or `meta.Schema` from the filelike object `f`, compiling and
+ installing it in `self.root`. If `f` contains a bundle, `module_name` is not used,
+ since the schema modules in the bundle know their own names; if `f` contains a plain
+ schema module, however, `module_name` is used directly if it is a string, and if it is
+ `None`, a suitable module name is computed from the `name` attribute of `f`, if it is
+ present. If `name` is absent in that case, `ValueError` is raised.
+
+ """
+ x = Decoder ( f . read ()) . next ()
+ if x . key == SCHEMA :
+ if module_name is None :
+ if hasattr ( f , 'name' ):
+ module_name = pathlib . Path ( f . name ) . stem
+ else :
+ raise ValueError ( 'Cannot load schema module from filelike object without a module_name' )
+ self . load_schema (( Symbol ( module_name ),), x )
+ elif x . key == BUNDLE :
+ for ( p , s ) in x [ 0 ] . items ():
+ self . load_schema ( p , s )
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Definition ( * args , ** kwargs )
+
+
+
+
+
+
+ Bases: SchemaObject
+
+
+
Subclasses of Definition are used to represent both
+standalone non-alternation definitions as well as alternatives within an
+Enumeration .
+
>>> bundle = load_schema_file ( 'docs/syndicate-protocols-schema-bundle.bin' )
+
+>>> bundle . stream . StreamListenerError . FIELD_NAMES
+[ 'spec' , 'message' ]
+>>> bundle . stream . StreamListenerError . SAFE_FIELD_NAMES
+[ 'spec' , 'message' ]
+>>> bundle . stream . StreamListenerError . ENUMERATION is None
+True
+
+>>> bundle . stream . Mode . object . FIELD_NAMES
+[ 'description' ]
+>>> bundle . stream . Mode . object . SAFE_FIELD_NAMES
+[ 'description' ]
+>>> bundle . stream . Mode . object . ENUMERATION is bundle . stream . Mode
+True
+
+>>> bundle . stream . CreditAmount . count . FIELD_NAMES
+[]
+>>> bundle . stream . CreditAmount . count . SAFE_FIELD_NAMES
+[]
+>>> bundle . stream . CreditAmount . count . ENUMERATION is bundle . stream . CreditAmount
+True
+
+>>> bundle . stream . CreditAmount . decode ( parse ( '123' ))
+CreditAmount . count ( 123 )
+>>> bundle . stream . CreditAmount . count ( 123 )
+CreditAmount . count ( 123 )
+>>> bundle . stream . CreditAmount . count ( 123 ) . value
+123
+
+
+
+
+ Source code in preserves/schema.py
+ 689
+690
+691
+692
+693
+694
+695
+696
+697
+698
+699
+700
+701
+702
+703
+704
+705
+706
+707
+708
+709
+710
+711
+712
+713
+714 def __init__ ( self , * args , ** kwargs ):
+ self . _fields = args
+ if self . SIMPLE :
+ if self . EMPTY :
+ if len ( args ) != 0 :
+ raise TypeError ( ' %s takes no arguments' % ( self . _constructor_name (),))
+ else :
+ if len ( args ) != 1 :
+ raise TypeError ( ' %s needs exactly one argument' % ( self . _constructor_name (),))
+ self . value = args [ 0 ]
+ else :
+ i = 0
+ for arg in args :
+ if i >= len ( self . FIELD_NAMES ):
+ raise TypeError ( ' %s given too many positional arguments' % ( self . _constructor_name (),))
+ setattr ( self , self . SAFE_FIELD_NAMES [ i ], arg )
+ i = i + 1
+ for ( argname , arg ) in kwargs . items ():
+ if hasattr ( self , argname ):
+ raise TypeError ( ' %s given duplicate attribute: %r ' % ( self . _constructor_name , argname ))
+ if argname not in self . SAFE_FIELD_NAMES :
+ raise TypeError ( ' %s given unknown attribute: %r ' % ( self . _constructor_name , argname ))
+ setattr ( self , argname , arg )
+ i = i + 1
+ if i != len ( self . FIELD_NAMES ):
+ raise TypeError ( ' %s needs argument(s) %r ' % ( self . _constructor_name (), self . FIELD_NAMES ))
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ENUMERATION = None
+
+
+ class-attribute
+
+
+
+
+
+
+
+
None
for standalone top-level definitions with a module; otherwise, an
+Enumeration subclass representing a top-level alternation
+definition.
+
+
+
+
+
+
+
+
+
+FIELD_NAMES = []
+
+
+ class-attribute
+
+
+
+
+
+
+
+
List of strings: names of the fields contained within this definition, if it has named
+fields at all; otherwise, an empty list, and the definition is a simple wrapper for another
+value, in which case that value is accessed via the value
attribute.
+
+
+
+
+
+
+
+
+
+SAFE_FIELD_NAMES = []
+
+
+ class-attribute
+
+
+
+
+
+
+
+
The list produced by mapping safeattrname over
+FIELD_NAMES .
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Enumeration ()
+
+
+
+
+
+
+ Bases: SchemaObject
+
+
+
Subclasses of Enumeration represent a group of variant
+options within a sum type.
+
>>> bundle = load_schema_file ( 'docs/syndicate-protocols-schema-bundle.bin' )
+
+>>> import pprint
+>>> pprint . pprint ( bundle . stream . Mode . VARIANTS )
+[( #bytes, <class 'stream.Mode.bytes'>),
+ ( #lines, <class 'stream.Mode.lines'>),
+ ( #packet, <class 'stream.Mode.packet'>),
+ ( #object, <class 'stream.Mode.object'>)]
+
+>>> bundle . stream . Mode . VARIANTS [ 0 ][ 1 ] is bundle . stream . Mode . bytes
+True
+
+
+
+
+ Source code in preserves/schema.py
+ def __init__ ( self ):
+ raise TypeError ( 'Cannot create instance of Enumeration' )
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+VARIANTS = None
+
+
+ class-attribute
+
+
+
+
+
+
+
+
List of (Symbol, SchemaObject class)
tuples representing the possible options within
+this sum type.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Namespace ( prefix )
+
+
+
+
+
+
+
+
A Namespace is a dictionary-like object representing a
+schema module that knows its location in a schema module hierarchy and whose attributes
+correspond to definitions and submodules within the schema module.
+
+
Attributes:
+
+
+
+ Name
+ Type
+ Description
+
+
+
+
+ _prefix
+
+ tuple[Symbol ]
+
+ path to this module/Namespace from the root Namespace
+
+
+
+
+
+
+ Source code in preserves/schema.py
+ def __init__ ( self , prefix ):
+ self . _prefix = prefix
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+SchemaDecodeFailed ( cls , p , v , failures = None )
+
+
+
+
+
+
+ Bases: ValueError
+
+
+
Raised when decode cannot find a way to parse a
+given input.
+
+
Attributes:
+
+
+
+ Name
+ Type
+ Description
+
+
+
+
+ cls
+
+ class
+
+ the SchemaObject subclass attempting the parse
+
+
+ pattern
+
+ Value
+
+ the failing pattern, a Value
conforming to schema meta.Pattern
+
+
+ value
+
+ Value
+
+ the unparseable value
+
+
+ failures
+
+ list[SchemaDecodeFailed ]
+
+ descriptions of failed paths attempted during the match this failure describes
+
+
+
+
+
+
+ Source code in preserves/schema.py
+ 311
+312
+313
+314
+315
+316 def __init__ ( self , cls , p , v , failures = None ):
+ super () . __init__ ()
+ self . cls = cls
+ self . pattern = p
+ self . value = v
+ self . failures = [] if failures is None else failures
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ SchemaObject
+
+
+
+
+
+
+
+
+
Base class for classes representing grammatical productions in a schema: instances of
+SchemaObject represent schema definitions . This is an
+abstract class, as are its subclasses Enumeration and
+Definition . It is subclasses of those subclasses,
+automatically produced during schema loading, that are actually instantiated.
+
>>> bundle = load_schema_file ( 'docs/syndicate-protocols-schema-bundle.bin' )
+
+>>> bundle . stream . Mode . mro ()[ 1 : - 1 ]
+[ < class ' preserves . schema . Enumeration '>, <class ' preserves . schema . SchemaObject '>]
+
+>>> bundle . stream . Mode . packet . mro ()[ 1 : - 1 ]
+[ < class ' stream . Mode . _ALL '>, <class ' preserves . schema . Definition '>, <class ' preserves . schema . SchemaObject '>]
+
+>>> bundle . stream . StreamListenerError . mro ()[ 1 : - 1 ]
+[ < class ' preserves . schema . Definition '>, <class ' preserves . schema . SchemaObject '>]
+
+
Illustrating the class attributes on SchemaObject
+subclasses:
+
>>> bundle . stream . Mode . ROOTNS is bundle
+True
+
+>>> print ( stringify ( bundle . stream . Mode . SCHEMA , indent = 2 ))
+< or [
+ [
+ "bytes"
+ < lit bytes >
+ ]
+ [
+ "lines"
+ < ref [] LineMode >
+ ]
+ [
+ "packet"
+ < rec < lit packet > < tuple [ < named size < atom SignedInteger >> ] >>
+ ]
+ [
+ "object"
+ < rec < lit object > < tuple [ < named description any > ] >>
+ ]
+] >
+
+>>> bundle . stream . Mode . MODULE_PATH
+( #stream,)
+
+>>> bundle . stream . Mode . NAME
+#Mode
+
+>>> bundle . stream . Mode . VARIANT is None
+True
+>>> bundle . stream . Mode . packet . VARIANT
+#packet
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+MODULE_PATH = None
+
+
+ class-attribute
+
+
+
+
+
+
+
+
A sequence (tuple) of Symbol s naming the path from the root
+to the schema module containing this definition.
+
+
+
+
+
+
+
+
+
+NAME = None
+
+
+ class-attribute
+
+
+
+
+
+
+
+
A Symbol naming this definition within its module.
+
+
+
+
+
+
+
+
+
+ROOTNS = None
+
+
+ class-attribute
+
+
+
+
+
+
+
+
A Namespace that is the top-level environment for all
+bundles included in the Compiler run that produced this
+SchemaObject .
+
+
+
+
+
+
+
+
+
+SCHEMA = None
+
+
+ class-attribute
+
+
+
+
+
+
+
+
A Value
conforming to schema meta.Definition
(and thus often to meta.Pattern
+etc.), interpreted by the SchemaObject machinery to drive
+parsing, unparsing and so forth.
+
+
+
+
+
+
+
+
+
+VARIANT = None
+
+
+ class-attribute
+
+
+
+
+
+
+
+
None
for Definition s (such as
+bundle.stream.StreamListenerError
above) and for overall
+Enumeration s (such as bundle.stream.Mode
), or a
+Symbol for variant definitions contained within an enumeration
+(such as bundle.stream.Mode.packet
).
+
+
+
+
+
+
+
+
+
+
+
+__preserve__ ()
+
+
+
+
+
+
+
Called by preserves.values.preserve : unparses the information represented by
+this instance, using its schema definition, to produce a Preserves Value
.
+
+
+ Source code in preserves/schema.py
+ def __preserve__ ( self ):
+ """Called by [preserves.values.preserve][]: *unparses* the information represented by
+ this instance, using its schema definition, to produce a Preserves `Value`."""
+ raise NotImplementedError ( 'Subclass responsibility' )
+
+
+
+
+
+
+
+
+
+
+
+decode ( v )
+
+
+ classmethod
+
+
+
+
+
+
+
+
Parses v
using the SCHEMA , returning a
+(sub)instance of SchemaObject or raising
+SchemaDecodeFailed .
+
+
+ Source code in preserves/schema.py
+ 444
+445
+446
+447
+448
+449 @classmethod
+def decode ( cls , v ):
+ """Parses `v` using the [SCHEMA][preserves.schema.SchemaObject.SCHEMA], returning a
+ (sub)instance of [SchemaObject][preserves.schema.SchemaObject] or raising
+ [SchemaDecodeFailed][preserves.schema.SchemaDecodeFailed]."""
+ raise NotImplementedError ( 'Subclass responsibility' )
+
+
+
+
+
+
+
+
+
+
+
+try_decode ( v )
+
+
+ classmethod
+
+
+
+
+
+
+
+
Parses v
using the SCHEMA , returning a
+(sub)instance of SchemaObject or None
if parsing
+failed.
+
+
+ Source code in preserves/schema.py
+ 451
+452
+453
+454
+455
+456
+457
+458
+459 @classmethod
+def try_decode ( cls , v ):
+ """Parses `v` using the [SCHEMA][preserves.schema.SchemaObject.SCHEMA], returning a
+ (sub)instance of [SchemaObject][preserves.schema.SchemaObject] or `None` if parsing
+ failed."""
+ try :
+ return cls . decode ( v )
+ except SchemaDecodeFailed :
+ return None
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+extend ( cls )
+
+
+
+
+
+
+
A decorator for function definitions. Useful for adding behaviour to the classes
+resulting from loading a schema module:
+
>>> bundle = load_schema_file ( 'docs/syndicate-protocols-schema-bundle.bin' )
+
+>>> @extend ( bundle . stream . LineMode . lf )
+... def what_am_i ( self ):
+... return 'I am a LINEFEED linemode'
+
+>>> @extend ( bundle . stream . LineMode . crlf )
+... def what_am_i ( self ):
+... return 'I am a CARRIAGE-RETURN-PLUS-LINEFEED linemode'
+
+>>> bundle . stream . LineMode . lf ()
+LineMode . lf ()
+>>> bundle . stream . LineMode . lf () . what_am_i ()
+'I am a LINEFEED linemode'
+
+>>> bundle . stream . LineMode . crlf ()
+LineMode . crlf ()
+>>> bundle . stream . LineMode . crlf () . what_am_i ()
+'I am a CARRIAGE-RETURN-PLUS-LINEFEED linemode'
+
+
+
+ Source code in preserves/schema.py
+ 977
+ 978
+ 979
+ 980
+ 981
+ 982
+ 983
+ 984
+ 985
+ 986
+ 987
+ 988
+ 989
+ 990
+ 991
+ 992
+ 993
+ 994
+ 995
+ 996
+ 997
+ 998
+ 999
+1000
+1001
+1002
+1003
+1004
+1005
+1006
+1007
+1008
+1009 def extend ( cls ):
+ """A decorator for function definitions. Useful for adding *behaviour* to the classes
+ resulting from loading a schema module:
+
+ ```python
+ >>> bundle = load_schema_file('docs/syndicate-protocols-schema-bundle.bin')
+
+ >>> @extend(bundle.stream.LineMode.lf)
+ ... def what_am_i(self):
+ ... return 'I am a LINEFEED linemode'
+
+ >>> @extend(bundle.stream.LineMode.crlf)
+ ... def what_am_i(self):
+ ... return 'I am a CARRIAGE-RETURN-PLUS-LINEFEED linemode'
+
+ >>> bundle.stream.LineMode.lf()
+ LineMode.lf()
+ >>> bundle.stream.LineMode.lf().what_am_i()
+ 'I am a LINEFEED linemode'
+
+ >>> bundle.stream.LineMode.crlf()
+ LineMode.crlf()
+ >>> bundle.stream.LineMode.crlf().what_am_i()
+ 'I am a CARRIAGE-RETURN-PLUS-LINEFEED linemode'
+
+ ```
+
+ """
+ @wraps ( cls )
+ def extender ( f ):
+ setattr ( cls , f . __name__ , f )
+ return f
+ return extender
+
+
+
+
+
+
+
+
+
+
+
+load_schema_file ( filename )
+
+
+
+
+
+
+
Simple entry point to the compiler: creates a Compiler ,
+calls load on it, and returns its root
+Namespace .
+
>>> bundle = load_schema_file ( 'docs/syndicate-protocols-schema-bundle.bin' )
+>>> type ( bundle )
+< class ' preserves . schema . Namespace '>
+
+
+
+ Source code in preserves/schema.py
+ 960
+961
+962
+963
+964
+965
+966
+967
+968
+969
+970
+971
+972
+973
+974 def load_schema_file ( filename ):
+ """Simple entry point to the compiler: creates a [Compiler][preserves.schema.Compiler],
+ calls [load][preserves.schema.Compiler.load] on it, and returns its `root`
+ [Namespace][preserves.schema.Namespace].
+
+ ```python
+ >>> bundle = load_schema_file('docs/syndicate-protocols-schema-bundle.bin')
+ >>> type(bundle)
+ <class 'preserves.schema.Namespace'>
+
+ ```
+ """
+ c = Compiler ()
+ c . load ( filename )
+ return c . root
+
+
+
+
+
+
+
+
+
+
+
+safeattrname ( k )
+
+
+
+
+
+
+
Escapes Python keywords by prepending _
; passes all other strings through.
+
+
+ Source code in preserves/schema.py
+ def safeattrname ( k ):
+ """Escapes Python keywords by prepending `_`; passes all other strings through."""
+ return k + '_' if keyword . iskeyword ( k ) else k
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Last update:
+ March 17, 2023
+
+
+ Created:
+ March 16, 2023
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/python/0.18.1/search/search_index.json b/python/0.18.1/search/search_index.json
new file mode 100644
index 0000000..f28b640
--- /dev/null
+++ b/python/0.18.1/search/search_index.json
@@ -0,0 +1 @@
+{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"Overview","text":"pip install preserves\n
This package (preserves
on pypi.org) implements Preserves for Python 3.x. It provides the core semantics as well as both the human-readable text syntax (a superset of JSON) and machine-oriented binary format (including canonicalization) for Preserves. It also implements Preserves Schema and Preserves Path.
Main package API: preserves "},{"location":"#what-is-preserves","title":"What is Preserves?","text":"Preserves is a data model, with associated serialization formats.
It supports records with user-defined labels, embedded references, and the usual suite of atomic and compound data types, including binary data as a distinct type from text strings. Its annotations allow separation of data from metadata such as comments, trace information, and provenance information.
Preserves departs from many other data languages in defining how to compare two values. Comparison is based on the data model, not on syntax or on data structures of any particular implementation language.
"},{"location":"#mapping-between-preserves-values-and-python-values","title":"Mapping between Preserves values and Python values","text":"Preserves Value
s are categorized in the following way:
Value = Atom\n | Compound\n | Embedded\n\n Atom = Boolean\n | Float\n | Double\n | SignedInteger\n | String\n | ByteString\n | Symbol\n\n Compound = Record\n | Sequence\n | Set\n | Dictionary\n
Python's strings, byte strings, integers, booleans, and double-precision floats stand directly for their Preserves counterparts. Wrapper objects for Float and Symbol complete the suite of atomic types.
Python's lists and tuples correspond to Preserves Sequence
s, and dicts and sets to Dictionary
and Set
values, respectively. Preserves Record
s are represented by Record objects. Finally, embedded values are represented by Embedded objects.
"},{"location":"api/","title":"The top-level preserves package","text":"import preserves\n
The main package re-exports a subset of the exports of its constituent modules:
From preserves.values:
Annotated Embedded Float ImmutableDict Record Symbol annotate is_annotated preserve strip_annotations From preserves.error:
DecodeError EncodeError ShortPacket From preserves.binary:
Decoder Encoder canonicalize decode decode_with_annotations encode From preserves.text:
Formatter Parser parse parse_with_annotations stringify From preserves.compare:
From preserves.merge:
It also exports the compare and fold modules themselves, permitting patterns like
>>> from preserves import *\n>>> compare.cmp(123, 234)\n-1\n
Finally, it provides a few utility aliases for common tasks:
"},{"location":"api/#preserves.dumps","title":"dumps = stringify
module-attribute
","text":"This alias for stringify
provides a familiar pythonesque name for converting a Preserves Value
to a string.
"},{"location":"api/#preserves.loads","title":"loads = parse
module-attribute
","text":"This alias for parse
provides a familiar pythonesque name for converting a string to a Preserves Value
.
"},{"location":"binary/","title":"Machine-oriented binary syntax","text":"The preserves.binary module implements the Preserves machine-oriented binary syntax.
The main entry points are functions encode, canonicalize, decode, and decode_with_annotations.
>>> encode(Record(Symbol('hi'), []))\nb'\\xb4\\xb3\\x02hi\\x84'\n>>> decode(b'\\xb4\\xb3\\x02hi\\x84')\n#hi()\n
"},{"location":"binary/#preserves.binary.Decoder","title":"Decoder(packet=b'', include_annotations=False, decode_embedded=lambda x: x)
","text":" Bases: BinaryCodec
Implementation of a decoder for the machine-oriented binary Preserves syntax.
Parameters:
Name Type Description Default packet
bytes
initial contents of the input buffer; may subsequently be extended by calling extend.
b''
include_annotations
bool
if True
, wrap each value and subvalue in an Annotated object.
False
decode_embedded
function accepting a Value
and returning a possibly-decoded form of that value suitable for placing into an Embedded object.
lambda x: x
Normal usage is to supply a buffer, and keep calling next until a ShortPacket exception is raised:
>>> d = Decoder(b'\\xa0{\\xb1\\x05hello\\x85\\xb3\\x01x\\xb5\\x84')\n>>> d.next()\n123\n>>> d.next()\n'hello'\n>>> d.next()\n()\n>>> d.next()\nTraceback (most recent call last):\n ...\npreserves.error.ShortPacket: Short packet\n
Alternatively, keep calling try_next until it yields None
, which is not in the domain of Preserves Value
s:
>>> d = Decoder(b'\\xa0{\\xb1\\x05hello\\x85\\xb3\\x01x\\xb5\\x84')\n>>> d.try_next()\n123\n>>> d.try_next()\n'hello'\n>>> d.try_next()\n()\n>>> d.try_next()\n
For convenience, Decoder implements the iterator interface, backing it with try_next, so you can simply iterate over all complete values in an input:
>>> d = Decoder(b'\\xa0{\\xb1\\x05hello\\x85\\xb3\\x01x\\xb5\\x84')\n>>> list(d)\n[123, 'hello', ()]\n
>>> for v in Decoder(b'\\xa0{\\xb1\\x05hello\\x85\\xb3\\x01x\\xb5\\x84'):\n... print(repr(v))\n123\n'hello'\n()\n
Supply include_annotations=True
to read annotations alongside the annotated values:
>>> d = Decoder(b'\\xa0{\\xb1\\x05hello\\x85\\xb3\\x01x\\xb5\\x84', include_annotations=True)\n>>> list(d)\n[123, 'hello', @#x ()]\n
If you are incrementally reading from, say, a socket, you can use extend to add new input as if comes available:
>>> d = Decoder(b'\\xa0{\\xb1\\x05he')\n>>> d.try_next()\n123\n>>> d.try_next() # returns None because the input is incomplete\n>>> d.extend(b'llo')\n>>> d.try_next()\n'hello'\n>>> d.try_next()\n
Attributes:
Name Type Description packet
bytes
buffered input waiting to be processed
index
int
read position within packet
Source code in preserves/binary.py
def __init__(self, packet=b'', include_annotations=False, decode_embedded=lambda x: x):\n super(Decoder, self).__init__()\n self.packet = packet\n self.index = 0\n self.include_annotations = include_annotations\n self.decode_embedded = decode_embedded\n
"},{"location":"binary/#preserves.binary.Decoder.extend","title":"extend(data)
","text":"Appends data
to the remaining bytes in self.packet
, trimming already-processed bytes from the front of self.packet
and resetting self.index
to zero.
Source code in preserves/binary.py
def extend(self, data):\n\"\"\"Appends `data` to the remaining bytes in `self.packet`, trimming already-processed\n bytes from the front of `self.packet` and resetting `self.index` to zero.\"\"\"\n self.packet = self.packet[self.index:] + data\n self.index = 0\n
"},{"location":"binary/#preserves.binary.Decoder.next","title":"next()
","text":"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/binary.py
def next(self):\n\"\"\"Reads the next complete `Value` from the internal buffer, raising\n [ShortPacket][preserves.error.ShortPacket] if too few bytes are available, or\n [DecodeError][preserves.error.DecodeError] if the input is invalid somehow.\n\n \"\"\"\n tag = self.nextbyte()\n if tag == 0x80: return self.wrap(False)\n if tag == 0x81: return self.wrap(True)\n if tag == 0x82: return self.wrap(Float.from_bytes(self.nextbytes(4)))\n if tag == 0x83: return self.wrap(struct.unpack('>d', self.nextbytes(8))[0])\n if tag == 0x84: raise DecodeError('Unexpected end-of-stream marker')\n if tag == 0x85:\n a = self.next()\n v = self.next()\n return self.unshift_annotation(a, v)\n if tag == 0x86:\n if self.decode_embedded is None:\n raise DecodeError('No decode_embedded function supplied')\n return self.wrap(Embedded(self.decode_embedded(self.next())))\n if tag >= 0x90 and tag <= 0x9f: return self.wrap(tag - (0xa0 if tag > 0x9c else 0x90))\n if tag >= 0xa0 and tag <= 0xaf: return self.wrap(self.nextint(tag - 0xa0 + 1))\n if tag == 0xb0: return self.wrap(self.nextint(self.varint()))\n if tag == 0xb1: return self.wrap(self.nextbytes(self.varint()).decode('utf-8'))\n if tag == 0xb2: return self.wrap(self.nextbytes(self.varint()))\n if tag == 0xb3: return self.wrap(Symbol(self.nextbytes(self.varint()).decode('utf-8')))\n if tag == 0xb4:\n vs = self.nextvalues()\n if not vs: raise DecodeError('Too few elements in encoded record')\n return self.wrap(Record(vs[0], vs[1:]))\n if tag == 0xb5: return self.wrap(tuple(self.nextvalues()))\n if tag == 0xb6: return self.wrap(frozenset(self.nextvalues()))\n if tag == 0xb7: return self.wrap(ImmutableDict.from_kvs(self.nextvalues()))\n raise DecodeError('Invalid tag: ' + hex(tag))\n
"},{"location":"binary/#preserves.binary.Decoder.try_next","title":"try_next()
","text":"Like next, but returns None
instead of raising ShortPacket.
Source code in preserves/binary.py
def try_next(self):\n\"\"\"Like [next][preserves.binary.Decoder.next], but returns `None` instead of raising\n [ShortPacket][preserves.error.ShortPacket].\"\"\"\n start = self.index\n try:\n return self.next()\n except ShortPacket:\n self.index = start\n return None\n
"},{"location":"binary/#preserves.binary.Encoder","title":"Encoder(encode_embedded=lambda x: x, canonicalize=False, include_annotations=None)
","text":" Bases: BinaryCodec
Implementation of an encoder for the machine-oriented binary Preserves syntax.
>>> e = Encoder()\n>>> e.append(123)\n>>> e.append('hello')\n>>> e.append(annotate([], Symbol('x')))\n>>> e.contents()\nb'\\xa0{\\xb1\\x05hello\\x85\\xb3\\x01x\\xb5\\x84'\n
Parameters:
Name Type Description Default encode_embedded
function accepting an Embedded.embeddedValue and returning a Value
for serialization.
lambda x: x
canonicalize
bool
if True
, ensures the serialized data are in canonical form. This is slightly more work than producing potentially-non-canonical output.
False
include_annotations
bool | None
if None
, includes annotations in the output only when canonicalize
is False
, because canonical serialization of values demands omission of annotations. If explicitly True
or False
, however, annotations will be included resp. excluded no matter the canonicalize
setting. This can be used to get canonical ordering (canonicalize=True
) and annotations (include_annotations=True
).
None
Attributes:
Name Type Description buffer
bytearray
accumulator for the output of the encoder
Source code in preserves/binary.py
def __init__(self,\n encode_embedded=lambda x: x,\n canonicalize=False,\n include_annotations=None):\n super(Encoder, self).__init__()\n self.buffer = bytearray()\n self._encode_embedded = encode_embedded\n self._canonicalize = canonicalize\n if include_annotations is None:\n self.include_annotations = not self._canonicalize\n else:\n self.include_annotations = include_annotations\n
"},{"location":"binary/#preserves.binary.Encoder.append","title":"append(v)
","text":"Extend self.buffer
with an encoding of v
.
Source code in preserves/binary.py
def append(self, v):\n\"\"\"Extend `self.buffer` with an encoding of `v`.\"\"\"\n v = preserve(v)\n if hasattr(v, '__preserve_write_binary__'):\n v.__preserve_write_binary__(self)\n elif v is False:\n self.buffer.append(0x80)\n elif v is True:\n self.buffer.append(0x81)\n elif isinstance(v, float):\n self.buffer.append(0x83)\n self.buffer.extend(struct.pack('>d', v))\n elif isinstance(v, numbers.Number):\n if v >= -3 and v <= 12:\n self.buffer.append(0x90 + (v if v >= 0 else v + 16))\n else:\n self.encodeint(v)\n elif isinstance(v, bytes):\n self.encodebytes(2, v)\n elif isinstance(v, basestring_):\n self.encodebytes(1, v.encode('utf-8'))\n elif isinstance(v, list):\n self.encodevalues(5, v)\n elif isinstance(v, tuple):\n self.encodevalues(5, v)\n elif isinstance(v, set):\n self.encodeset(v)\n elif isinstance(v, frozenset):\n self.encodeset(v)\n elif isinstance(v, dict):\n self.encodedict(v)\n else:\n try:\n i = iter(v)\n except TypeError:\n i = None\n if i is None:\n self.cannot_encode(v)\n else:\n self.encodevalues(5, i)\n
"},{"location":"binary/#preserves.binary.Encoder.contents","title":"contents()
","text":"Returns a bytes
constructed from the contents of self.buffer
.
Source code in preserves/binary.py
def contents(self):\n\"\"\"Returns a `bytes` constructed from the contents of `self.buffer`.\"\"\"\n return bytes(self.buffer)\n
"},{"location":"binary/#preserves.binary.Encoder.reset","title":"reset()
","text":"Clears self.buffer
to a fresh empty bytearray
.
Source code in preserves/binary.py
def reset(self):\n\"\"\"Clears `self.buffer` to a fresh empty `bytearray`.\"\"\"\n self.buffer = bytearray()\n
"},{"location":"binary/#preserves.binary.canonicalize","title":"canonicalize(v, **kwargs)
","text":"As encode, but sets canonicalize=True
in the Encoder constructor.
Source code in preserves/binary.py
def canonicalize(v, **kwargs):\n\"\"\"As [encode][preserves.binary.encode], but sets `canonicalize=True` in the\n [Encoder][preserves.binary.Encoder] constructor.\n\n \"\"\"\n return encode(v, canonicalize=True, **kwargs)\n
"},{"location":"binary/#preserves.binary.decode","title":"decode(bs, **kwargs)
","text":"Yields the first complete encoded value from bs
, passing kwargs
through to the Decoder constructor. Raises exceptions as per next.
Parameters:
Name Type Description Default bs
bytes
encoded data to decode
required Source code in preserves/binary.py
def decode(bs, **kwargs):\n\"\"\"Yields the first complete encoded value from `bs`, passing `kwargs` through to the\n [Decoder][preserves.binary.Decoder] constructor. Raises exceptions as per\n [next][preserves.binary.Decoder.next].\n\n Args:\n bs (bytes): encoded data to decode\n\n \"\"\"\n return Decoder(packet=bs, **kwargs).next()\n
"},{"location":"binary/#preserves.binary.decode_with_annotations","title":"decode_with_annotations(bs, **kwargs)
","text":"Like decode, but supplying include_annotations=True
to the Decoder constructor.
Source code in preserves/binary.py
def decode_with_annotations(bs, **kwargs):\n\"\"\"Like [decode][preserves.binary.decode], but supplying `include_annotations=True` to the\n [Decoder][preserves.binary.Decoder] constructor.\"\"\"\n return Decoder(packet=bs, include_annotations=True, **kwargs).next()\n
"},{"location":"binary/#preserves.binary.encode","title":"encode(v, **kwargs)
","text":"Encode a single Value
v
to a byte string. Any supplied kwargs
are passed on to the underlying Encoder constructor.
Source code in preserves/binary.py
def encode(v, **kwargs):\n\"\"\"Encode a single `Value` `v` to a byte string. Any supplied `kwargs` are passed on to the\n underlying [Encoder][preserves.binary.Encoder] constructor.\"\"\"\n e = Encoder(**kwargs)\n e.append(v)\n return e.contents()\n
"},{"location":"compare/","title":"Comparing Values","text":"Preserves specifies a total ordering and an equivalence between terms. The preserves.compare module implements the ordering and equivalence relations.
>>> cmp(\"bzz\", \"c\")\n-1\n>>> cmp(True, [])\n-1\n>>> lt(\"bzz\", \"c\")\nTrue\n>>> eq(\"bzz\", \"c\")\nFalse\n
Note that the ordering relates more values than Python's built-in ordering:
>>> [1, 2, 2] < [1, 2, \"3\"]\nTraceback (most recent call last):\n ..\nTypeError: '<' not supported between instances of 'int' and 'str'\n\n>>> lt([1, 2, 2], [1, 2, \"3\"])\nTrue\n
"},{"location":"compare/#preserves.compare.cmp","title":"cmp(a, b)
","text":"Returns -1
if a
< b
, or 0
if a
= b
, or 1
if a
> b
according to the Preserves total order.
Source code in preserves/compare.py
def cmp(a, b):\n\"\"\"Returns `-1` if `a` < `b`, or `0` if `a` = `b`, or `1` if `a` > `b` according to the\n [Preserves total order](https://preserves.dev/preserves.html#total-order).\"\"\"\n return _cmp(preserve(a), preserve(b))\n
"},{"location":"compare/#preserves.compare.eq","title":"eq(a, b)
","text":"Returns True
iff a
= b
according to the Preserves equivalence relation.
Source code in preserves/compare.py
def eq(a, b):\n\"\"\"Returns `True` iff `a` = `b` according to the [Preserves equivalence\n relation](https://preserves.dev/preserves.html#equivalence).\"\"\"\n return _eq(preserve(a), preserve(b))\n
"},{"location":"compare/#preserves.compare.le","title":"le(a, b)
","text":"Returns True
iff a
\u2264 b
according to the Preserves total order.
Source code in preserves/compare.py
def le(a, b):\n\"\"\"Returns `True` iff `a` \u2264 `b` according to the [Preserves total\n order](https://preserves.dev/preserves.html#total-order).\"\"\"\n return cmp(a, b) <= 0\n
"},{"location":"compare/#preserves.compare.lt","title":"lt(a, b)
","text":"Returns True
iff a
< b
according to the Preserves total order.
Source code in preserves/compare.py
def lt(a, b):\n\"\"\"Returns `True` iff `a` < `b` according to the [Preserves total\n order](https://preserves.dev/preserves.html#total-order).\"\"\"\n return cmp(a, b) < 0\n
"},{"location":"compare/#preserves.compare.sorted","title":"sorted(iterable, *, key=lambda x: x, reverse=False)
","text":"Returns a sorted list built from iterable
, extracting a sort key using key
, and ordering according to the Preserves total order. Directly analogous to the built-in Python sorted
routine, except uses the Preserves order instead of Python's less-than relation.
Source code in preserves/compare.py
def sorted(iterable, *, key=lambda x: x, reverse=False):\n\"\"\"Returns a sorted list built from `iterable`, extracting a sort key using `key`, and\n ordering according to the [Preserves total\n order](https://preserves.dev/preserves.html#total-order). Directly analogous to the\n [built-in Python `sorted`\n routine](https://docs.python.org/3/library/functions.html#sorted), except uses the\n Preserves order instead of Python's less-than relation.\n\n \"\"\"\n return _sorted(iterable, key=lambda x: _key(key(x)), reverse=reverse)\n
"},{"location":"compare/#preserves.compare.sorted_items","title":"sorted_items(d)
","text":"Given a dictionary d
, yields a list of (key, value)
tuples sorted by key
.
Source code in preserves/compare.py
def sorted_items(d):\n\"\"\"Given a dictionary `d`, yields a list of `(key, value)` tuples sorted by `key`.\"\"\"\n return sorted(d.items(), key=_item_key)\n
"},{"location":"error/","title":"Codec errors","text":"The preserves.error module exports various Error
subclasses.
"},{"location":"error/#preserves.error.DecodeError","title":"DecodeError
","text":" Bases: ValueError
Raised whenever preserves.binary.Decoder or preserves.text.Parser detect invalid input.
"},{"location":"error/#preserves.error.EncodeError","title":"EncodeError
","text":" Bases: ValueError
Raised whenever preserves.binary.Encoder or preserves.text.Formatter are unable to proceed.
"},{"location":"error/#preserves.error.ShortPacket","title":"ShortPacket
","text":" 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.
"},{"location":"fold/","title":"Traversing values","text":"The preserves.fold module exports various utilities for traversing compound Value
s.
"},{"location":"fold/#preserves.fold.map_embeddeds","title":"map_embeddeds(f, v)
","text":"Returns an equivalent copy of v
, except where each contained Embedded value is replaced by f
applied to the Embedded's embeddedValue
attribute.
>>> map_embeddeds(lambda w: Embedded(f'w={w}'), ['a', Embedded(123), {'z': 6.0}])\n('a', #!'w=123', {'z': 6.0})\n
Source code in preserves/fold.py
def map_embeddeds(f, v):\n\"\"\"Returns an [equivalent][preserves.compare.eq] copy of `v`, except where each contained\n [Embedded][preserves.values.Embedded] value is replaced by `f` applied to the Embedded's\n `embeddedValue` attribute.\n\n ```python\n >>> map_embeddeds(lambda w: Embedded(f'w={w}'), ['a', Embedded(123), {'z': 6.0}])\n ('a', #!'w=123', {'z': 6.0})\n\n ```\n \"\"\"\n def walk(v):\n if isinstance(v, Embedded):\n return f(v.embeddedValue)\n elif isinstance(v, (list, tuple)):\n return tuple(walk(w) for w in v)\n elif isinstance(v, (set, frozenset)):\n return frozenset(walk(w) for w in v)\n elif isinstance(v, dict):\n return ImmutableDict.from_kvs(walk(w) for w in dict_kvs(v))\n elif isinstance(v, Record):\n return Record(walk(v.key), walk(v.fields))\n else:\n return v\n return walk(v)\n
"},{"location":"merge/","title":"Merging values","text":"The preserves.merge module exports various utilities for merging Value
s.
"},{"location":"merge/#preserves.merge.merge","title":"merge(v0, *vs, merge_embedded=None)
","text":"Repeatedly merges v0
with each element in vs
using merge2, returning the final result. The merge_embedded
parameter is passed on to merge2.
Source code in preserves/merge.py
def merge(v0, *vs, merge_embedded=None):\n\"\"\"Repeatedly merges `v0` with each element in `vs` using [merge2][preserves.merge.merge2],\n returning the final result. The `merge_embedded` parameter is passed on to merge2.\"\"\"\n v = v0\n for vN in vs:\n v = merge2(v, vN, merge_embedded=merge_embedded)\n return v\n
"},{"location":"merge/#preserves.merge.merge2","title":"merge2(a, b, merge_embedded=None)
","text":"Merges a
and b
, returning the result. Raises ValueError
if, during the merge, a pair of incompatible values is discovered.
If a
and b
are Embedded objects, their embeddedValue
s are merged using merge_embedded
, and the result is again wrapped in an Embedded object.
>>> merge2(123, 234)\nTraceback (most recent call last):\n ...\nValueError: Cannot merge items\n>>> merge2(123, 123)\n123\n>>> merge2('hi', 0)\nTraceback (most recent call last):\n ...\nValueError: Cannot merge items\n>>> merge2([1, 2], [1, 2])\n[1, 2]\n>>> merge2([1, 2], [1, 3])\nTraceback (most recent call last):\n ...\nValueError: Cannot merge items\n>>> merge2({'a': 1, 'b': 2}, {'a': 1, 'c': 3})\n{'a': 1, 'b': 2, 'c': 3}\n>>> merge2({'a': 1, 'b': 2}, {'a': 10, 'c': 3})\nTraceback (most recent call last):\n ...\nValueError: Cannot merge items\n>>> merge2(Record('a', [1, {'x': 2}]), Record('a', [1, {'y': 3}]))\na(1, {'x': 2, 'y': 3})\n
Source code in preserves/merge.py
def merge2(a, b, merge_embedded=None):\n\"\"\"Merges `a` and `b`, returning the result. Raises `ValueError` if, during the merge, a\n pair of incompatible values is discovered.\n\n If `a` and `b` are [Embedded][preserves.values.Embedded] objects, their `embeddedValue`s\n are merged using `merge_embedded`, and the result is again wrapped in an\n [Embedded][preserves.values.Embedded] object.\n\n ```python\n >>> merge2(123, 234)\n Traceback (most recent call last):\n ...\n ValueError: Cannot merge items\n >>> merge2(123, 123)\n 123\n >>> merge2('hi', 0)\n Traceback (most recent call last):\n ...\n ValueError: Cannot merge items\n >>> merge2([1, 2], [1, 2])\n [1, 2]\n >>> merge2([1, 2], [1, 3])\n Traceback (most recent call last):\n ...\n ValueError: Cannot merge items\n >>> merge2({'a': 1, 'b': 2}, {'a': 1, 'c': 3})\n {'a': 1, 'b': 2, 'c': 3}\n >>> merge2({'a': 1, 'b': 2}, {'a': 10, 'c': 3})\n Traceback (most recent call last):\n ...\n ValueError: Cannot merge items\n >>> merge2(Record('a', [1, {'x': 2}]), Record('a', [1, {'y': 3}]))\n a(1, {'x': 2, 'y': 3})\n\n ```\n\n \"\"\"\n if a == b:\n return a\n if isinstance(a, (list, tuple)) and isinstance(b, (list, tuple)):\n return merge_seq(a, b)\n if isinstance(a, (set, frozenset)) and isinstance(b, (set, frozenset)):\n _die()\n if isinstance(a, dict) and isinstance(b, dict):\n r = {}\n for (ak, av) in a.items():\n bv = b.get(ak, None)\n r[ak] = av if bv is None else merge2(av, bv, merge_embedded=merge_embedded)\n for (bk, bv) in b.items():\n if bk not in r:\n r[bk] = bv\n return r\n if isinstance(a, Record) and isinstance(b, Record):\n return Record(merge2(a.key, b.key, merge_embedded=merge_embedded),\n merge_seq(a.fields, b.fields, merge_embedded=merge_embedded))\n if isinstance(a, Embedded) and isinstance(b, Embedded):\n m = (merge_embedded or merge_embedded_id)(a.embeddedValue, b.embeddedValue)\n if m is None: _die()\n return Embedded(m)\n _die()\n
"},{"location":"path/","title":"Preserves Path","text":"The preserves.path module implements Preserves Path.
Preserves Path is roughly analogous to XPath, but for Preserves values: just as XPath selects portions of an XML document, a Preserves Path uses path expressions to select portions of a Value
.
Use parse to compile a path expression, and then use the exec method on the result to apply it to a given input:
parse(PATH_EXPRESSION_STRING).exec(PRESERVES_VALUE)\n -> SEQUENCE_OF_PRESERVES_VALUES\n
"},{"location":"path/#preserves.path--command-line-usage","title":"Command-line usage","text":"When preserves.path is run as a __main__
module, sys.argv[1]
is parsed, interpreted as a path expression, and run against human-readable values read from standard input. Each matching result is passed to stringify and printed to standard output.
"},{"location":"path/#preserves.path--examples","title":"Examples","text":""},{"location":"path/#preserves.path--setup-loading-test-data","title":"Setup: Loading test data","text":"The following examples use testdata
:
>>> with open('tests/samples.bin', 'rb') as f:\n... testdata = decode_with_annotations(f.read())\n
Recall that samples.bin
contains a binary-syntax form of the human-readable [samples.pr](https://preserves.dev/tests/samples.pr) test data file, intended to exercise most of the features of Preserves. In particular, the root
Value` in the file has a number of annotations (for documentation and other purposes).
"},{"location":"path/#preserves.path--example-1-selecting-string-valued-documentation-annotations","title":"Example 1: Selecting string-valued documentation annotations","text":"The path expression .annotations ^ Documentation . 0 / string
proceeds in five steps:
.annotations
selects each annotation on the root document ^ Documentation
retains only those values (each an annotation of the root) that are Record
s with label equal to the symbol Documentation
. 0
moves into the first child (the first field) of each such Record
, which in our case is a list of other Value
s /
selects all immediate children of these lists string
retains only those values that are strings The result of evaluating it on testdata
is as follows:
>>> selector = parse('.annotations ^ Documentation . 0 / string')\n>>> for result in selector.exec(testdata):\n... print(stringify(result))\n\"Individual test cases may be any of the following record types:\"\n\"In each test, let value = strip(annotatedValue),\"\n\" forward = value,\"\n\" back = value,\"\n\"except where test-case-specific values of `forward` and/or `back`\"\n\"are provided by the executing harness, and check the following\"\n\"numbered expectations according to the table above:\"\n\"Implementations may vary in their treatment of the difference between expectations\"\n\"13/14 and 16/17, depending on how they wish to treat end-of-stream conditions.\"\n
"},{"location":"path/#preserves.path--example-2-selecting-tests-with-records-as-their-annotatedvalues","title":"Example 2: Selecting tests with Records as their annotatedValues","text":"The path expression // [.^ [= Test + = NondeterministicTest]] [. 1 rec]
proceeds in three steps:
//
recursively decomposes the input, yielding all direct and indirect descendants of each input value
[.^ [= Test + = NondeterministicTest]]
retains only those inputs (each a descendant of the root) that yield more than zero results when executed against the expression within the brackets:
.^
selects only labels of values that are Records
, filtering by type and transforming in a single step [= Test + = NondeterministicTest]
again filters by a path expression: the infix +
operator takes the union of matches of its arguments the left-hand argument, = Test
selects values (remember, record labels) equal to the symbol Test
the right-hand argument = NondeterministicTest
selects values equal to NondeterministicTest
The result is thus all Record
s anywhere inside testdata
that have either Test
or NondeterministicTest
as their labels.
[. 1 rec]
filters these Record
s by another path expression:
. 1
selects their second field (fields are numbered from 0) rec
retains only values that are Record
s Evaluating the expression against testdata
yields the following:
>>> selector = parse('// [.^ [= Test + = NondeterministicTest]] [. 1 rec]')\n>>> for result in selector.exec(testdata):\n... print(stringify(result))\n<Test #[tLMHY2FwdHVyZbSzB2Rpc2NhcmSEhA==] <capture <discard>>>\n<Test #[tLMHb2JzZXJ2ZbSzBXNwZWFrtLMHZGlzY2FyZIS0swdjYXB0dXJltLMHZGlzY2FyZISEhIQ=] <observe <speak <discard> <capture <discard>>>>>\n<Test #[tLWzBnRpdGxlZLMGcGVyc29ukrMFdGhpbmeRhKBlsQlCbGFja3dlbGy0swRkYXRloQcdkpOEsQJEcoQ=] <[titled person 2 thing 1] 101 \"Blackwell\" <date 1821 2 3> \"Dr\">>\n<Test #[tLMHZGlzY2FyZIQ=] <discard>>\n<Test #[tJe1hIQ=] <7 []>>\n<Test #[tLMHZGlzY2FyZLMIc3VycHJpc2WE] <discard surprise>>\n<Test #[tLEHYVN0cmluZ5OUhA==] <\"aString\" 3 4>>\n<Test #[tLSzB2Rpc2NhcmSEk5SE] <<discard> 3 4>>\n<Test #[hbMCYXK0swFShbMCYWazAWaE] @ar <R @af f>>\n<Test #[tIWzAmFyswFShbMCYWazAWaE] <@ar R @af f>>\n
"},{"location":"path/#preserves.path.Predicate","title":"Predicate = syntax.Predicate
module-attribute
","text":"Schema definition for representing a Preserves Path Predicate
.
"},{"location":"path/#preserves.path.Selector","title":"Selector = syntax.Selector
module-attribute
","text":"Schema definition for representing a sequence of Preserves Path Step
s.
"},{"location":"path/#preserves.path.syntax","title":"syntax = load_schema_file(pathlib.Path(__file__).parent / 'path.prb').path
module-attribute
","text":"This value is a Python representation of a Preserves Schema definition for the Preserves Path expression language. The language is defined in the file path.prs.
"},{"location":"path/#preserves.path.exec","title":"exec(self, v)
","text":"WARNING: This is not a function: it is a method on Selector, Predicate, and so on.
>>> sel = parse('/ [.length gt 1]')\n>>> sel.exec(['', 'a', 'ab', 'abc', 'abcd', 'bcd', 'cd', 'd', ''])\n('ab', 'abc', 'abcd', 'bcd', 'cd')\n
Source code in preserves/path.py
@extend(syntax.Function)\ndef exec(self, v):\n\"\"\"WARNING: This is not a *function*: it is a *method* on\n [Selector][preserves.path.Selector], [Predicate][preserves.path.Predicate], and so on.\n\n ```python\n >>> sel = parse('/ [.length gt 1]')\n >>> sel.exec(['', 'a', 'ab', 'abc', 'abcd', 'bcd', 'cd', 'd', ''])\n ('ab', 'abc', 'abcd', 'bcd', 'cd')\n\n ```\n\n \"\"\"\n return (len(self.selector.exec(v)),)\n
"},{"location":"path/#preserves.path.parse","title":"parse(s)
","text":"Parse s
as a Preserves Path path expression, yielding a Selector object. Selectors (and Predicates etc.) have an exec method defined on them.
Raises ValueError
if s
is not a valid path expression.
Source code in preserves/path.py
def parse(s):\n\"\"\"Parse `s` as a Preserves Path path expression, yielding a\n [Selector][preserves.path.Selector] object. Selectors (and Predicates etc.) have an\n [exec][preserves.path.exec] method defined on them.\n\n Raises `ValueError` if `s` is not a valid path expression.\n\n \"\"\"\n return parse_selector(Parser(s))\n
"},{"location":"schema/","title":"Preserves Schema","text":"A Preserves schema connects Preserves Value
s to host-language data structures. Each definition within a schema can be processed by a compiler to produce
a simple host-language type definition;
a partial parsing function from Value
s to instances of the produced type; and
a total serialization function from instances of the type to Value
s.
Every parsed Value
retains enough information to always be able to be serialized again, and every instance of a host-language data structure contains, by construction, enough information to be successfully serialized.
"},{"location":"schema/#schema-support-in-python","title":"Schema support in Python","text":"The preserves.schema module implements Preserves Schema for Python.
A Schema source file (like this one) is first compiled using preserves-schemac
to produce a binary-syntax schema bundle containing schema module definitons (like this one). Python code then loads the bundle, exposing its contents as Namespaces ultimately containing SchemaObjects.
"},{"location":"schema/#preserves.schema--examples","title":"Examples","text":""},{"location":"schema/#preserves.schema--setup-loading-a-schema-bundle","title":"Setup: Loading a schema bundle","text":"For our running example, we will use schemas associated with the Syndicated Actor Model. (The schema bundle is a copy of this file from the syndicate-protocols
repository.)
To load a schema bundle, use load_schema_file (or, alternatively, use Compiler directly):
>>> bundle = load_schema_file('docs/syndicate-protocols-schema-bundle.bin')\n>>> type(bundle)\n<class 'preserves.schema.Namespace'>\n
The top-level entries in the loaded bundle are schema modules. Let's examine the stream
schema module, whose source code indicates that it should contain definitions for Mode
, Source
, Sink
, etc.:
>>> bundle.stream # doctest: +ELLIPSIS\n{'Mode': <class 'stream.Mode'>, 'Sink': <class 'stream.Sink'>, ...}\n
"},{"location":"schema/#preserves.schema--example-1-streamstreamlistenererror-a-product-type","title":"Example 1: stream.StreamListenerError, a product type","text":"Drilling down further, let's consider the definition of StreamListenerError, which appears in the source as
StreamListenerError = <stream-listener-error @spec any @message string> .\n
This reads, in the Preserves Schema language, as the definition of a simple product type (record, class, object) with two named fields spec
and message
. Parsing a value into a StreamListenerError
will only succeed if it's a record, if the label matches, the second field (message
) is a string, and it has exactly two fields.
>>> bundle.stream.StreamListenerError\n<class 'stream.StreamListenerError'>\n
The StreamListenerError
class includes a decode method that analyzes an input value:
>>> bundle.stream.StreamListenerError.decode(\n... parse('<stream-listener-error <xyz> \"an error\">'))\nStreamListenerError {'spec': #xyz(), 'message': 'an error'}\n
If invalid input is supplied, decode will raise SchemaDecodeFailed, which includes helpful information for diagnosing the problem (as we will see below, this is especially useful for parsers for sum types):
>>> bundle.stream.StreamListenerError.decode(\n... parse('<i-am-invalid>'))\nTraceback (most recent call last):\n ...\npreserves.schema.SchemaDecodeFailed: Could not decode i-am-invalid using <class 'stream.StreamListenerError'>\nMost likely reason: in stream.StreamListenerError: <lit stream-listener-error> didn't match i-am-invalid\nFull explanation: \n in stream.StreamListenerError: <lit stream-listener-error> didn't match i-am-invalid\n
Alternatively, the try_decode method catches SchemaDecodeFailed, transforming it into None
:
>>> bundle.stream.StreamListenerError.try_decode(\n... parse('<stream-listener-error <xyz> \"an error\">'))\nStreamListenerError {'spec': #xyz(), 'message': 'an error'}\n>>> bundle.stream.StreamListenerError.try_decode(\n... parse('<i-am-invalid>'))\n
The class can also be instantiated directly:
>>> err = bundle.stream.StreamListenerError(Record(Symbol('xyz'), []), 'an error')\n>>> err\nStreamListenerError {'spec': #xyz(), 'message': 'an error'}\n
The fields and contents of instances can be queried:
>>> err.spec\n#xyz()\n>>> err.message\n'an error'\n
And finally, instances can of course be serialized and encoded:
>>> print(stringify(err))\n<stream-listener-error <xyz> \"an error\">\n>>> canonicalize(err)\nb'\\xb4\\xb3\\x15stream-listener-error\\xb4\\xb3\\x03xyz\\x84\\xb1\\x08an error\\x84'\n
"},{"location":"schema/#preserves.schema--example-2-streammode-a-sum-type","title":"Example 2: stream.Mode, a sum type","text":"Now let's consider the definition of Mode, which appears in the source as
Mode = =bytes / @lines LineMode / <packet @size int> / <object @description any> .\n
This reads, in the Preserves Schema language, as an alternation (disjoint union, variant, sum type) of four possible kinds of value: the symbol bytes
; a LineMode
value; a record with packet
as its label and an integer as its only field; or a record with object
as its label and any kind of value as its only field. In Python, this becomes:
>>> bundle.stream.Mode.bytes\n<class 'stream.Mode.bytes'>\n>>> bundle.stream.Mode.lines\n<class 'stream.Mode.lines'>\n>>> bundle.stream.Mode.packet\n<class 'stream.Mode.packet'>\n>>> bundle.stream.Mode.object\n<class 'stream.Mode.object'>\n
As before, Mode
includes a decode method that analyzes an input value:
>>> bundle.stream.Mode.decode(parse('bytes'))\nMode.bytes()\n>>> bundle.stream.Mode.decode(parse('lf'))\nMode.lines(LineMode.lf())\n>>> bundle.stream.Mode.decode(parse('<packet 123>'))\nMode.packet {'size': 123}\n>>> bundle.stream.Mode.decode(parse('<object \"?\">'))\nMode.object {'description': '?'}\n
Invalid input causes SchemaDecodeFailed to be raised:
>>> bundle.stream.Mode.decode(parse('<i-am-not-a-valid-mode>'))\nTraceback (most recent call last):\n ...\npreserves.schema.SchemaDecodeFailed: Could not decode <i-am-not-a-valid-mode> using <class 'stream.Mode'>\nMost likely reason: in stream.LineMode.crlf: <lit crlf> didn't match <i-am-not-a-valid-mode>\nFull explanation: \n in stream.Mode: matching <i-am-not-a-valid-mode>\n in stream.Mode.bytes: <lit bytes> didn't match <i-am-not-a-valid-mode>\n in stream.Mode.lines: <ref [] LineMode> didn't match <i-am-not-a-valid-mode>\n in stream.LineMode: matching <i-am-not-a-valid-mode>\n in stream.LineMode.lf: <lit lf> didn't match <i-am-not-a-valid-mode>\n in stream.LineMode.crlf: <lit crlf> didn't match <i-am-not-a-valid-mode>\n in stream.Mode.packet: <lit packet> didn't match i-am-not-a-valid-mode\n in stream.Mode.object: <lit object> didn't match i-am-not-a-valid-mode\n
The \"full explanation\" includes details on which parses were attempted, and why they failed.
Again, the try_decode method catches SchemaDecodeFailed, transforming it into None
:
>>> bundle.stream.Mode.try_decode(parse('bytes'))\nMode.bytes()\n>>> bundle.stream.Mode.try_decode(parse('<i-am-not-a-valid-mode>'))\n
Direct instantiation is done with the variant classes, not with Mode
itself:
>>> bundle.stream.Mode.bytes()\nMode.bytes()\n>>> bundle.stream.Mode.lines(bundle.stream.LineMode.lf())\nMode.lines(LineMode.lf())\n>>> bundle.stream.Mode.packet(123)\nMode.packet {'size': 123}\n>>> bundle.stream.Mode.object('?')\nMode.object {'description': '?'}\n
Fields and contents can be queried as usual:
>>> bundle.stream.Mode.lines(bundle.stream.LineMode.lf()).value\nLineMode.lf()\n>>> bundle.stream.Mode.packet(123).size\n123\n>>> bundle.stream.Mode.object('?').description\n'?'\n
And serialization and encoding are also as expected:
>>> print(stringify(bundle.stream.Mode.bytes()))\nbytes\n>>> print(stringify(bundle.stream.Mode.lines(bundle.stream.LineMode.lf())))\nlf\n>>> print(stringify(bundle.stream.Mode.packet(123)))\n<packet 123>\n>>> print(stringify(bundle.stream.Mode.object('?')))\n<object \"?\">\n>>> canonicalize(bundle.stream.Mode.object('?'))\nb'\\xb4\\xb3\\x06object\\xb1\\x01?\\x84'\n
Finally, the VARIANT attribute of instances allows code to dispatch on what kind of data it is handling at a given moment:
>>> bundle.stream.Mode.bytes().VARIANT\n#bytes\n>>> bundle.stream.Mode.lines(bundle.stream.LineMode.lf()).VARIANT\n#lines\n>>> bundle.stream.Mode.packet(123).VARIANT\n#packet\n>>> bundle.stream.Mode.object('?').VARIANT\n#object\n
"},{"location":"schema/#preserves.schema.meta","title":"meta = load_schema_file(__metaschema_filename).schema
module-attribute
","text":"Schema module Namespace corresponding to Preserves Schema's metaschema.
"},{"location":"schema/#preserves.schema.Compiler","title":"Compiler()
","text":"Instances of Compiler populate an initially-empty Namespace by loading and compiling schema bundle files.
>>> c = Compiler()\n>>> c.load('docs/syndicate-protocols-schema-bundle.bin')\n>>> type(c.root)\n<class 'preserves.schema.Namespace'>\n
Attributes:
Name Type Description root
Namespace
the root namespace into which top-level schema modules are installed.
Source code in preserves/schema.py
def __init__(self):\n self.root = Namespace(())\n
"},{"location":"schema/#preserves.schema.Compiler.load","title":"load(filename)
","text":"Opens the file at filename
, passing the resulting file object to load_filelike.
Source code in preserves/schema.py
def load(self, filename):\n\"\"\"Opens the file at `filename`, passing the resulting file object to\n [load_filelike][preserves.schema.Compiler.load_filelike].\"\"\"\n filename = pathlib.Path(filename)\n with open(filename, 'rb') as f:\n self.load_filelike(f, filename.stem)\n
"},{"location":"schema/#preserves.schema.Compiler.load_filelike","title":"load_filelike(f, module_name=None)
","text":"Reads a meta.Bundle
or meta.Schema
from the filelike object f
, compiling and installing it in self.root
. If f
contains a bundle, module_name
is not used, since the schema modules in the bundle know their own names; if f
contains a plain schema module, however, module_name
is used directly if it is a string, and if it is None
, a suitable module name is computed from the name
attribute of f
, if it is present. If name
is absent in that case, ValueError
is raised.
Source code in preserves/schema.py
def load_filelike(self, f, module_name=None):\n\"\"\"Reads a `meta.Bundle` or `meta.Schema` from the filelike object `f`, compiling and\n installing it in `self.root`. If `f` contains a bundle, `module_name` is not used,\n since the schema modules in the bundle know their own names; if `f` contains a plain\n schema module, however, `module_name` is used directly if it is a string, and if it is\n `None`, a suitable module name is computed from the `name` attribute of `f`, if it is\n present. If `name` is absent in that case, `ValueError` is raised.\n\n \"\"\"\n x = Decoder(f.read()).next()\n if x.key == SCHEMA:\n if module_name is None:\n if hasattr(f, 'name'):\n module_name = pathlib.Path(f.name).stem\n else:\n raise ValueError('Cannot load schema module from filelike object without a module_name')\n self.load_schema((Symbol(module_name),), x)\n elif x.key == BUNDLE:\n for (p, s) in x[0].items():\n self.load_schema(p, s)\n
"},{"location":"schema/#preserves.schema.Definition","title":"Definition(*args, **kwargs)
","text":" Bases: SchemaObject
Subclasses of Definition are used to represent both standalone non-alternation definitions as well as alternatives within an Enumeration.
>>> bundle = load_schema_file('docs/syndicate-protocols-schema-bundle.bin')\n\n>>> bundle.stream.StreamListenerError.FIELD_NAMES\n['spec', 'message']\n>>> bundle.stream.StreamListenerError.SAFE_FIELD_NAMES\n['spec', 'message']\n>>> bundle.stream.StreamListenerError.ENUMERATION is None\nTrue\n\n>>> bundle.stream.Mode.object.FIELD_NAMES\n['description']\n>>> bundle.stream.Mode.object.SAFE_FIELD_NAMES\n['description']\n>>> bundle.stream.Mode.object.ENUMERATION is bundle.stream.Mode\nTrue\n\n>>> bundle.stream.CreditAmount.count.FIELD_NAMES\n[]\n>>> bundle.stream.CreditAmount.count.SAFE_FIELD_NAMES\n[]\n>>> bundle.stream.CreditAmount.count.ENUMERATION is bundle.stream.CreditAmount\nTrue\n\n>>> bundle.stream.CreditAmount.decode(parse('123'))\nCreditAmount.count(123)\n>>> bundle.stream.CreditAmount.count(123)\nCreditAmount.count(123)\n>>> bundle.stream.CreditAmount.count(123).value\n123\n
Source code in preserves/schema.py
def __init__(self, *args, **kwargs):\n self._fields = args\n if self.SIMPLE:\n if self.EMPTY:\n if len(args) != 0:\n raise TypeError('%s takes no arguments' % (self._constructor_name(),))\n else:\n if len(args) != 1:\n raise TypeError('%s needs exactly one argument' % (self._constructor_name(),))\n self.value = args[0]\n else:\n i = 0\n for arg in args:\n if i >= len(self.FIELD_NAMES):\n raise TypeError('%s given too many positional arguments' % (self._constructor_name(),))\n setattr(self, self.SAFE_FIELD_NAMES[i], arg)\n i = i + 1\n for (argname, arg) in kwargs.items():\n if hasattr(self, argname):\n raise TypeError('%s given duplicate attribute: %r' % (self._constructor_name, argname))\n if argname not in self.SAFE_FIELD_NAMES:\n raise TypeError('%s given unknown attribute: %r' % (self._constructor_name, argname))\n setattr(self, argname, arg)\n i = i + 1\n if i != len(self.FIELD_NAMES):\n raise TypeError('%s needs argument(s) %r' % (self._constructor_name(), self.FIELD_NAMES))\n
"},{"location":"schema/#preserves.schema.Definition.ENUMERATION","title":"ENUMERATION = None
class-attribute
","text":"None
for standalone top-level definitions with a module; otherwise, an Enumeration subclass representing a top-level alternation definition.
"},{"location":"schema/#preserves.schema.Definition.FIELD_NAMES","title":"FIELD_NAMES = []
class-attribute
","text":"List of strings: names of the fields contained within this definition, if it has named fields at all; otherwise, an empty list, and the definition is a simple wrapper for another value, in which case that value is accessed via the value
attribute.
"},{"location":"schema/#preserves.schema.Definition.SAFE_FIELD_NAMES","title":"SAFE_FIELD_NAMES = []
class-attribute
","text":"The list produced by mapping safeattrname over FIELD_NAMES.
"},{"location":"schema/#preserves.schema.Enumeration","title":"Enumeration()
","text":" Bases: SchemaObject
Subclasses of Enumeration represent a group of variant options within a sum type.
>>> bundle = load_schema_file('docs/syndicate-protocols-schema-bundle.bin')\n\n>>> import pprint\n>>> pprint.pprint(bundle.stream.Mode.VARIANTS)\n[(#bytes, <class 'stream.Mode.bytes'>),\n (#lines, <class 'stream.Mode.lines'>),\n (#packet, <class 'stream.Mode.packet'>),\n (#object, <class 'stream.Mode.object'>)]\n\n>>> bundle.stream.Mode.VARIANTS[0][1] is bundle.stream.Mode.bytes\nTrue\n
Source code in preserves/schema.py
def __init__(self):\n raise TypeError('Cannot create instance of Enumeration')\n
"},{"location":"schema/#preserves.schema.Enumeration.VARIANTS","title":"VARIANTS = None
class-attribute
","text":"List of (Symbol, SchemaObject class)
tuples representing the possible options within this sum type.
"},{"location":"schema/#preserves.schema.Namespace","title":"Namespace(prefix)
","text":"A Namespace is a dictionary-like object representing a schema module that knows its location in a schema module hierarchy and whose attributes correspond to definitions and submodules within the schema module.
Attributes:
Name Type Description _prefix
tuple[Symbol]
path to this module/Namespace from the root Namespace
Source code in preserves/schema.py
def __init__(self, prefix):\n self._prefix = prefix\n
"},{"location":"schema/#preserves.schema.SchemaDecodeFailed","title":"SchemaDecodeFailed(cls, p, v, failures=None)
","text":" Bases: ValueError
Raised when decode cannot find a way to parse a given input.
Attributes:
Name Type Description cls
class
the SchemaObject subclass attempting the parse
pattern
Value
the failing pattern, a Value
conforming to schema meta.Pattern
value
Value
the unparseable value
failures
list[SchemaDecodeFailed]
descriptions of failed paths attempted during the match this failure describes
Source code in preserves/schema.py
def __init__(self, cls, p, v, failures=None):\n super().__init__()\n self.cls = cls\n self.pattern = p\n self.value = v\n self.failures = [] if failures is None else failures\n
"},{"location":"schema/#preserves.schema.SchemaObject","title":"SchemaObject
","text":"Base class for classes representing grammatical productions in a schema: instances of SchemaObject represent schema definitions. This is an abstract class, as are its subclasses Enumeration and Definition. It is subclasses of those subclasses, automatically produced during schema loading, that are actually instantiated.
>>> bundle = load_schema_file('docs/syndicate-protocols-schema-bundle.bin')\n\n>>> bundle.stream.Mode.mro()[1:-1]\n[<class 'preserves.schema.Enumeration'>, <class 'preserves.schema.SchemaObject'>]\n\n>>> bundle.stream.Mode.packet.mro()[1:-1]\n[<class 'stream.Mode._ALL'>, <class 'preserves.schema.Definition'>, <class 'preserves.schema.SchemaObject'>]\n\n>>> bundle.stream.StreamListenerError.mro()[1:-1]\n[<class 'preserves.schema.Definition'>, <class 'preserves.schema.SchemaObject'>]\n
Illustrating the class attributes on SchemaObject subclasses:
>>> bundle.stream.Mode.ROOTNS is bundle\nTrue\n\n>>> print(stringify(bundle.stream.Mode.SCHEMA, indent=2))\n<or [\n [\n \"bytes\"\n <lit bytes>\n ]\n [\n \"lines\"\n <ref [] LineMode>\n ]\n [\n \"packet\"\n <rec <lit packet> <tuple [<named size <atom SignedInteger>>]>>\n ]\n [\n \"object\"\n <rec <lit object> <tuple [<named description any>]>>\n ]\n]>\n\n>>> bundle.stream.Mode.MODULE_PATH\n(#stream,)\n\n>>> bundle.stream.Mode.NAME\n#Mode\n\n>>> bundle.stream.Mode.VARIANT is None\nTrue\n>>> bundle.stream.Mode.packet.VARIANT\n#packet\n
"},{"location":"schema/#preserves.schema.SchemaObject.MODULE_PATH","title":"MODULE_PATH = None
class-attribute
","text":"A sequence (tuple) of Symbols naming the path from the root to the schema module containing this definition.
"},{"location":"schema/#preserves.schema.SchemaObject.NAME","title":"NAME = None
class-attribute
","text":"A Symbol naming this definition within its module.
"},{"location":"schema/#preserves.schema.SchemaObject.ROOTNS","title":"ROOTNS = None
class-attribute
","text":"A Namespace that is the top-level environment for all bundles included in the Compiler run that produced this SchemaObject.
"},{"location":"schema/#preserves.schema.SchemaObject.SCHEMA","title":"SCHEMA = None
class-attribute
","text":"A Value
conforming to schema meta.Definition
(and thus often to meta.Pattern
etc.), interpreted by the SchemaObject machinery to drive parsing, unparsing and so forth.
"},{"location":"schema/#preserves.schema.SchemaObject.VARIANT","title":"VARIANT = None
class-attribute
","text":"None
for Definitions (such as bundle.stream.StreamListenerError
above) and for overall Enumerations (such as bundle.stream.Mode
), or a Symbol for variant definitions contained within an enumeration (such as bundle.stream.Mode.packet
).
"},{"location":"schema/#preserves.schema.SchemaObject.__preserve__","title":"__preserve__()
","text":"Called by preserves.values.preserve: unparses the information represented by this instance, using its schema definition, to produce a Preserves Value
.
Source code in preserves/schema.py
def __preserve__(self):\n\"\"\"Called by [preserves.values.preserve][]: *unparses* the information represented by\n this instance, using its schema definition, to produce a Preserves `Value`.\"\"\"\n raise NotImplementedError('Subclass responsibility')\n
"},{"location":"schema/#preserves.schema.SchemaObject.decode","title":"decode(v)
classmethod
","text":"Parses v
using the SCHEMA, returning a (sub)instance of SchemaObject or raising SchemaDecodeFailed.
Source code in preserves/schema.py
@classmethod\ndef decode(cls, v):\n\"\"\"Parses `v` using the [SCHEMA][preserves.schema.SchemaObject.SCHEMA], returning a\n (sub)instance of [SchemaObject][preserves.schema.SchemaObject] or raising\n [SchemaDecodeFailed][preserves.schema.SchemaDecodeFailed].\"\"\"\n raise NotImplementedError('Subclass responsibility')\n
"},{"location":"schema/#preserves.schema.SchemaObject.try_decode","title":"try_decode(v)
classmethod
","text":"Parses v
using the SCHEMA, returning a (sub)instance of SchemaObject or None
if parsing failed.
Source code in preserves/schema.py
@classmethod\ndef try_decode(cls, v):\n\"\"\"Parses `v` using the [SCHEMA][preserves.schema.SchemaObject.SCHEMA], returning a\n (sub)instance of [SchemaObject][preserves.schema.SchemaObject] or `None` if parsing\n failed.\"\"\"\n try:\n return cls.decode(v)\n except SchemaDecodeFailed:\n return None\n
"},{"location":"schema/#preserves.schema.extend","title":"extend(cls)
","text":"A decorator for function definitions. Useful for adding behaviour to the classes resulting from loading a schema module:
>>> bundle = load_schema_file('docs/syndicate-protocols-schema-bundle.bin')\n\n>>> @extend(bundle.stream.LineMode.lf)\n... def what_am_i(self):\n... return 'I am a LINEFEED linemode'\n\n>>> @extend(bundle.stream.LineMode.crlf)\n... def what_am_i(self):\n... return 'I am a CARRIAGE-RETURN-PLUS-LINEFEED linemode'\n\n>>> bundle.stream.LineMode.lf()\nLineMode.lf()\n>>> bundle.stream.LineMode.lf().what_am_i()\n'I am a LINEFEED linemode'\n\n>>> bundle.stream.LineMode.crlf()\nLineMode.crlf()\n>>> bundle.stream.LineMode.crlf().what_am_i()\n'I am a CARRIAGE-RETURN-PLUS-LINEFEED linemode'\n
Source code in preserves/schema.py
def extend(cls):\n\"\"\"A decorator for function definitions. Useful for adding *behaviour* to the classes\n resulting from loading a schema module:\n\n ```python\n >>> bundle = load_schema_file('docs/syndicate-protocols-schema-bundle.bin')\n\n >>> @extend(bundle.stream.LineMode.lf)\n ... def what_am_i(self):\n ... return 'I am a LINEFEED linemode'\n\n >>> @extend(bundle.stream.LineMode.crlf)\n ... def what_am_i(self):\n ... return 'I am a CARRIAGE-RETURN-PLUS-LINEFEED linemode'\n\n >>> bundle.stream.LineMode.lf()\n LineMode.lf()\n >>> bundle.stream.LineMode.lf().what_am_i()\n 'I am a LINEFEED linemode'\n\n >>> bundle.stream.LineMode.crlf()\n LineMode.crlf()\n >>> bundle.stream.LineMode.crlf().what_am_i()\n 'I am a CARRIAGE-RETURN-PLUS-LINEFEED linemode'\n\n ```\n\n \"\"\"\n @wraps(cls)\n def extender(f):\n setattr(cls, f.__name__, f)\n return f\n return extender\n
"},{"location":"schema/#preserves.schema.load_schema_file","title":"load_schema_file(filename)
","text":"Simple entry point to the compiler: creates a Compiler, calls load on it, and returns its root
Namespace.
>>> bundle = load_schema_file('docs/syndicate-protocols-schema-bundle.bin')\n>>> type(bundle)\n<class 'preserves.schema.Namespace'>\n
Source code in preserves/schema.py
def load_schema_file(filename):\n\"\"\"Simple entry point to the compiler: creates a [Compiler][preserves.schema.Compiler],\n calls [load][preserves.schema.Compiler.load] on it, and returns its `root`\n [Namespace][preserves.schema.Namespace].\n\n ```python\n >>> bundle = load_schema_file('docs/syndicate-protocols-schema-bundle.bin')\n >>> type(bundle)\n <class 'preserves.schema.Namespace'>\n\n ```\n \"\"\"\n c = Compiler()\n c.load(filename)\n return c.root\n
"},{"location":"schema/#preserves.schema.safeattrname","title":"safeattrname(k)
","text":"Escapes Python keywords by prepending _
; passes all other strings through.
Source code in preserves/schema.py
def safeattrname(k):\n\"\"\"Escapes Python keywords by prepending `_`; passes all other strings through.\"\"\"\n return k + '_' if keyword.iskeyword(k) else k\n
"},{"location":"text/","title":"Human-readable text syntax","text":"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]]))\n'<hi 1 [2 3]>'\n>>> parse('<hi 1 [2 3]>')\n#hi(1, (2, 3))\n
"},{"location":"text/#preserves.text.Formatter","title":"Formatter(format_embedded=lambda x: x, indent=None, with_commas=False, trailing_comma=False, include_annotations=True)
","text":" Bases: TextCodec
Printer (and indenting pretty-printer) for producing human-readable syntax from Preserves Value
s.
>>> f = Formatter()\n>>> f.append({'a': 1, 'b': 2})\n>>> f.append(Record(Symbol('label'), ['field1', ['field2item1', 'field2item2']]))\n>>> print(f.contents())\n{\"a\": 1 \"b\": 2} <label \"field1\" [\"field2item1\" \"field2item2\"]>\n\n>>> f = Formatter(indent=4)\n>>> f.append({'a': 1, 'b': 2})\n>>> f.append(Record(Symbol('label'), ['field1', ['field2item1', 'field2item2']]))\n>>> print(f.contents())\n{\n \"a\": 1\n \"b\": 2\n}\n<label \"field1\" [\n \"field2item1\"\n \"field2item2\"\n]>\n
Parameters:
Name Type Description Default format_embedded
function accepting an Embedded.embeddedValue and returning a Value
for serialization.
lambda x: x
indent
int | None
None
disables indented pretty-printing; otherwise, an int
specifies indentation per nesting-level.
None
with_commas
bool
True
causes commas to separate sequence and set items and dictionary entries; False
omits commas.
False
trailing_comma
bool
True
causes a comma to be printed after the final item or entry in a sequence, set or dictionary; False
omits this trailing comma
False
include_annotations
bool
True
causes annotations to be included in the output; False
causes them to be omitted.
True
Attributes:
Name Type Description indent_delta
int
indentation per nesting-level
chunks
list[str]
fragments of output
Source code in preserves/text.py
def __init__(self,\n format_embedded=lambda x: x,\n indent=None,\n with_commas=False,\n trailing_comma=False,\n include_annotations=True):\n super(Formatter, self).__init__()\n self.indent_delta = 0 if indent is None else indent\n self.indent_distance = 0\n self.nesting = 0\n self.with_commas = with_commas\n self.trailing_comma = trailing_comma\n self.chunks = []\n self._format_embedded = format_embedded\n self.include_annotations = include_annotations\n
"},{"location":"text/#preserves.text.Formatter.append","title":"append(v)
","text":"Extend self.chunks
with at least one chunk, together making up the text representation of v
.
Source code in preserves/text.py
def append(self, v):\n\"\"\"Extend `self.chunks` with at least one chunk, together making up the text\n representation of `v`.\"\"\"\n if self.chunks and self.nesting == 0:\n self.write_indent_space()\n try:\n self.nesting += 1\n self._append(v)\n finally:\n self.nesting -= 1\n
"},{"location":"text/#preserves.text.Formatter.contents","title":"contents()
","text":"Returns a str
constructed from the join of the chunks in self.chunks
.
Source code in preserves/text.py
def contents(self):\n\"\"\"Returns a `str` constructed from the join of the chunks in `self.chunks`.\"\"\"\n return u''.join(self.chunks)\n
"},{"location":"text/#preserves.text.Formatter.is_indenting","title":"is_indenting()
","text":"Returns True
iff this Formatter is in pretty-printing indenting mode.
Source code in preserves/text.py
def is_indenting(self):\n\"\"\"Returns `True` iff this [Formatter][preserves.text.Formatter] is in pretty-printing\n indenting mode.\"\"\"\n return self.indent_delta > 0\n
"},{"location":"text/#preserves.text.Parser","title":"Parser(input_buffer='', include_annotations=False, parse_embedded=lambda x: x)
","text":" 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 True
, wrap each value and subvalue in an Annotated object.
False
parse_embedded
function accepting a Value
and returning a possibly-decoded form of that value suitable for placing into an Embedded object.
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 []')\n>>> d.next()\n123\n>>> d.next()\n'hello'\n>>> d.next()\n()\n>>> d.next()\nTraceback (most recent call last):\n ...\npreserves.error.ShortPacket: Short input buffer\n
Alternatively, keep calling try_next until it yields None
, which is not in the domain of Preserves Value
s:
>>> d = Parser('123 \"hello\" @x []')\n>>> d.try_next()\n123\n>>> d.try_next()\n'hello'\n>>> d.try_next()\n()\n>>> d.try_next()\n
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 []')\n>>> list(d)\n[123, 'hello', ()]\n
>>> for v in Parser('123 \"hello\" @x []'):\n... print(repr(v))\n123\n'hello'\n()\n
Supply include_annotations=True
to read annotations alongside the annotated values:
>>> d = Parser('123 \"hello\" @x []', include_annotations=True)\n>>> list(d)\n[123, 'hello', @#x ()]\n
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')\n>>> d.try_next()\n123\n>>> d.try_next() # returns None because the input is incomplete\n>>> d.extend('llo\"')\n>>> d.try_next()\n'hello'\n>>> d.try_next()\n
Attributes:
Name Type Description input_buffer
str
buffered input waiting to be processed
index
int
read position within input_buffer
Source code in preserves/text.py
def __init__(self, input_buffer=u'', include_annotations=False, parse_embedded=lambda x: x):\n super(Parser, self).__init__()\n self.input_buffer = input_buffer\n self.index = 0\n self.include_annotations = include_annotations\n self.parse_embedded = parse_embedded\n
"},{"location":"text/#preserves.text.Parser.extend","title":"extend(text)
","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
def extend(self, text):\n\"\"\"Appends `text` to the remaining contents of `self.input_buffer`, trimming already-processed\n text from the front of `self.input_buffer` and resetting `self.index` to zero.\"\"\"\n self.input_buffer = self.input_buffer[self.index:] + text\n self.index = 0\n
"},{"location":"text/#preserves.text.Parser.next","title":"next()
","text":"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
def next(self):\n\"\"\"Reads the next complete `Value` from the internal buffer, raising\n [ShortPacket][preserves.error.ShortPacket] if too few bytes are available, or\n [DecodeError][preserves.error.DecodeError] if the input is invalid somehow.\n\n \"\"\"\n self.skip_whitespace()\n c = self.peek()\n if c == '\"':\n self.skip()\n return self.wrap(self.read_string('\"'))\n if c == '|':\n self.skip()\n return self.wrap(Symbol(self.read_string('|')))\n if c in ';@':\n annotations = self.gather_annotations()\n v = self.next()\n if self.include_annotations:\n v.annotations = annotations + v.annotations\n return v\n if c == ':':\n raise DecodeError('Unexpected key/value separator between items')\n if c == '#':\n self.skip()\n c = self.nextchar()\n if c == 'f': return self.wrap(False)\n if c == 't': return self.wrap(True)\n if c == '{': return self.wrap(frozenset(self.upto('}')))\n if c == '\"': return self.wrap(self.read_literal_binary())\n if c == 'x':\n c = self.nextchar()\n if c == '\"': return self.wrap(self.read_hex_binary())\n if c == 'f': return self.wrap(self.read_hex_float(4))\n if c == 'd': return self.wrap(self.read_hex_float(8))\n raise DecodeError('Invalid #x syntax')\n if c == '[': return self.wrap(self.read_base64_binary())\n if c == '=':\n old_ann = self.include_annotations\n self.include_annotations = True\n bs_val = self.next()\n self.include_annotations = old_ann\n if len(bs_val.annotations) > 0:\n raise DecodeError('Annotations not permitted after #=')\n bs_val = bs_val.item\n if not isinstance(bs_val, bytes):\n raise DecodeError('ByteString must follow #=')\n return self.wrap(Decoder(bs_val, include_annotations = self.include_annotations).next())\n if c == '!':\n if self.parse_embedded is None:\n raise DecodeError('No parse_embedded function supplied')\n return self.wrap(Embedded(self.parse_embedded(self.next())))\n raise DecodeError('Invalid # syntax')\n if c == '<':\n self.skip()\n vs = self.upto('>')\n if len(vs) == 0:\n raise DecodeError('Missing record label')\n return self.wrap(Record(vs[0], vs[1:]))\n if c == '[':\n self.skip()\n return self.wrap(self.upto(']'))\n if c == '{':\n self.skip()\n return self.wrap(self.read_dictionary())\n if c in '>]}':\n raise DecodeError('Unexpected ' + c)\n self.skip()\n return self.wrap(self.read_raw_symbol_or_number([c]))\n
"},{"location":"text/#preserves.text.Parser.try_next","title":"try_next()
","text":"Like next, but returns None
instead of raising ShortPacket.
Source code in preserves/text.py
def try_next(self):\n\"\"\"Like [next][preserves.text.Parser.next], but returns `None` instead of raising\n [ShortPacket][preserves.error.ShortPacket].\"\"\"\n start = self.index\n try:\n return self.next()\n except ShortPacket:\n self.index = start\n return None\n
"},{"location":"text/#preserves.text.parse","title":"parse(text, **kwargs)
","text":"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
def parse(text, **kwargs):\n\"\"\"Yields the first complete encoded value from `text`, passing `kwargs` through to the\n [Parser][preserves.text.Parser] constructor. Raises exceptions as per\n [next][preserves.text.Parser.next].\n\n Args:\n text (str): encoded data to decode\n\n \"\"\"\n return Parser(input_buffer=text, **kwargs).next()\n
"},{"location":"text/#preserves.text.parse_with_annotations","title":"parse_with_annotations(bs, **kwargs)
","text":"Like parse, but supplying include_annotations=True
to the Parser constructor.
Source code in preserves/text.py
def parse_with_annotations(bs, **kwargs):\n\"\"\"Like [parse][preserves.text.parse], but supplying `include_annotations=True` to the\n [Parser][preserves.text.Parser] constructor.\"\"\"\n return Parser(input_buffer=bs, include_annotations=True, **kwargs).next()\n
"},{"location":"text/#preserves.text.stringify","title":"stringify(v, **kwargs)
","text":"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
def stringify(v, **kwargs):\n\"\"\"Convert a single `Value` `v` to a string. Any supplied `kwargs` are passed on to the\n underlying [Formatter][preserves.text.Formatter] constructor.\"\"\"\n e = Formatter(**kwargs)\n e.append(v)\n return e.contents()\n
"},{"location":"values/","title":"Representations of Values","text":"Python's strings, byte strings, integers, booleans, and double-precision floats stand directly for their Preserves counterparts. Wrapper objects for Float and Symbol complete the suite of atomic types.
Python's lists and tuples correspond to Preserves Sequence
s, and dicts and sets to Dictionary
and Set
values, respectively. Preserves Record
s are represented by Record objects. Finally, embedded values are represented by Embedded objects.
The preserves.values module implements the core representations of Preserves Value
s as Python values.
"},{"location":"values/#preserves.values.Annotated","title":"Annotated(item)
","text":" 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\n>>> a = preserves.parse('''\n... ; A comment\n... [1 2 3]\n... ''', include_annotations=True)\n>>> a\n@' A comment' (1, 2, 3)\n>>> a.item\n(1, 2, 3)\n>>> a.annotations\n[' A comment']\n>>> a == (1, 2, 3)\nTrue\n>>> a == preserves.parse('@xyz [1 2 3]', include_annotations=True)\nTrue\n>>> a[0]\nTraceback (most recent call last):\n ...\nTypeError: 'Annotated' object is not subscriptable\n>>> a.item[0]\n1\n>>> type(a.item[0])\n<class 'preserves.values.Annotated'>\n>>> a.item[0].annotations\n[]\n>>> print(preserves.stringify(a))\n@\" A comment\" [1 2 3]\n>>> print(preserves.stringify(a, include_annotations=False))\n[1 2 3]\n
Attributes:
Name Type Description item
Value
the underlying annotated Value
annotations
list[Value]
the annotations attached to self.item
Source code in preserves/values.py
def __init__(self, item):\n self.annotations = []\n self.item = item\n
"},{"location":"values/#preserves.values.Annotated.peel","title":"peel()
","text":"Calls strip_annotations on self
with depth=1
.
Source code in preserves/values.py
def peel(self):\n\"\"\"Calls [strip_annotations][preserves.values.strip_annotations] on `self` with `depth=1`.\"\"\"\n return strip_annotations(self, 1)\n
"},{"location":"values/#preserves.values.Annotated.strip","title":"strip(depth=inf)
","text":"Calls strip_annotations on self
and depth
.
Source code in preserves/values.py
def strip(self, depth=inf):\n\"\"\"Calls [strip_annotations][preserves.values.strip_annotations] on `self` and `depth`.\"\"\"\n return strip_annotations(self, depth)\n
"},{"location":"values/#preserves.values.Embedded","title":"Embedded(embeddedValue)
","text":"Representation of a Preserves Embedded
value. For more on the meaning and use of embedded values, see the specification.
>>> import io\n>>> e = Embedded(io.StringIO('some text'))\n>>> e # doctest: +ELLIPSIS\n#!<_io.StringIO object at ...>\n>>> e.embeddedValue # doctest: +ELLIPSIS\n<_io.StringIO object at ...>\n
>>> import preserves\n>>> print(preserves.stringify(Embedded(None)))\nTraceback (most recent call last):\n ...\nTypeError: Cannot preserves-format: None\n>>> print(preserves.stringify(Embedded(None), format_embedded=lambda x: 'abcdef'))\n#!\"abcdef\"\n
Attributes:
Name Type Description embeddedValue
any Python value; could be a platform object, could be a representation of a Preserves Value
, could be None
, could be anything!
Source code in preserves/values.py
def __init__(self, embeddedValue):\n self.embeddedValue = embeddedValue\n
"},{"location":"values/#preserves.values.Float","title":"Float(value)
","text":" Bases: object
Wrapper for treating a Python double-precision floating-point value as a single-precision (32-bit) float, from Preserves' perspective. (Python lacks native single-precision floating point support.)
>>> Float(3.45)\nFloat(3.45)\n>>> import preserves\n>>> preserves.stringify(Float(3.45))\n'3.45f'\n>>> preserves.stringify(3.45)\n'3.45'\n>>> preserves.parse('3.45f')\nFloat(3.45)\n>>> preserves.parse('3.45')\n3.45\n>>> preserves.encode(Float(3.45))\nb'\\x82@\\\\\\xcc\\xcd'\n>>> preserves.encode(3.45)\nb'\\x83@\\x0b\\x99\\x99\\x99\\x99\\x99\\x9a'\n
Attributes:
Name Type Description value
float
the double-precision representation of intended single-precision value
Source code in preserves/values.py
def __init__(self, value):\n self.value = value\n
"},{"location":"values/#preserves.values.Float.from_bytes","title":"from_bytes(bs)
staticmethod
","text":"Converts a 4-byte-long byte string to a 32-bit single-precision floating point value wrapped in a Float instance. Takes care to preserve the quiet/signalling bit-pattern of NaN values, unlike its struct.unpack('>f', ...)
equivalent.
>>> Float.from_bytes(b'\\x7f\\x80\\x00{')\nFloat(nan)\n>>> Float.from_bytes(b'\\x7f\\x80\\x00{').to_bytes()\nb'\\x7f\\x80\\x00{'\n\n>>> struct.unpack('>f', b'\\x7f\\x80\\x00{')[0]\nnan\n>>> Float(struct.unpack('>f', b'\\x7f\\x80\\x00{')[0]).to_bytes()\nb'\\x7f\\xc0\\x00{'\n>>> struct.pack('>f', struct.unpack('>f', b'\\x7f\\x80\\x00{')[0])\nb'\\x7f\\xc0\\x00{'\n
(Note well the difference between 7f80007b
and 7fc0007b
!)
Source code in preserves/values.py
@staticmethod\ndef from_bytes(bs):\n\"\"\"Converts a 4-byte-long byte string to a 32-bit single-precision floating point value\n wrapped in a [Float][preserves.values.Float] instance. Takes care to preserve the\n quiet/signalling bit-pattern of NaN values, unlike its `struct.unpack('>f', ...)`\n equivalent.\n\n ```python\n >>> Float.from_bytes(b'\\\\x7f\\\\x80\\\\x00{')\n Float(nan)\n >>> Float.from_bytes(b'\\\\x7f\\\\x80\\\\x00{').to_bytes()\n b'\\\\x7f\\\\x80\\\\x00{'\n\n >>> struct.unpack('>f', b'\\\\x7f\\\\x80\\\\x00{')[0]\n nan\n >>> Float(struct.unpack('>f', b'\\\\x7f\\\\x80\\\\x00{')[0]).to_bytes()\n b'\\\\x7f\\\\xc0\\\\x00{'\n >>> struct.pack('>f', struct.unpack('>f', b'\\\\x7f\\\\x80\\\\x00{')[0])\n b'\\\\x7f\\\\xc0\\\\x00{'\n\n ```\n\n (Note well the difference between `7f80007b` and `7fc0007b`!)\n\n \"\"\"\n vf = struct.unpack('>I', bs)[0]\n if (vf & 0x7f800000) == 0x7f800000:\n # NaN or inf. Preserve quiet/signalling bit by manually expanding to double-precision.\n sign = vf >> 31\n payload = vf & 0x007fffff\n dbs = struct.pack('>Q', (sign << 63) | 0x7ff0000000000000 | (payload << 29))\n return Float(struct.unpack('>d', dbs)[0])\n else:\n return Float(struct.unpack('>f', bs)[0])\n
"},{"location":"values/#preserves.values.Float.to_bytes","title":"to_bytes()
","text":"Converts this 32-bit single-precision floating point value to its binary32 format, taking care to preserve the quiet/signalling bit-pattern of NaN values, unlike its struct.pack('>f', ...)
equivalent.
>>> Float.from_bytes(b'\\x7f\\x80\\x00{')\nFloat(nan)\n>>> Float.from_bytes(b'\\x7f\\x80\\x00{').to_bytes()\nb'\\x7f\\x80\\x00{'\n\n>>> struct.unpack('>f', b'\\x7f\\x80\\x00{')[0]\nnan\n>>> Float(struct.unpack('>f', b'\\x7f\\x80\\x00{')[0]).to_bytes()\nb'\\x7f\\xc0\\x00{'\n>>> struct.pack('>f', struct.unpack('>f', b'\\x7f\\x80\\x00{')[0])\nb'\\x7f\\xc0\\x00{'\n
(Note well the difference between 7f80007b
and 7fc0007b
!)
Source code in preserves/values.py
def to_bytes(self):\n\"\"\"Converts this 32-bit single-precision floating point value to its binary32 format,\n taking care to preserve the quiet/signalling bit-pattern of NaN values, unlike its\n `struct.pack('>f', ...)` equivalent.\n\n ```python\n >>> Float.from_bytes(b'\\\\x7f\\\\x80\\\\x00{')\n Float(nan)\n >>> Float.from_bytes(b'\\\\x7f\\\\x80\\\\x00{').to_bytes()\n b'\\\\x7f\\\\x80\\\\x00{'\n\n >>> struct.unpack('>f', b'\\\\x7f\\\\x80\\\\x00{')[0]\n nan\n >>> Float(struct.unpack('>f', b'\\\\x7f\\\\x80\\\\x00{')[0]).to_bytes()\n b'\\\\x7f\\\\xc0\\\\x00{'\n >>> struct.pack('>f', struct.unpack('>f', b'\\\\x7f\\\\x80\\\\x00{')[0])\n b'\\\\x7f\\\\xc0\\\\x00{'\n\n ```\n\n (Note well the difference between `7f80007b` and `7fc0007b`!)\n\n \"\"\"\n\n if math.isnan(self.value) or math.isinf(self.value):\n dbs = struct.pack('>d', self.value)\n vd = struct.unpack('>Q', dbs)[0]\n sign = vd >> 63\n payload = (vd >> 29) & 0x007fffff\n vf = (sign << 31) | 0x7f800000 | payload\n return struct.pack('>I', vf)\n else:\n return struct.pack('>f', self.value)\n
"},{"location":"values/#preserves.values.ImmutableDict","title":"ImmutableDict(*args, **kwargs)
","text":" 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)])\n>>> d\n{'a': 1, 'b': 2}\n>>> d['c'] = 3\nTraceback (most recent call last):\n ...\nTypeError: Immutable\n>>> del d['b']\nTraceback (most recent call last):\n ...\nTypeError: Immutable\n
Source code in preserves/values.py
def __init__(self, *args, **kwargs):\n if hasattr(self, '__hash'): raise TypeError('Immutable')\n super(ImmutableDict, self).__init__(*args, **kwargs)\n self.__hash = None\n
"},{"location":"values/#preserves.values.ImmutableDict.from_kvs","title":"from_kvs(kvs)
staticmethod
","text":"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])\n{'a': 1, 'b': 2}\n>>> ImmutableDict.from_kvs(['a', 1, 'b', 2])['c'] = 3\nTraceback (most recent call last):\n ...\nTypeError: Immutable\n
Source code in preserves/values.py
@staticmethod\ndef from_kvs(kvs):\n\"\"\"Constructs an [ImmutableDict][preserves.values.ImmutableDict] from a sequence of\n alternating keys and values; compare to the\n [ImmutableDict][preserves.values.ImmutableDict] constructor, which takes a sequence of\n key-value pairs.\n\n ```python\n >>> ImmutableDict.from_kvs(['a', 1, 'b', 2])\n {'a': 1, 'b': 2}\n >>> ImmutableDict.from_kvs(['a', 1, 'b', 2])['c'] = 3\n Traceback (most recent call last):\n ...\n TypeError: Immutable\n\n ```\n\n \"\"\"\n\n i = iter(kvs)\n result = ImmutableDict()\n result_proxy = super(ImmutableDict, result)\n try:\n while True:\n k = next(i)\n try:\n v = next(i)\n except StopIteration:\n raise DecodeError(\"Missing dictionary value\")\n result_proxy.__setitem__(k, v)\n except StopIteration:\n pass\n return result\n
"},{"location":"values/#preserves.values.Record","title":"Record(key, fields)
","text":" 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']])\n>>> r\n#label('field1', ['field2item1', 'field2item2'])\n>>> r.key\n#label\n>>> r.fields\n('field1', ['field2item1', 'field2item2'])\n>>> import preserves\n>>> preserves.stringify(r)\n'<label \"field1\" [\"field2item1\" \"field2item2\"]>'\n>>> r == preserves.parse('<label \"field1\" [\"field2item1\" \"field2item2\"]>')\nTrue\n
Parameters:
Name Type Description Default key
Value
the Record
's label
required fields
iterable[Value]
the fields of the Record
required Attributes:
Name Type Description key
Value
the Record
's label
fields
tuple[Value]
the fields of the Record
Source code in preserves/values.py
def __init__(self, key, fields):\n self.key = key\n self.fields = tuple(fields)\n self.__hash = None\n
"},{"location":"values/#preserves.values.Record.makeBasicConstructor","title":"makeBasicConstructor(label, fieldNames)
staticmethod
","text":"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')\n>>> c(1969, 7, 16)\n#date(1969, 7, 16)\n>>> c.constructorInfo\n#date/3\n>>> c.isClassOf(c(1969, 7, 16))\nTrue\n>>> c.isClassOf(Record(Symbol('date'), [1969, 7, 16]))\nTrue\n>>> c.isClassOf(Record(Symbol('date'), [1969]))\nFalse\n>>> c.ensureClassOf(c(1969, 7, 16))\n#date(1969, 7, 16)\n>>> c.ensureClassOf(Record(Symbol('date'), [1969]))\nTraceback (most recent call last):\n ...\nTypeError: Record: expected #date/3, got #date(1969)\n>>> c._year(c(1969, 7, 16))\n1969\n>>> c._month(c(1969, 7, 16))\n7\n>>> c._day(c(1969, 7, 16))\n16\n
Parameters:
Name Type Description Default label
Value
Label to use for constructed/matched Record
s
required fieldNames
tuple[str] | list[str] | str
Names of the Record
's fields
required Source code in preserves/values.py
@staticmethod\ndef makeBasicConstructor(label, fieldNames):\n\"\"\"Constructs and returns a \"constructor\" for `Record`s having a certain `label` and\n number of fields.\n\n Deprecated:\n Use [preserves.schema][] definitions instead.\n\n The \"constructor\" is a callable function that accepts `len(fields)` arguments and\n returns a [Record][preserves.values.Record] with `label` as its label and the arguments\n to the constructor as field values.\n\n In addition, the \"constructor\" has a `constructorInfo` attribute holding a\n [RecordConstructorInfo][preserves.values.RecordConstructorInfo] object, an `isClassOf`\n attribute holding a unary function that returns `True` iff its argument is a\n [Record][preserves.values.Record] with label `label` and arity `len(fieldNames)`, and\n an `ensureClassOf` attribute that raises an `Exception` if `isClassOf` returns false on\n its argument and returns the argument otherwise.\n\n Finally, for each field name `f` in `fieldNames`, the \"constructor\" object has an\n attribute `_f` that is a unary function that retrieves the `f` field from the passed in\n argument.\n\n ```python\n >>> c = Record.makeBasicConstructor(Symbol('date'), 'year month day')\n >>> c(1969, 7, 16)\n #date(1969, 7, 16)\n >>> c.constructorInfo\n #date/3\n >>> c.isClassOf(c(1969, 7, 16))\n True\n >>> c.isClassOf(Record(Symbol('date'), [1969, 7, 16]))\n True\n >>> c.isClassOf(Record(Symbol('date'), [1969]))\n False\n >>> c.ensureClassOf(c(1969, 7, 16))\n #date(1969, 7, 16)\n >>> c.ensureClassOf(Record(Symbol('date'), [1969]))\n Traceback (most recent call last):\n ...\n TypeError: Record: expected #date/3, got #date(1969)\n >>> c._year(c(1969, 7, 16))\n 1969\n >>> c._month(c(1969, 7, 16))\n 7\n >>> c._day(c(1969, 7, 16))\n 16\n\n ```\n\n Args:\n label (Value): Label to use for constructed/matched `Record`s\n fieldNames (tuple[str] | list[str] | str): Names of the `Record`'s fields\n\n \"\"\"\n if type(fieldNames) == str:\n fieldNames = fieldNames.split()\n arity = len(fieldNames)\n def ctor(*fields):\n if len(fields) != arity:\n raise Exception(\"Record: cannot instantiate %r expecting %d fields with %d fields\"%(\n label,\n arity,\n len(fields)))\n return Record(label, fields)\n ctor.constructorInfo = RecordConstructorInfo(label, arity)\n ctor.isClassOf = lambda v: \\\n isinstance(v, Record) and v.key == label and len(v.fields) == arity\n def ensureClassOf(v):\n if not ctor.isClassOf(v):\n raise TypeError(\"Record: expected %r/%d, got %r\" % (label, arity, v))\n return v\n ctor.ensureClassOf = ensureClassOf\n for fieldIndex in range(len(fieldNames)):\n fieldName = fieldNames[fieldIndex]\n # Stupid python scoping bites again\n def getter(fieldIndex):\n return lambda v: ensureClassOf(v)[fieldIndex]\n setattr(ctor, '_' + fieldName, getter(fieldIndex))\n return ctor\n
"},{"location":"values/#preserves.values.Record.makeConstructor","title":"makeConstructor(labelSymbolText, fieldNames)
staticmethod
","text":"Equivalent to Record.makeBasicConstructor(Symbol(labelSymbolText), fieldNames)
.
Deprecated Use preserves.schema definitions instead.
Source code in preserves/values.py
@staticmethod\ndef makeConstructor(labelSymbolText, fieldNames):\n\"\"\"\n Equivalent to `Record.makeBasicConstructor(Symbol(labelSymbolText), fieldNames)`.\n\n Deprecated:\n Use [preserves.schema][] definitions instead.\n \"\"\"\n return Record.makeBasicConstructor(Symbol(labelSymbolText), fieldNames)\n
"},{"location":"values/#preserves.values.RecordConstructorInfo","title":"RecordConstructorInfo(key, arity)
","text":" Bases: object
Describes the shape of a Record
constructor, namely its label and its arity (field count).
>>> RecordConstructorInfo(Symbol('label'), 3)\n#label/3\n
Attributes:
Name Type Description key
Value
the label of matching Record
s
arity
int
the number of fields in matching Record
s
Source code in preserves/values.py
def __init__(self, key, arity):\n self.key = key\n self.arity = arity\n
"},{"location":"values/#preserves.values.Symbol","title":"Symbol(name)
","text":" Bases: object
Representation of Preserves Symbol
s.
>>> Symbol('xyz')\n#xyz\n>>> Symbol('xyz').name\n'xyz'\n>>> import preserves\n>>> preserves.stringify(Symbol('xyz'))\n'xyz'\n>>> preserves.stringify(Symbol('hello world'))\n'|hello world|'\n>>> preserves.parse('xyz')\n#xyz\n>>> preserves.parse('|hello world|')\n#hello world\n
Attributes:
Name Type Description name
str | Symbol
The symbol's text label. If an existing Symbol is passed in, the existing Symbol's name
is used as the name
for the new Symbol.
Source code in preserves/values.py
def __init__(self, name):\n self.name = name.name if isinstance(name, Symbol) else name\n
"},{"location":"values/#preserves.values.annotate","title":"annotate(v, *anns)
","text":"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\n>>> print(preserves.stringify(annotate(123, \"A comment\", \"Another comment\")))\n@\"A comment\" @\"Another comment\" 123\n
Source code in preserves/values.py
def annotate(v, *anns):\n\"\"\"Wraps `v` in an [Annotated][preserves.values.Annotated] object, if it isn't already\n wrapped, and appends each of the `anns` to the [Annotated][preserves.values.Annotated]'s\n `annotations` sequence. NOTE: Does not recursively ensure that any parts of the argument\n `v` are themselves wrapped in [Annotated][preserves.values.Annotated] objects!\n\n ```python\n >>> import preserves\n >>> print(preserves.stringify(annotate(123, \"A comment\", \"Another comment\")))\n @\"A comment\" @\"Another comment\" 123\n\n ```\n \"\"\"\n if not is_annotated(v):\n v = Annotated(v)\n for a in anns:\n v.annotations.append(a)\n return v\n
"},{"location":"values/#preserves.values.cmp_floats","title":"cmp_floats(a, b)
","text":"Implements the totalOrder
predicate defined in section 5.10 of IEEE Std 754-2008.
Source code in preserves/values.py
def cmp_floats(a, b):\n\"\"\"Implements the `totalOrder` predicate defined in section 5.10 of [IEEE Std\n 754-2008](https://dx.doi.org/10.1109/IEEESTD.2008.4610935).\n\n \"\"\"\n a = float_to_int(a)\n b = float_to_int(b)\n if a & 0x8000000000000000: a = a ^ 0x7fffffffffffffff\n if b & 0x8000000000000000: b = b ^ 0x7fffffffffffffff\n return a - b\n
"},{"location":"values/#preserves.values.dict_kvs","title":"dict_kvs(d)
","text":"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}))\n['a', 1, 'b', 2]\n
Source code in preserves/values.py
def dict_kvs(d):\n\"\"\"Generator function yielding a sequence of alternating keys and values from `d`. In some\n sense the inverse of [ImmutableDict.from_kvs][preserves.values.ImmutableDict.from_kvs].\n\n ```python\n >>> list(dict_kvs({'a': 1, 'b': 2}))\n ['a', 1, 'b', 2]\n\n ```\n \"\"\"\n for k in d:\n yield k\n yield d[k]\n
"},{"location":"values/#preserves.values.is_annotated","title":"is_annotated(v)
","text":"True
iff v
is an instance of Annotated.
Source code in preserves/values.py
def is_annotated(v):\n\"\"\"`True` iff `v` is an instance of [Annotated][preserves.values.Annotated].\"\"\"\n return isinstance(v, Annotated)\n
"},{"location":"values/#preserves.values.preserve","title":"preserve(v)
","text":"Converts v
to a representation of a Preserves Value
by (repeatedly) setting
v = v.__preserve__()\n
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
def preserve(v):\n\"\"\"Converts `v` to a representation of a Preserves `Value` by (repeatedly) setting\n\n ```python\n v = v.__preserve__()\n ```\n\n while `v` has a `__preserve__` method. Parsed [Schema][preserves.schema]\n values are able to render themselves to their serialized representations this way.\n\n \"\"\"\n while hasattr(v, '__preserve__'):\n v = v.__preserve__()\n return v\n
"},{"location":"values/#preserves.values.strip_annotations","title":"strip_annotations(v, depth=inf)
","text":"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\n>>> a = preserves.parse('@\"A comment\" [@a 1 @b 2 @c 3]', include_annotations=True)\n>>> is_annotated(a)\nTrue\n>>> print(preserves.stringify(a))\n@\"A comment\" [@a 1 @b 2 @c 3]\n>>> print(preserves.stringify(strip_annotations(a)))\n[1 2 3]\n>>> print(preserves.stringify(strip_annotations(a, depth=1)))\n[@a 1 @b 2 @c 3]\n
Source code in preserves/values.py
def strip_annotations(v, depth=inf):\n\"\"\"Exposes `depth` layers of raw structure of\n potentially-[Annotated][preserves.values.Annotated] `Value`s. If `depth==0` or `v` is not\n [Annotated][preserves.values.Annotated], just returns `v`. Otherwise, descends recursively\n into the structure of `v.item`.\n\n ```python\n >>> import preserves\n >>> a = preserves.parse('@\"A comment\" [@a 1 @b 2 @c 3]', include_annotations=True)\n >>> is_annotated(a)\n True\n >>> print(preserves.stringify(a))\n @\"A comment\" [@a 1 @b 2 @c 3]\n >>> print(preserves.stringify(strip_annotations(a)))\n [1 2 3]\n >>> print(preserves.stringify(strip_annotations(a, depth=1)))\n [@a 1 @b 2 @c 3]\n\n ```\n \"\"\"\n\n if depth == 0: return v\n if not is_annotated(v): return v\n\n next_depth = depth - 1\n def walk(v):\n return strip_annotations(v, next_depth)\n\n v = v.item\n if isinstance(v, Record):\n return Record(strip_annotations(v.key, depth), tuple(walk(f) for f in v.fields))\n elif isinstance(v, list):\n return tuple(walk(f) for f in v)\n elif isinstance(v, tuple):\n return tuple(walk(f) for f in v)\n elif isinstance(v, set):\n return frozenset(walk(f) for f in v)\n elif isinstance(v, frozenset):\n return frozenset(walk(f) for f in v)\n elif isinstance(v, dict):\n return ImmutableDict.from_kvs(walk(f) for f in dict_kvs(v))\n elif is_annotated(v):\n raise ValueError('Improper annotation structure')\n else:\n return v\n
"}]}
\ No newline at end of file
diff --git a/python/0.18.1/sitemap.xml b/python/0.18.1/sitemap.xml
new file mode 100644
index 0000000..6913358
--- /dev/null
+++ b/python/0.18.1/sitemap.xml
@@ -0,0 +1,58 @@
+
+
+
+ https://preserves.dev/python/latest/
+ 2023-03-17
+ daily
+
+
+ https://preserves.dev/python/latest/api/
+ 2023-03-17
+ daily
+
+
+ https://preserves.dev/python/latest/binary/
+ 2023-03-17
+ daily
+
+
+ https://preserves.dev/python/latest/compare/
+ 2023-03-17
+ daily
+
+
+ https://preserves.dev/python/latest/error/
+ 2023-03-17
+ daily
+
+
+ https://preserves.dev/python/latest/fold/
+ 2023-03-17
+ daily
+
+
+ https://preserves.dev/python/latest/merge/
+ 2023-03-17
+ daily
+
+
+ https://preserves.dev/python/latest/path/
+ 2023-03-17
+ daily
+
+
+ https://preserves.dev/python/latest/schema/
+ 2023-03-17
+ daily
+
+
+ https://preserves.dev/python/latest/text/
+ 2023-03-17
+ daily
+
+
+ https://preserves.dev/python/latest/values/
+ 2023-03-17
+ daily
+
+
\ No newline at end of file
diff --git a/python/0.18.1/text/index.html b/python/0.18.1/text/index.html
new file mode 100644
index 0000000..78105b8
--- /dev/null
+++ b/python/0.18.1/text/index.html
@@ -0,0 +1,1577 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Human-readable text syntax - Python Preserves
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+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))
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+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 Value
for serialization.
+
+ lambda x: x
+
+
+
+ indent
+
+ int | None
+
+ None
disables indented pretty-printing; otherwise, an int
specifies indentation
+per nesting-level.
+
+ None
+
+
+
+ with_commas
+
+ bool
+
+ True
causes commas to separate sequence and set items and dictionary entries;
+False
omits commas.
+
+ False
+
+
+
+ trailing_comma
+
+ bool
+
+ True
causes a comma to be printed after the final item or entry in a sequence,
+set or dictionary; False
omits this trailing comma
+
+ False
+
+
+
+ include_annotations
+
+ bool
+
+ True
causes annotations to be included in the output; False
causes them to be
+omitted.
+
+ True
+
+
+
+
+
+
Attributes:
+
+
+
+ Name
+ Type
+ Description
+
+
+
+
+ indent_delta
+
+ int
+
+ indentation per nesting-level
+
+
+ chunks
+
+ list[str]
+
+ fragments of output
+
+
+
+
+
+
+ Source code in preserves/text.py
+ 475
+476
+477
+478
+479
+480
+481
+482
+483
+484
+485
+486
+487
+488
+489 def __init__ ( self ,
+ format_embedded = lambda x : x ,
+ indent = None ,
+ with_commas = False ,
+ trailing_comma = False ,
+ include_annotations = True ):
+ super ( Formatter , self ) . __init__ ()
+ self . indent_delta = 0 if indent is None else indent
+ self . indent_distance = 0
+ self . nesting = 0
+ self . with_commas = with_commas
+ self . trailing_comma = trailing_comma
+ self . chunks = []
+ self . _format_embedded = format_embedded
+ self . include_annotations = include_annotations
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+append ( v )
+
+
+
+
+
+
+
Extend self.chunks
with at least one chunk, together making up the text
+representation of v
.
+
+
+ Source code in preserves/text.py
+ 545
+546
+547
+548
+549
+550
+551
+552
+553
+554 def append ( self , v ):
+ """Extend `self.chunks` with at least one chunk, together making up the text
+ representation of `v`."""
+ if self . chunks and self . nesting == 0 :
+ self . write_indent_space ()
+ try :
+ self . nesting += 1
+ self . _append ( v )
+ finally :
+ self . nesting -= 1
+
+
+
+
+
+
+
+
+
+
+
+contents ()
+
+
+
+
+
+
+
Returns a str
constructed from the join of the chunks in self.chunks
.
+
+
+ Source code in preserves/text.py
+ def contents ( self ):
+ """Returns a `str` constructed from the join of the chunks in `self.chunks`."""
+ return u '' . join ( self . chunks )
+
+
+
+
+
+
+
+
+
+
+
+is_indenting ()
+
+
+
+
+
+
+
Returns True
iff this Formatter is in pretty-printing
+indenting mode.
+
+
+ Source code in preserves/text.py
+ def is_indenting ( self ):
+ """Returns `True` iff this [Formatter][preserves.text.Formatter] is in pretty-printing
+ indenting mode."""
+ return self . indent_delta > 0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+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 True
, wrap each value and subvalue in an
+Annotated object.
+
+ False
+
+
+
+ parse_embedded
+
+
+ function accepting a Value
and returning a possibly-decoded form of that value
+suitable for placing into an Embedded object.
+
+ 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 input_buffer
+
+
+
+
+
+
+ Source code in preserves/text.py
+ 132
+133
+134
+135
+136
+137 def __init__ ( self , input_buffer = u '' , include_annotations = False , parse_embedded = lambda x : x ):
+ super ( Parser , self ) . __init__ ()
+ self . input_buffer = input_buffer
+ self . index = 0
+ self . include_annotations = include_annotations
+ self . parse_embedded = parse_embedded
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+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
+ def extend ( self , 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."""
+ self . input_buffer = self . input_buffer [ self . index :] + text
+ self . index = 0
+
+
+
+
+
+
+
+
+
+
+
+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
+ 320
+321
+322
+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
+384
+385
+386
+387 def next ( self ):
+ """Reads the next complete `Value` from the internal buffer, raising
+ [ShortPacket][preserves.error.ShortPacket] if too few bytes are available, or
+ [DecodeError][preserves.error.DecodeError] if the input is invalid somehow.
+
+ """
+ self . skip_whitespace ()
+ c = self . peek ()
+ if c == '"' :
+ self . skip ()
+ return self . wrap ( self . read_string ( '"' ))
+ if c == '|' :
+ self . skip ()
+ return self . wrap ( Symbol ( self . read_string ( '|' )))
+ if c in ';@' :
+ annotations = self . gather_annotations ()
+ v = self . next ()
+ if self . include_annotations :
+ v . annotations = annotations + v . annotations
+ return v
+ if c == ':' :
+ raise DecodeError ( 'Unexpected key/value separator between items' )
+ if c == '#' :
+ self . skip ()
+ c = self . nextchar ()
+ if c == 'f' : return self . wrap ( False )
+ if c == 't' : return self . wrap ( True )
+ if c == '{' : return self . wrap ( frozenset ( self . upto ( '}' )))
+ if c == '"' : return self . wrap ( self . read_literal_binary ())
+ if c == 'x' :
+ c = self . nextchar ()
+ if c == '"' : return self . wrap ( self . read_hex_binary ())
+ if c == 'f' : return self . wrap ( self . read_hex_float ( 4 ))
+ if c == 'd' : return self . wrap ( self . read_hex_float ( 8 ))
+ raise DecodeError ( 'Invalid #x syntax' )
+ if c == '[' : return self . wrap ( self . read_base64_binary ())
+ if c == '=' :
+ old_ann = self . include_annotations
+ self . include_annotations = True
+ bs_val = self . next ()
+ self . include_annotations = old_ann
+ if len ( bs_val . annotations ) > 0 :
+ raise DecodeError ( 'Annotations not permitted after #=' )
+ bs_val = bs_val . item
+ if not isinstance ( bs_val , bytes ):
+ raise DecodeError ( 'ByteString must follow #=' )
+ return self . wrap ( Decoder ( bs_val , include_annotations = self . include_annotations ) . next ())
+ if c == '!' :
+ if self . parse_embedded is None :
+ raise DecodeError ( 'No parse_embedded function supplied' )
+ return self . wrap ( Embedded ( self . parse_embedded ( self . next ())))
+ raise DecodeError ( 'Invalid # syntax' )
+ if c == '<' :
+ self . skip ()
+ vs = self . upto ( '>' )
+ if len ( vs ) == 0 :
+ raise DecodeError ( 'Missing record label' )
+ return self . wrap ( Record ( vs [ 0 ], vs [ 1 :]))
+ if c == '[' :
+ self . skip ()
+ return self . wrap ( self . upto ( ']' ))
+ if c == '{' :
+ self . skip ()
+ return self . wrap ( self . read_dictionary ())
+ if c in '>]}' :
+ raise DecodeError ( 'Unexpected ' + c )
+ self . skip ()
+ return self . wrap ( self . read_raw_symbol_or_number ([ c ]))
+
+
+
+
+
+
+
+
+
+
+
+try_next ()
+
+
+
+
+
+
+
Like next , but returns None
instead of raising
+ShortPacket .
+
+
+ Source code in preserves/text.py
+ 389
+390
+391
+392
+393
+394
+395
+396
+397 def try_next ( self ):
+ """Like [next][preserves.text.Parser.next], but returns `None` instead of raising
+ [ShortPacket][preserves.error.ShortPacket]."""
+ start = self . index
+ try :
+ return self . next ()
+ except ShortPacket :
+ self . index = start
+ return None
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+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
+ 408
+409
+410
+411
+412
+413
+414
+415
+416
+417 def parse ( text , ** kwargs ):
+ """Yields the first complete encoded value from `text`, passing `kwargs` through to the
+ [Parser][preserves.text.Parser] constructor. Raises exceptions as per
+ [next][preserves.text.Parser.next].
+
+ Args:
+ text (str): encoded data to decode
+
+ """
+ return Parser ( input_buffer = text , ** kwargs ) . next ()
+
+
+
+
+
+
+
+
+
+
+
+parse_with_annotations ( bs , ** kwargs )
+
+
+
+
+
+
+
Like parse , but supplying include_annotations=True
to the
+Parser constructor.
+
+
+ Source code in preserves/text.py
+ def parse_with_annotations ( bs , ** kwargs ):
+ """Like [parse][preserves.text.parse], but supplying `include_annotations=True` to the
+ [Parser][preserves.text.Parser] constructor."""
+ return Parser ( input_buffer = bs , include_annotations = True , ** kwargs ) . next ()
+
+
+
+
+
+
+
+
+
+
+
+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
+ 606
+607
+608
+609
+610
+611 def stringify ( v , ** kwargs ):
+ """Convert a single `Value` `v` to a string. Any supplied `kwargs` are passed on to the
+ underlying [Formatter][preserves.text.Formatter] constructor."""
+ e = Formatter ( ** kwargs )
+ e . append ( v )
+ return e . contents ()
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Last update:
+ March 16, 2023
+
+
+ Created:
+ March 16, 2023
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/python/0.18.1/values/index.html b/python/0.18.1/values/index.html
new file mode 100644
index 0000000..9954ce6
--- /dev/null
+++ b/python/0.18.1/values/index.html
@@ -0,0 +1,2551 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Representations of Values - Python Preserves
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Representations of Values
+Python's strings, byte strings, integers, booleans, and double-precision floats stand directly
+for their Preserves counterparts. Wrapper objects for Float and
+Symbol complete the suite of atomic types.
+Python's lists and tuples correspond to Preserves Sequence
s, and dicts and sets to
+Dictionary
and Set
values, respectively. Preserves Record
s are represented by
+Record objects. Finally, embedded values are represented by
+Embedded objects.
+
+
+
+
+
+
+
+
+
The preserves.values module implements the core representations of Preserves
+Value
s as Python values.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+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 Value
+
+
+ annotations
+
+ list[Value]
+
+ the annotations attached to self.item
+
+
+
+
+
+
+ Source code in preserves/values.py
+ def __init__ ( self , item ):
+ self . annotations = []
+ self . item = item
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+peel ()
+
+
+
+
+
+
+
Calls strip_annotations on self
with depth=1
.
+
+
+ Source code in preserves/values.py
+ def peel ( self ):
+ """Calls [strip_annotations][preserves.values.strip_annotations] on `self` with `depth=1`."""
+ return strip_annotations ( self , 1 )
+
+
+
+
+
+
+
+
+
+
+
+strip ( depth = inf )
+
+
+
+
+
+
+
Calls strip_annotations on self
and depth
.
+
+
+ Source code in preserves/values.py
+ def strip ( self , depth = inf ):
+ """Calls [strip_annotations][preserves.values.strip_annotations] on `self` and `depth`."""
+ return strip_annotations ( self , depth )
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+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 Value
, could be None
, could be anything!
+
+
+
+
+
+
+ Source code in preserves/values.py
+ def __init__ ( self , embeddedValue ):
+ self . embeddedValue = embeddedValue
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Float ( value )
+
+
+
+
+
+
+ Bases: object
+
+
+
Wrapper for treating a Python double-precision floating-point value as a
+single-precision (32-bit) float, from Preserves' perspective. (Python lacks native
+single-precision floating point support.)
+
>>> Float ( 3.45 )
+Float ( 3.45 )
+>>> import preserves
+>>> preserves . stringify ( Float ( 3.45 ))
+'3.45f'
+>>> preserves . stringify ( 3.45 )
+'3.45'
+>>> preserves . parse ( '3.45f' )
+Float ( 3.45 )
+>>> preserves . parse ( '3.45' )
+3.45
+>>> preserves . encode ( Float ( 3.45 ))
+b ' \x82 @ \\\xcc\xcd '
+>>> preserves . encode ( 3.45 )
+b ' \x83 @ \x0b\x99\x99\x99\x99\x99\x9a '
+
+
+
Attributes:
+
+
+
+ Name
+ Type
+ Description
+
+
+
+
+ value
+
+ float
+
+ the double-precision representation of intended single-precision value
+
+
+
+
+
+
+ Source code in preserves/values.py
+ def __init__ ( self , value ):
+ self . value = value
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+from_bytes ( bs )
+
+
+ staticmethod
+
+
+
+
+
+
+
+
Converts a 4-byte-long byte string to a 32-bit single-precision floating point value
+wrapped in a Float instance. Takes care to preserve the
+quiet/signalling bit-pattern of NaN values, unlike its struct.unpack('>f', ...)
+equivalent.
+
>>> Float . from_bytes ( b ' \x7f\x80\x00 {' )
+Float ( nan )
+>>> Float . from_bytes ( b ' \x7f\x80\x00 {' ) . to_bytes ()
+b ' \x7f\x80\x00 {'
+
+>>> struct . unpack ( '>f' , b ' \x7f\x80\x00 {' )[ 0 ]
+nan
+>>> Float ( struct . unpack ( '>f' , b ' \x7f\x80\x00 {' )[ 0 ]) . to_bytes ()
+b ' \x7f\xc0\x00 {'
+>>> struct . pack ( '>f' , struct . unpack ( '>f' , b ' \x7f\x80\x00 {' )[ 0 ])
+b ' \x7f\xc0\x00 {'
+
+
(Note well the difference between 7f80007b
and 7fc0007b
!)
+
+
+ Source code in preserves/values.py
+ 135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168 @staticmethod
+def from_bytes ( bs ):
+ """Converts a 4-byte-long byte string to a 32-bit single-precision floating point value
+ wrapped in a [Float][preserves.values.Float] instance. Takes care to preserve the
+ quiet/signalling bit-pattern of NaN values, unlike its `struct.unpack('>f', ...)`
+ equivalent.
+
+ ```python
+ >>> Float.from_bytes(b'\\x7f\\x80\\x00{')
+ Float(nan)
+ >>> Float.from_bytes(b'\\x7f\\x80\\x00{').to_bytes()
+ b'\\x7f\\x80\\x00{'
+
+ >>> struct.unpack('>f', b'\\x7f\\x80\\x00{')[0]
+ nan
+ >>> Float(struct.unpack('>f', b'\\x7f\\x80\\x00{')[0]).to_bytes()
+ b'\\x7f\\xc0\\x00{'
+ >>> struct.pack('>f', struct.unpack('>f', b'\\x7f\\x80\\x00{')[0])
+ b'\\x7f\\xc0\\x00{'
+
+ ```
+
+ (Note well the difference between `7f80007b` and `7fc0007b`!)
+
+ """
+ vf = struct . unpack ( '>I' , bs )[ 0 ]
+ if ( vf & 0x7f800000 ) == 0x7f800000 :
+ # NaN or inf. Preserve quiet/signalling bit by manually expanding to double-precision.
+ sign = vf >> 31
+ payload = vf & 0x007fffff
+ dbs = struct . pack ( '>Q' , ( sign << 63 ) | 0x7ff0000000000000 | ( payload << 29 ))
+ return Float ( struct . unpack ( '>d' , dbs )[ 0 ])
+ else :
+ return Float ( struct . unpack ( '>f' , bs )[ 0 ])
+
+
+
+
+
+
+
+
+
+
+
+to_bytes ()
+
+
+
+
+
+
+
Converts this 32-bit single-precision floating point value to its binary32 format,
+taking care to preserve the quiet/signalling bit-pattern of NaN values, unlike its
+struct.pack('>f', ...)
equivalent.
+
>>> Float . from_bytes ( b ' \x7f\x80\x00 {' )
+Float ( nan )
+>>> Float . from_bytes ( b ' \x7f\x80\x00 {' ) . to_bytes ()
+b ' \x7f\x80\x00 {'
+
+>>> struct . unpack ( '>f' , b ' \x7f\x80\x00 {' )[ 0 ]
+nan
+>>> Float ( struct . unpack ( '>f' , b ' \x7f\x80\x00 {' )[ 0 ]) . to_bytes ()
+b ' \x7f\xc0\x00 {'
+>>> struct . pack ( '>f' , struct . unpack ( '>f' , b ' \x7f\x80\x00 {' )[ 0 ])
+b ' \x7f\xc0\x00 {'
+
+
(Note well the difference between 7f80007b
and 7fc0007b
!)
+
+
+ Source code in preserves/values.py
+ 91
+ 92
+ 93
+ 94
+ 95
+ 96
+ 97
+ 98
+ 99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123 def to_bytes ( self ):
+ """Converts this 32-bit single-precision floating point value to its binary32 format,
+ taking care to preserve the quiet/signalling bit-pattern of NaN values, unlike its
+ `struct.pack('>f', ...)` equivalent.
+
+ ```python
+ >>> Float.from_bytes(b'\\x7f\\x80\\x00{')
+ Float(nan)
+ >>> Float.from_bytes(b'\\x7f\\x80\\x00{').to_bytes()
+ b'\\x7f\\x80\\x00{'
+
+ >>> struct.unpack('>f', b'\\x7f\\x80\\x00{')[0]
+ nan
+ >>> Float(struct.unpack('>f', b'\\x7f\\x80\\x00{')[0]).to_bytes()
+ b'\\x7f\\xc0\\x00{'
+ >>> struct.pack('>f', struct.unpack('>f', b'\\x7f\\x80\\x00{')[0])
+ b'\\x7f\\xc0\\x00{'
+
+ ```
+
+ (Note well the difference between `7f80007b` and `7fc0007b`!)
+
+ """
+
+ if math . isnan ( self . value ) or math . isinf ( self . value ):
+ dbs = struct . pack ( '>d' , self . value )
+ vd = struct . unpack ( '>Q' , dbs )[ 0 ]
+ sign = vd >> 63
+ payload = ( vd >> 29 ) & 0x007fffff
+ vf = ( sign << 31 ) | 0x7f800000 | payload
+ return struct . pack ( '>I' , vf )
+ else :
+ return struct . pack ( '>f' , self . value )
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+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
+ def __init__ ( self , * args , ** kwargs ):
+ if hasattr ( self , '__hash' ): raise TypeError ( 'Immutable' )
+ super ( ImmutableDict , self ) . __init__ ( * args , ** kwargs )
+ self . __hash = None
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+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
+ 482
+483
+484
+485
+486
+487
+488
+489
+490
+491
+492
+493
+494
+495
+496
+497
+498
+499
+500
+501
+502
+503
+504
+505
+506
+507
+508
+509
+510
+511
+512
+513
+514 @staticmethod
+def from_kvs ( kvs ):
+ """Constructs an [ImmutableDict][preserves.values.ImmutableDict] from a sequence of
+ alternating keys and values; compare to the
+ [ImmutableDict][preserves.values.ImmutableDict] constructor, which takes a sequence of
+ key-value pairs.
+
+ ```python
+ >>> 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
+
+ ```
+
+ """
+
+ i = iter ( kvs )
+ result = ImmutableDict ()
+ result_proxy = super ( ImmutableDict , result )
+ try :
+ while True :
+ k = next ( i )
+ try :
+ v = next ( i )
+ except StopIteration :
+ raise DecodeError ( "Missing dictionary value" )
+ result_proxy . __setitem__ ( k , v )
+ except StopIteration :
+ pass
+ return result
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+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 Record
's label
+
+ required
+
+
+
+ fields
+
+ iterable[Value]
+
+ the fields of the Record
+
+ required
+
+
+
+
+
+
Attributes:
+
+
+
+ Name
+ Type
+ Description
+
+
+
+
+ key
+
+ Value
+
+ the Record
's label
+
+
+ fields
+
+ tuple[Value]
+
+ the fields of the Record
+
+
+
+
+
+
+ Source code in preserves/values.py
+ def __init__ ( self , key , fields ):
+ self . key = key
+ self . fields = tuple ( fields )
+ self . __hash = None
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+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 Record
s
+
+ required
+
+
+
+ fieldNames
+
+ tuple[str] | list[str] | str
+
+ Names of the Record
's fields
+
+ required
+
+
+
+
+
+
+ Source code in preserves/values.py
+ 321
+322
+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
+384
+385
+386
+387
+388
+389
+390
+391
+392
+393
+394
+395
+396
+397
+398
+399
+400 @staticmethod
+def makeBasicConstructor ( label , fieldNames ):
+ """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][preserves.values.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][preserves.values.RecordConstructorInfo] object, an `isClassOf`
+ attribute holding a unary function that returns `True` iff its argument is a
+ [Record][preserves.values.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.
+
+ ```python
+ >>> 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
+
+ ```
+
+ Args:
+ label (Value): Label to use for constructed/matched `Record`s
+ fieldNames (tuple[str] | list[str] | str): Names of the `Record`'s fields
+
+ """
+ if type ( fieldNames ) == str :
+ fieldNames = fieldNames . split ()
+ arity = len ( fieldNames )
+ def ctor ( * fields ):
+ if len ( fields ) != arity :
+ raise Exception ( "Record: cannot instantiate %r expecting %d fields with %d fields" % (
+ label ,
+ arity ,
+ len ( fields )))
+ return Record ( label , fields )
+ ctor . constructorInfo = RecordConstructorInfo ( label , arity )
+ ctor . isClassOf = lambda v : \
+ isinstance ( v , Record ) and v . key == label and len ( v . fields ) == arity
+ def ensureClassOf ( v ):
+ if not ctor . isClassOf ( v ):
+ raise TypeError ( "Record: expected %r / %d , got %r " % ( label , arity , v ))
+ return v
+ ctor . ensureClassOf = ensureClassOf
+ for fieldIndex in range ( len ( fieldNames )):
+ fieldName = fieldNames [ fieldIndex ]
+ # Stupid python scoping bites again
+ def getter ( fieldIndex ):
+ return lambda v : ensureClassOf ( v )[ fieldIndex ]
+ setattr ( ctor , '_' + fieldName , getter ( fieldIndex ))
+ return ctor
+
+
+
+
+
+
+
+
+
+
+
+makeConstructor ( labelSymbolText , fieldNames )
+
+
+ staticmethod
+
+
+
+
+
+
+
+
Equivalent to Record.makeBasicConstructor(Symbol(labelSymbolText), fieldNames)
.
+
+
+ Deprecated
+ Use preserves.schema definitions instead.
+
+
+ Source code in preserves/values.py
+ 311
+312
+313
+314
+315
+316
+317
+318
+319 @staticmethod
+def makeConstructor ( labelSymbolText , fieldNames ):
+ """
+ Equivalent to `Record.makeBasicConstructor(Symbol(labelSymbolText), fieldNames)`.
+
+ Deprecated:
+ Use [preserves.schema][] definitions instead.
+ """
+ return Record . makeBasicConstructor ( Symbol ( labelSymbolText ), fieldNames )
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+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 Record
s
+
+
+ arity
+
+ int
+
+ the number of fields in matching Record
s
+
+
+
+
+
+
+ Source code in preserves/values.py
+ def __init__ ( self , key , arity ):
+ self . key = key
+ self . arity = arity
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Symbol ( name )
+
+
+
+
+
+
+ Bases: object
+
+
+
Representation of Preserves Symbol
s.
+
>>> Symbol ( 'xyz' )
+#xyz
+>>> Symbol ( 'xyz' ) . name
+'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 name
is used as the name
for the new Symbol.
+
+
+
+
+
+
+ Source code in preserves/values.py
+ def __init__ ( self , name ):
+ self . name = name . name if isinstance ( name , Symbol ) else name
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+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
+ 664
+665
+666
+667
+668
+669
+670
+671
+672
+673
+674
+675
+676
+677
+678
+679
+680
+681 def annotate ( v , * anns ):
+ """Wraps `v` in an [Annotated][preserves.values.Annotated] object, if it isn't already
+ wrapped, and appends each of the `anns` to the [Annotated][preserves.values.Annotated]'s
+ `annotations` sequence. NOTE: Does not recursively ensure that any parts of the argument
+ `v` are themselves wrapped in [Annotated][preserves.values.Annotated] objects!
+
+ ```python
+ >>> import preserves
+ >>> print(preserves.stringify(annotate(123, "A comment", "Another comment")))
+ @"A comment" @"Another comment" 123
+
+ ```
+ """
+ if not is_annotated ( v ):
+ v = Annotated ( v )
+ for a in anns :
+ v . annotations . append ( a )
+ return v
+
+
+
+
+
+
+
+
+
+
+
+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 def cmp_floats ( a , b ):
+ """Implements the `totalOrder` predicate defined in section 5.10 of [IEEE Std
+ 754-2008](https://dx.doi.org/10.1109/IEEESTD.2008.4610935).
+
+ """
+ a = float_to_int ( a )
+ b = float_to_int ( b )
+ if a & 0x8000000000000000 : a = a ^ 0x7fffffffffffffff
+ if b & 0x8000000000000000 : b = b ^ 0x7fffffffffffffff
+ return a - b
+
+
+
+
+
+
+
+
+
+
+
+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
+ 516
+517
+518
+519
+520
+521
+522
+523
+524
+525
+526
+527
+528 def dict_kvs ( d ):
+ """Generator function yielding a sequence of alternating keys and values from `d`. In some
+ sense the inverse of [ImmutableDict.from_kvs][preserves.values.ImmutableDict.from_kvs].
+
+ ```python
+ >>> list(dict_kvs({'a': 1, 'b': 2}))
+ ['a', 1, 'b', 2]
+
+ ```
+ """
+ for k in d :
+ yield k
+ yield d [ k ]
+
+
+
+
+
+
+
+
+
+
+
+is_annotated ( v )
+
+
+
+
+
+
+
True
iff v
is an instance of Annotated .
+
+
+ Source code in preserves/values.py
+ def is_annotated ( v ):
+ """`True` iff `v` is an instance of [Annotated][preserves.values.Annotated]."""
+ return isinstance ( v , Annotated )
+
+
+
+
+
+
+
+
+
+
+
+preserve ( v )
+
+
+
+
+
+
+
Converts v
to a representation of a Preserves Value
by (repeatedly) setting
+
+
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 def preserve ( v ):
+ """Converts `v` to a representation of a Preserves `Value` by (repeatedly) setting
+
+ ```python
+ v = v.__preserve__()
+ ```
+
+ while `v` has a `__preserve__` method. Parsed [Schema][preserves.schema]
+ values are able to render themselves to their serialized representations this way.
+
+ """
+ while hasattr ( v , '__preserve__' ):
+ v = v . __preserve__ ()
+ return v
+
+
+
+
+
+
+
+
+
+
+
+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
+ 618
+619
+620
+621
+622
+623
+624
+625
+626
+627
+628
+629
+630
+631
+632
+633
+634
+635
+636
+637
+638
+639
+640
+641
+642
+643
+644
+645
+646
+647
+648
+649
+650
+651
+652
+653
+654
+655
+656
+657
+658
+659
+660
+661
+662 def strip_annotations ( v , depth = inf ):
+ """Exposes `depth` layers of raw structure of
+ potentially-[Annotated][preserves.values.Annotated] `Value`s. If `depth==0` or `v` is not
+ [Annotated][preserves.values.Annotated], just returns `v`. Otherwise, descends recursively
+ into the structure of `v.item`.
+
+ ```python
+ >>> 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]
+
+ ```
+ """
+
+ if depth == 0 : return v
+ if not is_annotated ( v ): return v
+
+ next_depth = depth - 1
+ def walk ( v ):
+ return strip_annotations ( v , next_depth )
+
+ v = v . item
+ if isinstance ( v , Record ):
+ return Record ( strip_annotations ( v . key , depth ), tuple ( walk ( f ) for f in v . fields ))
+ elif isinstance ( v , list ):
+ return tuple ( walk ( f ) for f in v )
+ elif isinstance ( v , tuple ):
+ return tuple ( walk ( f ) for f in v )
+ elif isinstance ( v , set ):
+ return frozenset ( walk ( f ) for f in v )
+ elif isinstance ( v , frozenset ):
+ return frozenset ( walk ( f ) for f in v )
+ elif isinstance ( v , dict ):
+ return ImmutableDict . from_kvs ( walk ( f ) for f in dict_kvs ( v ))
+ elif is_annotated ( v ):
+ raise ValueError ( 'Improper annotation structure' )
+ else :
+ return v
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Last update:
+ March 16, 2023
+
+
+ Created:
+ March 16, 2023
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/python/index.md b/python/index.md
new file mode 100644
index 0000000..a4e20b0
--- /dev/null
+++ b/python/index.md
@@ -0,0 +1,12 @@
+---
+title: 'Python Implementation Documentation'
+---
+
+The [latest **released** version of the documentation is {% for v in site.data.python-versions %}{% if v.aliases[0] == 'latest' %}{{ v.title }}{% endif %}{% endfor %}](latest/).
+
+Other available versions:
+
+{% for v in site.data.python-versions %}
+ - [{{v.title}}]({{v.version}}/)
+{% endfor %}
+
diff --git a/python/latest b/python/latest
new file mode 120000
index 0000000..6b2d58c
--- /dev/null
+++ b/python/latest
@@ -0,0 +1 @@
+0.18.1
\ No newline at end of file
diff --git a/python/versions.json b/python/versions.json
new file mode 100644
index 0000000..0d4c150
--- /dev/null
+++ b/python/versions.json
@@ -0,0 +1,4 @@
+---
+layout: null
+---
+{{ site.data.python-versions | jsonify }}
diff --git a/schema/schema.bin b/schema/schema.bin
index a248064..408302c 100644
--- a/schema/schema.bin
+++ b/schema/schema.bin
@@ -5,4 +5,4 @@ ByteString
ByteString„„µ±Symbol´³lit³Symbol„„„„³
Definition´³orµµ±or´³rec´³lit³or„´³tupleµ´³tuplePrefixµ´³named³pattern0´³refµ„³NamedAlternative„„´³named³pattern1´³refµ„³NamedAlternative„„„´³named³patternN´³seqof´³refµ„³NamedAlternative„„„„„„„„µ±and´³rec´³lit³and„´³tupleµ´³tuplePrefixµ´³named³pattern0´³refµ„³NamedPattern„„´³named³pattern1´³refµ„³NamedPattern„„„´³named³patternN´³seqof´³refµ„³NamedPattern„„„„„„„„µ±Pattern´³refµ„³Pattern„„„„³
ModulePath´³seqof´³atom³Symbol„„³Definitions´³dictof´³atom³Symbol„´³refµ„³
-Definition„„³NamedPattern´³orµµ±named´³refµ„³Binding„„µ± anonymous´³refµ„³Pattern„„„„³
SimplePattern´³orµµ±any´³lit³any„„µ±atom´³rec´³lit³atom„´³tupleµ´³named³atomKind´³refµ„³AtomKind„„„„„„µ±embedded´³rec´³lit³embedded„´³tupleµ´³named³ interface´³refµ„³
SimplePattern„„„„„„µ±lit´³rec´³lit³lit„´³tupleµ´³named³value³any„„„„„µ±seqof´³rec´³lit³seqof„´³tupleµ´³named³pattern´³refµ„³
SimplePattern„„„„„„µ±setof´³rec´³lit³setof„´³tupleµ´³named³pattern´³refµ„³
SimplePattern„„„„„„µ±dictof´³rec´³lit³dictof„´³tupleµ´³named³key´³refµ„³
SimplePattern„„´³named³value´³refµ„³
SimplePattern„„„„„„µ±Ref´³refµ„³Ref„„„„³CompoundPattern´³orµµ±rec´³rec´³lit³rec„´³tupleµ´³named³label´³refµ„³NamedPattern„„´³named³fields´³refµ„³NamedPattern„„„„„„µ±tuple´³rec´³lit³tuple„´³tupleµ´³named³patterns´³seqof´³refµ„³NamedPattern„„„„„„„µ±tuplePrefix´³rec´³lit³tuplePrefix„´³tupleµ´³named³fixed´³seqof´³refµ„³NamedPattern„„„´³named³variable´³refµ„³NamedSimplePattern„„„„„„µ±dict´³rec´³lit³dict„´³tupleµ´³named³entries´³refµ„³DictionaryEntries„„„„„„„„³EmbeddedTypeName´³orµµ±Ref´³refµ„³Ref„„µ±false´³lit€„„„„³NamedAlternative´³tupleµ´³named³variantLabel´³atom³String„„´³named³pattern´³refµ„³Pattern„„„„³DictionaryEntries´³dictof³any´³refµ„³NamedSimplePattern„„³NamedSimplePattern´³orµµ±named´³refµ„³Binding„„µ± anonymous´³refµ„³
SimplePattern„„„„„³embeddedType€„„
\ No newline at end of file
+Definition„„³NamedPattern´³orµµ±named´³refµ„³Binding„„µ± anonymous´³refµ„³Pattern„„„„³
SimplePattern´³orµµ±any´³lit³any„„µ±atom´³rec´³lit³atom„´³tupleµ´³named³atomKind´³refµ„³AtomKind„„„„„„µ±embedded´³rec´³lit³embedded„´³tupleµ´³named³ interface´³refµ„³
SimplePattern„„„„„„µ±lit´³rec´³lit³lit„´³tupleµ´³named³value³any„„„„„µ±seqof´³rec´³lit³seqof„´³tupleµ´³named³pattern´³refµ„³
SimplePattern„„„„„„µ±setof´³rec´³lit³setof„´³tupleµ´³named³pattern´³refµ„³
SimplePattern„„„„„„µ±dictof´³rec´³lit³dictof„´³tupleµ´³named³key´³refµ„³
SimplePattern„„´³named³value´³refµ„³
SimplePattern„„„„„„µ±Ref´³refµ„³Ref„„„„³CompoundPattern´³orµµ±rec´³rec´³lit³rec„´³tupleµ´³named³label´³refµ„³NamedPattern„„´³named³fields´³refµ„³NamedPattern„„„„„„µ±tuple´³rec´³lit³tuple„´³tupleµ´³named³patterns´³seqof´³refµ„³NamedPattern„„„„„„„µ±tuplePrefix´³rec´³lit³tuplePrefix„´³tupleµ´³named³fixed´³seqof´³refµ„³NamedPattern„„„´³named³variable´³refµ„³NamedSimplePattern„„„„„„µ±dict´³rec´³lit³dict„´³tupleµ´³named³entries´³refµ„³DictionaryEntries„„„„„„„„³EmbeddedTypeName´³orµµ±false´³lit€„„µ±Ref´³refµ„³Ref„„„„³NamedAlternative´³tupleµ´³named³variantLabel´³atom³String„„´³named³pattern´³refµ„³Pattern„„„„³DictionaryEntries´³dictof³any´³refµ„³NamedSimplePattern„„³NamedSimplePattern´³orµµ±named´³refµ„³Binding„„µ± anonymous´³refµ„³
SimplePattern„„„„„³embeddedType€„„
\ No newline at end of file
diff --git a/schema/schema.prs b/schema/schema.prs
index 5db39dc..db50618 100644
--- a/schema/schema.prs
+++ b/schema/schema.prs
@@ -17,7 +17,7 @@ Schema =
## Notes