Basic bytes interface

Note

This assumes familiarity with Ghid and HGXLink objects.

Hypergolix objects

class Obj(state, api_id, dynamic, private, *, hgxlink, ipc_manager, _legroom, ghid=None, binder=None, callback=None)

New in version 0.1.

The basic Hypergolix object. Create it using HGXLink.get() or HGXLink.new(); these objects are not intended to be created directly. If you create the object directly, it won’t receive state updates from upstream.

All Hypergolix objects have a unique, cryptographic-hash-based address (the Ghid) and a binder (roughly speaking, the public key fingerprint of the object’s creator). They may be dynamic or static.

All Hypergolix objects have a so-called “API ID” – an arbitrary, unique, implicit identifier for the structure of the object. In traditional web service parlance, it’s somewhere between an endpoint and a schema, which (unfortunately) is a pretty terrible analogy.

Hypergolix objects persist nonlocally until explicitly deleted through one of the delete() methods.

Parameters:
  • hgxlink (HGXLink) – The currently-active HGXLink object used to connect to the Hypergolix service.
  • state – The state of the object.
  • api_id (hgx.utils.ApiID) – The API ID for the object (see above).
  • dynamic (bool) – A value of True will result in a dynamic object, whose state may be updated. False will result in a static object with immutable state.
  • private (bool) – Declare the object as available to this application only (as opposed to any application for the logged-in Hypergolix user). Setting this to True requires an HGXLink.app_token.
  • ghid (Ghid) – The Ghid address of the object.
  • binder (Ghid) – The Ghid of the object’s binder.
Returns:

The Obj instance, with state declared, but not initialized with Hypergolix.

Warning

Hypergolix objects are not intended to be created directly. Instead, they should always be created through the HGXLink, using one of its HGXLink.new() or HGXLink.get() methods.

Creating the objects directly will result in them being unavailable for automatic updates, and forced to poll through their sync() methods. Furthermore, their binder and ghid properties will be unavailable until after the first call to push().

>>> obj = hgxlink.new_threadsafe(
...     cls = hgx.Obj,
...     state = b'Hello world!'
... )
>>> obj
<Obj with state b'Hello world!' at Ghid('bf3dR')>
state

The read-write value of the object itself. This will be serialized and uploaded through Hypergolix upon any call to push().

Warning

Updating state will not update Hypergolix. To upload the change, you must explicitly call push().

Return type:bytes
>>> obj
<Obj with state b'Hello world!' at Ghid('bf3dR')>
>>> obj.state
b'Hello world!'
>>> # This change won't yet exist anywhere else
>>> obj.state = b'Hello Hypergolix!'
>>> obj
<Obj with state b'Hello Hypergolix!' at Ghid('bf3dR')>
ghid

The read-only address for the object.

Return Ghid:read-only address.
>>> obj
<Obj with state b'Hello world!' at Ghid('bf3dR')>
>>> obj.ghid
Ghid(algo=1, address=b'\xb7\xf7u\x13Y\x00\xf8k\xa9\x8fw\xab\x84>\xc0m\x10\xbc\xf9\xcf\xfd\xa9\xd5\xf1w\xda\xb9S%\x14\xeb\xc0\x81\xe0\xb9%U\x9e]5\x1f\xb4\x9e\xad\x99\x8b\xde\x1fK-\x19\xa0\t\xd23}\xc4\xaa\xe2M=E\xe8\xc9')
>>> str(obj.ghid)
Ghid('bf3dR')
api_id

The read-only API ID for the object.

