Compasses: Describe Objects

Compass Class

class gemma.Compass(target_types=None, attrs=True, items=True, calls=False)[source]
__init__(target_types=None, attrs=True, items=True, calls=False)[source]

Contains rules for how to map a type’s bearings:

  • what bearings should be available when mapping.

  • what type of bearing should be returned.

Parameters
  • target_types (Union[Tuple[Type, …], Type, None]) –

    The classes of object that this compass can map.

    • NoneType = all objects accepted.

  • attrs (Union[bool, List[str]]) –

    Restricted list of Attr name values the compass can return.

    • True: return all Attr bearings (default).

    • False: return no Attr bearings.

  • items (Union[bool, List[Any]]) –

    Restricted list of Item name values the compass can return.

    • True: return all Item bearings (default).

    • False: return no Item bearings.

  • calls (Union[bool, List[str]]) –

    Restricted list of Call name values the compass can return.

    • True: return all Call bearings.

    • False: return no Call bearings (default).

The core use of the Compass object is through Compass.bearings_iter().

attr_iter(target)[source]

Yields (Attr, value) pairs for attributes of target.

Parameters

target (Any) – object to return attributes of.

Return type

Generator[Tuple[Attr, Any], None, None]

Returns

(Attr, value) pair of next valid attr on target

Raises

StopIteration – At end.

Custom compass should raise NotImplementedError if functionality to be disabled.

This method is not meant to be called directly, but through Compass.bearings_iter()

bearings(target)[source]

Returns all results from Compass.bearings_iter() as list.

Parameters

target (Any) – object to get bearings of

Return type

List[Tuple[BearingAbstract, Any]]

Returns

List of (bearing, value) pairs.

list will not be sorted.

bearings_iter(target)[source]

Yields all bearing names of target as BearingAbstract objects.

Parameters

target (Any) – target to yield bearings of

Return type

Generator[Tuple[BearingAbstract, Any], None, None]

Returns

(bearing, value) pair for each valid bearing in target

Raises

NonNavigableError – If target type cannot be inspected by Compass.

Bearings are not yielded in sorted order. Values are yielded from all other methods ending in _iter. For the default Compass, these methods are Compass.attr_iter(), Compass.item_iter(), and Compass.call_iter(). Any method that raises NotImplementedError is skipped silently.

Compasses are used to help Surveyor objects traverse through a data structure. Compasses tell surveyors what bearings it should traverse for a given object type.

See main Compass documentation for examples.

call_iter(target)[source]

Yields (Call, value) pairs for methods of target.

Parameters

target (Any) – object to return item names of.

Return type

Generator[Tuple[Call, Any], None, None]

Returns

(method name as bearing, value) of next valid method on target

Raises

StopIteration – At end.

Custom compass should raise NotImplementedError if functionality to be disabled.

This method is not meant to be called directly, but through Compass.bearings_iter()

is_navigable(target)[source]

Whether the compass can provide Bearings for target.

Parameters

target (Any) – Object to check

Return type

bool

Returns

  • True: compass can provide Bearings for target.

  • False: Cannot.

Can be overridden for custom inspection.

DEFAULT BEHAVIOR: compares the type of target to target_types passed to __init__ using isinstance()

If the target_types param was None or the target type passes, True is returned.

item_iter(target)[source]

Yields (Item, value) pairs for keys/indexes of target.

Parameters

target (Any) – object to return item names of.

Return type

Generator[Tuple[Item, Any], None, None]

Returns

(item name, value) of next valid key/index on target

Raises

StopIteration – At end.

Custom compass should raise NotImplementedError if functionality to be disabled.

This method is not meant to be called directly, but through Compass.bearings_iter()

Map Bearings of Object

Get all (Item, value) pairs of dict:

>>> from gemma import Compass
>>> from gemma.test_objects import test_objects
...
... simple, data_dict, data_list, structured, target = test_objects()
>>>
>>> default_compass = Compass()
>>>
>>> for bearing in default_compass.bearings_iter(data_dict):
...     print(bearing)
...
(<Item: 'a'>, 'a dict')
(<Item: 'b'>, 'b dict')
(<Item: 1>, 'one dict')
(<Item: 2>, 'two dict')
(<Item: 'nested'>, {'one key': 1, 'two key': 2})
(<Item: 'simple'>, DataSimple(text='string value', number=40))

By default, compass returns all non-callable attributes and items. dict does not support attrs, so only Item objects and their values are returned.

Alternatively Compass.bearings() returns the results as a list, rather than a Generator.

