jztools.reference_sequence_v0#

Motivation#

Getting or setting nested object members often requires a sequence of attribute and key references such as

B.contents['obj'][2].value

This access pattern can be summarized by the following reference sequence:

  1. Get attribute contents.

  2. Get entry with key 'obj'

  3. Get entry with key 2.

  4. Get attribute value.

This same reference sequence might need to be transferred (applied) to a variety of current and future objects, or it might need sent as a parameter to an algorithm. Being able to save serialize it in human-readable form for future use is also relevant.

Usage#

This module presents a natural way to define transferable, serializable reference sequences. Such sequences can be defined by applying the same pattern (e.g., everything after B. in the above exampe) to a RefSeq object:

rs = RefSeq().contents['obj'][2].value

Setting or getting a value for a given object can then be carried out using the __call__() method:

value = rs(B)
rs(B, new_value)
from jztools.reference_sequence import RefSeq
# Using reference sequences
arr = {'key1':list(range(20))}

orig = arr['key1'][1:10][0]
with_syntax1 = RefSeq()['key1'][1:10][0](arr)
with_syntax2 = RefSeq(['[key1]', slice(1,10), 0])(arr)

assert with_syntax1 == orig
assert with_syntax1 == with_syntax2

Slice sequence objects can be serialized in a human-readable format:

from xerializer import Serializer

# Serialization:
srlzr = Serializer()
rs = RefSeq()['key1'][1:10][0]
json_string = srlzr.serialize(rs)

print(json_string)

deserialized_rs = srlzr.deserialize(json_string)
assert deserialized_rs(arr) == rs(arr)
{"__type__": "RefSeq", "value": ["[key1]", {"__type__": "slice", "start": 1, "stop": 10}, 0]}

Reference sequences can also contain references to attributes that can be accessed in a natural way:

class A:
  value = 'target'

class B:
  contents = {'obj': [0, 1, A]}

# Accessing  B.contents['obj'][2].value
rs = RefSeq().contents['obj'][2].value
assert rs(B) == 'target'

This approach to attribute access will pose a problem when referenced attribute and object attribute names collide. To minimize this occurrence, all object attributes are pre- or suffixed with _. Nonetheless, it is safer to use the a_() method:

rs = RefSeq().a_('contents')['obj'][2].a_('value')
assert rs(B) == 'target'

Reference sequences with attribute references can also be serialized. String keys and attributes are differentiated by surrounding keys with square brackets in the serialized representation:

print(srlzr.serialize(rs))
{"__type__": "RefSeq", "value": ["contents", "[obj]", 2, "value"]}

A similar syntax can be used to initialize reference sequences directly:

rs = RefSeq(["contents", "[obj]", 2, "value"])
assert rs(B) == 'target'

In this syntax, all non-string and bracket-enclosed string values are treated as keys, while non-bracket-enclosed strings are treated as attributes. Alternatively, the attribute and key helper functions a_() and k_() can be used to explicitly indicate intent for some or all entries:

from jztools.reference_sequence import a_, k_

rs = RefSeq([a_("contents"), k_("obj"), k_(2), a_("value")])
assert rs(B) == 'target'

To summarize, all the following are equivalent:

rs1 = RefSeq().contents['obj'][2].value
rs2 = RefSeq().a_('contents').k_('obj').k_(2).a_('value')
rs3 = RefSeq([a_("contents"), k_("obj"), k_(2), a_("value")])

assert rs1(B) == 'target'
assert rs2(B) == 'target'
assert rs3(B) == 'target'

Note that any serializable object can be used as a key, including string-key dictionaries and slices.

Todo

  • Re-organize, add ..rubric:: headers.

  • Add set_ examples.

  • Add exampes for slice and dictionary keys, including serialization.

Warning

Care needs to be taken when using RefSeq objects since mis-spelled member names will not raise errors but rather return a new object with the misspelled member appended as an attribute reference. This is mitigated by the fact that only two members (RefSeq.k_() and RefSeq.a_()) should be called directly by the user. Note also that all RefSeq members begin or end with _.

Warning

Reference sequences should only be initialized from trusted sources, and in particular those initialized from user input can be harmful.

Classes

RefSeq([value])

Serializable, transferable reference sequence.

a_(value)

Specifies that the provided value is an attribute retrieved / set with getattr / setattr (obj.attribute syntax).

k_(value)

Specifies that the provided value is a key retrieved / set with __getitem__ / __setitem__ (obj[key] syntax).

class jztools.reference_sequence_v0.RefSeq(value=None)#

Bases: object

Serializable, transferable reference sequence.

k_(key: Any)#

Appends a key reference - same operation as __getitem__(). Provided for consistency with a_().

Keys can be appended to the reference sequence rs using rs = rs[new_key] or rs = rs.k_(new_key).

Keys can be any object.

__getitem__(key: Any)#

Alias to k_().

a_(name: str)#

Appends an attribute reference. See also __getattribute__().

Attribute 'attr' can be appended to the reference sequence rs using rs = rs.attr (when there is no conflict with RefSeq members) or rs = rs.a_('attr').

Attributes need to be valid python identifiers.

__call__(*args)#

Sets or gets the obj at the reference sequence:

  • __call__(obj) Gets the value of obj at the reference sequence.

  • __call__(obj, val) Sets the value of obj at the reference sequence to val.

class jztools.reference_sequence_v0.k_(value)#

Bases: _Ref

Specifies that the provided value is a key retrieved / set with __getitem__ / __setitem__ (obj[key] syntax). When serializing this key, strings will be wrapped in square brackets and other types will be passed through.

class jztools.reference_sequence_v0.a_(value)#

Bases: _Ref

Specifies that the provided value is an attribute retrieved / set with getattr / setattr (obj.attribute syntax).