Return bytes:read-only API ID.
>>> obj
<Obj with state b'Hello world!' at Ghid('bf3dR')>
>>> obj.api_id
ApiID(b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01')
private

Whether or not the object is restricted to this application only (see above). Read-only.

Return bool:read-only privacy setting.
>>> obj
<Obj with state b'Hello world!' at Ghid('bf3dR')>
>>> obj.private
False
dynamic

Is the object dynamic (True) or static (False)? Read-only.

Return bool:read-only dynamic/static status.
>>> obj
<Obj with state b'Hello world!' at Ghid('bf3dR')>
>>> obj.dynamic
True
binder

The read-only binder of the object. Roughly speaking, the public key fingerprint of its creator (see above).

Return Ghid:read-only binder.
>>> obj
<Obj with state b'Hello world!' at Ghid('bf3dR')>
>>> obj.binder
Ghid(algo=1, address=b'\xf8A\xd6`\x11\xedN\x14\xab\xe5"\x16\x0fs\n\x02\x08\xa1\xca\xa6\xc6$\xa7D\xf7\xb9\xa2\xbc\xc0\x8c\xf3\xe1\xefP\xa1]dE\x87\tw\xb1\xc8\x003\xac>\x89U\xdd\xcc\xb5X\x1d\xcf\x8c\x0e\x0e\x03\x7f\x1e]IQ')
>>> str(obj.binder)
Ghid('fhB1m')
callback

Gets, sets, or deletes an update callback. This will be awaited every time the object receives an upstream update, but it will not be called when the application itself calls push(). The callback will be passed a single argument: the object itself. The object’s state will already have been updated to the new upstream state before the callback is invoked.

Because they are running independently of your actual application, and are called by the HGXLink itself, any exceptions raised by the callback will be swallowed and logged.

Parameters:callback – An awaitable callback.

Warning

For threadsafe or loopsafe usage, this callback must be appropriately wrapped using HGXLink.wrap_threadsafe() or HGXLink.wrap_loopsafe() before setting it as a callback.

Setting the callback:

>>> obj
<Obj with state b'Hello world!' at Ghid('bf3dR')>
>>> async def handler(obj):
...     print('Updated! ' + repr(obj))
...
>>> obj.callback = handler

The resulting call:

>>>
Updated! <Obj with state b'Hello Hypergolix!' at Ghid('bf3dR')>
__eq__(other)

Compares two Hypergolix objects. The result will be True if (and only if) all of the following conditions are satisfied:

  1. They both have a ghid (else, raise TypeError)
  2. The ghid compares equally
  3. They both have a state (else, raise TypeError)
  4. The state compares equally
  5. They both have a binder (else, raise TypeError)
  6. The binder compares equally
Parameters:other – The Hypergolix object to compare with.
Return bool:The comparison result.
Raises:TypeError – when attempting to compare with a non-Hypergolix object.
>>> obj
<Obj with state b'Hello world!' at Ghid('bf3dR')>
>>> obj2
<Obj with state b'Hello world!' at Ghid('WFUmW')>
>>> obj == obj2
False

Note

The following methods each expose three equivalent APIs:

  1. an internal API (ex: push()).

    Warning

    This method must only be awaited from within the internal HGXLink event loop, or it may break the HGXLink, and will likely fail to work.

    This method is a coroutine. Example usage:

    await obj.push()
    
  2. a threadsafe API, denoted by the _threadsafe suffix (ex: push_threadsafe()).

    Warning

    This method must not be called from within the internal HGXLink event loop, or it will deadlock.

    This method is a standard, blocking, synchronous method. Example usage:

    obj.push_threadsafe()
    
  3. a loopsafe API, denoted by the _loopsafe suffix (ex: push_loopsafe()).

    Warning

    This method must not be awaited from within the internal HGXLink event loop, or it will deadlock.

    This method is a coroutine that may be awaited from your own external event loop. Example usage:

    await obj.push_loopsafe()
    
recast(cls)
recast_threadsafe(cls)
recast_loopsafe(cls)

Converts between Hypergolix object types. Returns a new copy of the current Hypergolix object, converted to type cls.

Parameters:cls – the type of object to recast into.
Returns:a new version of obj, in the current class.

Warning

Recasting an object renders the previous Python object inoperable and dead. It will cease to receive updates from the HGXLink, and subsequent manipulation of the old object is likely to cause bugs with the new object as well.

>>> obj
<Obj with state b'Hello world!' at Ghid('bf3dR')>
>>> obj.recast_threadsafe(hgx.JsonObj)
<JsonObj with state b'Hello world!' at Ghid('bf3dR')>
push()
push_threadsafe()
push_loopsafe()

Notifies the Hypergolix service (through the HGXLink) of updates to the object. Must be called explicitly for any changes to be available outside of the current Python session.

Raises:
  • hypergolix.exceptions.IPCError – if unsuccessful.
  • hypergolix.exceptions.LocallyImmutable – if the object is static, or if the current Hypergolix user did not create it.
  • hypergolix.exceptions.DeadObject – if the object is unavailable, for example, as a result of a discard() call.
>>> obj
<Obj with state b'Hello world!' at Ghid('bf3dR')>
>>> # This state is unknown everywhere except in current memory
>>> obj.state = b'Foo'
>>> obj.state = b'Bar'
>>> # Hypergolix now has no record of b'Foo' ever existing.
>>> obj.push_threadsafe()
>>> # The new state b'Bar' is now known to Hypergolix.
sync()
sync_threadsafe()
sync_loopsafe()

Manually initiates an update through Hypergolix. So long as you create and retrieve objects through the HGXLink, you will not need these methods.

Raises:
  • hypergolix.exceptions.IPCError – if unsuccessful.
  • hypergolix.exceptions.DeadObject – if the object is unavailable, for example, as a result of a discard() call.
>>> obj
<Obj with state b'Hello world!' at Ghid('bf3dR')>
>>> obj.sync_threadsafe()
share(recipient)
share_threadsafe(recipient)
share_loopsafe(recipient)

Shares the Obj instance with recipient. The recipient will receive a read-only copy of the object, which will automatically update upon any local changes that are push()ed upstream.

Parameters:

recipient (Ghid) – The public key fingerprint “identity” of the entity to share with.

Raises:
  • hypergolix.exceptions.IPCError – if immediately unsuccessful.
  • hypergolix.exceptions.DeadObject – if the object is unavailable, for example, as a result of a discard() call.
  • hypergolix.exceptions.Unsharable – if the object is private.

Note

Successful sharing does not imply successful receipt. The recipient could ignore the share, be temporarily unavailable, etc.

Note

In order to actually receive the object, the recipient must have a share handler defined for the api_id of the object.

>>> obj
<Obj with state b'Hello world!' at Ghid('bf3dR')>
>>> bob = hgx.Ghid.from_str('AfhB1mAR7U4Uq-UiFg9zCgIIocqmxiSnRPe5orzAjPPh71ChXWRFhwl3scgAM6w-iVXdzLVYHc-MDg4Dfx5dSVE=')
>>> obj.share_threadsafe(bob)
freeze()
freeze_threadsafe()
freeze_loopsafe()

Creates a static “snapshot” of a dynamic object. This new static object will be available at its own dedicated address.

Returns:

a frozen copy of the Obj (or subclass) instance. The class of the new instance will match the class of the original.

Raises:
  • hypergolix.exceptions.IPCError – if unsuccessful.
  • hypergolix.exceptions.LocallyImmutable – if the object is static.
  • hypergolix.exceptions.DeadObject – if the object is unavailable, for example, as a result of a discard() call.
>>> obj
<Obj with state b'Hello world!' at Ghid('bf3dR')>
>>> obj.dynamic
True
>>> frozen = obj.freeze_threadsafe()
>>> frozen
<Obj with state b'hello world' at Ghid('RS48N')>
>>> frozen.dynamic
False
hold()
hold_threadsafe()
hold_loopsafe()

Creates a separate binding to the object, preventing its deletion. This does not necessarily prevent other applications at the currently-logged-in Hypergolix user session from removing the object.

Raises:
  • hypergolix.exceptions.IPCError – if unsuccessful.
  • hypergolix.exceptions.DeadObject – if the object is unavailable, for example, as a result of a discard() call.
>>> obj
<Obj with state b'Hello world!' at Ghid('bf3dR')>
>>> obj.hold_threadsafe()
discard()
discard_threadsafe()
discard_loopsafe()

Notifies the Hypergolix service that the application is no longer interested in the object, but does not delete it. This renders the object inoperable and dead, preventing most future operations. However, a new copy of the object can still be retrieved through any of the HGXLink.get() methods.

Raises:
  • hypergolix.exceptions.IPCError – if unsuccessful.
  • hypergolix.exceptions.DeadObject – if the object is unavailable, for example, as a result of a discard() call.
>>> obj
<Obj with state b'Hello world!' at Ghid('bf3dR')>
>>> obj.discard_threadsafe()
delete()
delete_threadsafe()
delete_loopsafe()

Attempts to permanently delete the object. If successful, it will be inoperable and dead. It will also be removed from Hypergolix and made unavailable to other applications, as well as unavailable to any recipients of an share() call, unless they have called hold().

Raises:
  • hypergolix.exceptions.IPCError – if unsuccessful.
  • hypergolix.exceptions.DeadObject – if the object is unavailable, for example, as a result of a discard() call.
>>> obj
<Obj with state b'Hello world!' at Ghid('bf3dR')>
>>> obj.delete_threadsafe()

Hypergolix proxies

class Proxy(state, api_id, dynamic, private, *, hgxlink, ipc_manager, _legroom, ghid=None, binder=None, callback=None)

New in version 0.1.

The Hypergolix proxy, partly inspired by weakref.proxy, is a mechanism by which almost any existing Python object can be encapsulated within a Hypergolix-aware wrapper. In every other way, the proxy behaves exactly like the original object. This is accomplished by overloading the Proxy.__getattr__(), Proxy.__setattr__(), and Proxy.__delattr__() methods.

Except where otherwise noted, a Hypergolix Proxy exposes the same API as an Obj, except that the Hypergolix methods are given an hgx_ prefix to avoid namespace collisions. For example, Obj.push() becomes Proxy.hgx_push(), and so on.

A proxy is hashable if its hgx_ghid is defined, but unhashable otherwise. Note, however, that this hash has nothing to do with the proxied object. Also note that isinstance(proxy_obj, collections.Hashable) will always identify an Proxy as hashable, regardless of its actual runtime behavior.

Parameters:
  • hgxlink (HGXLink) – The currently-active HGXLink object used to connect to the Hypergolix service.
  • state – The state of the object. For objects using the default (ie noop) serialization, this must be bytes-like. For subclasses of Obj, this can be anything supported by the subclass’ serialization strategy.
  • api_id (bytes) – The API ID for the object (see above). Should be a bytes-like object of length 64.
  • dynamic (bool) – A value of True will result in a dynamic object, whose state may be update. False will result in a static object with immutable state.
  • private (bool) – Declare the object as available to this application only (as opposed to any application for the logged-in Hypergolix user). Setting this to True requires an HGXLink.app_token.
  • ghid (Ghid) – The Ghid of the object. Used to instantiate a preexisting object.
  • binder (Ghid) – The Ghid of the object’s binder. Used to instantiate a preexisting object.
Returns:

The Obj instance, with state declared, but not initialized with Hypergolix.

Warning

As with Obj objects, Proxy objects are not intended to be created directly.

Note

Support for Python special methods (aka “magic methods”, “dunder methods”, etc) is provided. However, due to implementation details in Python itself, this is accomplished by explicitly passing all possible __dunder__ methods used by Python to the proxied object.

This has the result that IDEs will present a very long list of available methods for Proxy objects, even if these methods are not, in fact, available. However, the built-in dir() command should still return a list limited to the methods actually supported by the proxied:proxy combination.

Note

Proxy objects will detect other Proxy instances and subclasses, but they will not detect Obj instances or subclasses unless they also subclass Proxy. This is intentional behavior.

Warning

Because of how Python works, explicitly reassigning hgx_state is the only way to reassign the value of the proxied object directly. For example, this will fail, overwriting the name of the object, and leaving the original unchanged:

>>> obj
<Proxy to b'Hello world!' at Ghid('bf3dR')>
>>> obj = b'Hello Hypergolix!'
>>> obj
b'Hello Hypergolix!'

whereas this will succeed in updating the object state:

>>> obj
<Proxy to b'Hello world!' at Ghid('bf3dR')>
>>> obj.hgx_state = b'Hello Hypergolix!'
>>> obj
<Proxy to b'Hello Hypergolix!' at Ghid('bf3dR')>
>>> obj = hgxlink.new_threadsafe(
...     cls = hgx.Proxy,
...     state = b'Hello world!'
... )
>>> obj
<Proxy to b'hello world' at Ghid('bJQMj')>
>>> obj += b' foo'
>>> obj
<Proxy to b'hello world foo' at Ghid('bJQMj')>
>>> obj.state = b'bar'
>>> obj
<Proxy to b'bar' at Ghid('bJQMj')>
__eq__(other)

Compares the Proxy with another object. The comparison recognizes other Hypergolix objects, comparing them more thoroughly than other objects.

If other is a Hypergolix object, the comparison will return True if and only if:

  1. The Obj.ghid attribute compares equally
  2. The Obj.state attribute compares equally
  3. The Obj.binder attribute compares equally

If, on the other hand, the other object is not a Hypergolix object or proxy, it will directly compare other with hgx_state.

Parameters:other – The object to compare with
Return type:bool
>>> obj
<Proxy to b'Hello world!' at Ghid('bf3dR')>
>>> obj2
<Proxy to b'Hello world!' at Ghid('WFUmW')>
>>> obj == obj2
False
>>> not_hgx_obj = b'Hello world!'
>>> not_hgx_obj == obj
True
>>> obj2 == not_hgx_obj
True