>>> default_compass.bearings(data_dict)
[(<Item: 'a'>, 'a dict'), (<Item: 'b'>, 'b dict'), ...

example object refs: data_dict

Include Methods of Object

By default, methods of target are not returned. We have to explicitly allow them by name.

>>> keys_compass = Compass(calls=['keys'])
>>>
>>> for bearing in keys_compass.bearings_iter(data_dict):
...     print(bearing)
...
(<Item: 'a'>, 'a dict')
(<Item: 'b'>, 'b dict')
(<Item: 1>, 'one dict')
(<Item: 2>, 'two dict')
(<Item: 'nested'>, {'one key': 1, 'two key': 2})
(<Item: 'simple'>, DataSimple(text='string value', number=40))
(<Call: 'keys'>, dict_keys(['a', 'b', 1, 2, 'nested', 'simple'])

This is to avoid a compass automatically executing a method like dict.pop() as it traverses an object.

example object refs: data_dict

Omitting Bearings from Map

>>> keys_only_compass = Compass(
...     target_types=dict, items=False, calls=["keys"]
... )
>>>
>>> keys_only_compass.bearings(data_dict)
[(<Call: 'keys'>, dict_keys(['a', 'b', 1, 2, 'nested', 'simple']))]

By passing False to items, we stop any Item bearings from being returned. The same behavior applies to attrs and methods

example object refs: data_dict

Restrict Bearings from Map

Lets say we have a compass that extracts specific info we want about pets from a form. Here is our form data:

>>> pet = {
...     "type": "pet",
...     "name": "fluffy",
...     "age": 7,
...     "species": "cat",
...     "owner": {
...         "name": "Joe",
...         "age": 25,
...         "other_pets":[
...             2345332,
...             1244553,
...             6455666
...         ]
...     }
... }

Now, we only need to grab name, age and species. The owner info is not important to us, and is at least two additional layers that do not need to be traversed.

We could set up a compass, and to get those bearings like so:

>>> target_keys = ["name", "age", "species"]
>>> pet_compass = Compass(items=target_keys)
>>>
>>> pet_compass.bearings(pet)
[(<Item: 'name'>, 'fluffy'), (<Item: 'age'>, 7), (<Item: 'species'>, 'cat')]

Declare Compatible Types

In the keys example above, we may want to signal to a Surveyor object that a compass should only be used with certain types.

>>> dict_compass = Compass(target_types=dict, calls=["keys"])

target_types allows us to signal that only dicts can be passed to this compass.

Now when we check if a data structure can be navigated by this compass:

>>> dict_compass.is_navigable(data_dict)
True
>>> dict_compass.is_navigable(structured)
False

The dict passes, but the dataclass does not. If we try to navigate each, the dataclass will throw a NonNavigableError

>>> dict_compass.bearings(data_dict)
[(<Item: 'a'>, 'a dict'), (<Item: 'b'>, 'b dict'), (<Item: 1>, ...
>>> dict_compass.bearings(structured)
Traceback (most recent call last):
    ...
gemma._exceptions.NonNavigableError: Compass cannot map DataStructured(...

example object refs: data_dict example object refs: structured

Custom Compatibility

What if we want to be more specific about what a compass can navigate?

Using the pet form example from Restrict Bearings from Map, we could restrict the compass to only act on dict objects:

>>> target_keys = ["name", "age", "species"]
>>> pet_compass = Compass(target_types=dict, items=target_keys)

However, this compass will act on any dict:

>>> not_a_pet = {
...     "type": "car",
...     "name": "blue",
...     "make": "toyota",
...     "model": "camry",
...     "age": 7
... }
>>>
>>> pet_compass.bearings(not_a_pet)
[(<Item: 'name'>, 'blue'), (<Item: 'age'>, 7)]

What we must do is create a new compass type and override Compass.is_navigable().

>>> class PetCompass(Compass):
...     def __init__(self):
...         target_keys = ["name", "age", "species"]
...         super().__init__(target_types=dict, items=target_keys)
...
...     def is_navigable(self, target: Any) -> bool:
...         try:
...             dict_type = target["type"]
...         except (KeyError, TypeError):
...             return False
...
...         if dict_type == "pet":
...             return True
...         else:
...             return False

We’ve also altered __init__ to pass our target type and restricted items.

Now when we check if each dict is navigable:

>>> pet_compass = PetCompass()
>>> pet_compass.is_navigable(pet)
True
>>> pet_compass.is_navigable(not_a_pet)
False

And when we try to get the bearings for each:

>>> pet_compass.bearings(pet)
[(<Item: 'name'>, 'fluffy'), (<Item: 'age'>, 7), (<Item: 'species'>, 'cat')]
>>> pet_compass.bearings(not_a_pet)
Traceback (most recent call last):
    ...
gemma._exceptions.NonNavigableError: PetCompass cannot map {'type': 'car'...

Additional Examples

Traversing a Dataclass.
>>> default_compass = Compass()
>>> for bearing in default_compass.bearings_iter(structured):
...     print(bearing)
...
(<Attr: 'a'>, 'a data')
(<Attr: 'b'>, 'b data')
(<Attr: 'one'>, 31)
(<Attr: 'two'>, 32)
(<Attr: 'dict_data'>, {'a': 'a dict', 'b': 'b dict', 1: 'one dict', ...
(<Attr: 'list_data'>, ['zero list', 'one list', 'two list', ...
(<Attr: 'simple'>, DataSimple(text=None, number=None))

Compasses can be extended in other ways. For more information on extending the Compass type, see: Custom-Compasses