Source code for kingston.dig

# yapf
"""This module if for reading values from live objects. The general
idea is that you use a function ``kingston.dig.dig()`` with an object
and a string. The string is (somewhat) inspired by a CSS selector. It
specifies how to get a certain sub-element.

The goal is that these spec-strings should be storable for future
re-use.

This module is a work in progress module and thus subject to change.

"""


import hashlib
import fnmatch

from typing import Any

from . import lang

from dataclasses import dataclass
from functools import singledispatch

import funcy as fy

@dataclass
class Attr:
    infer_types = {}
    PrimType = lang.Undefined
    __bind__ = []

    @classmethod
    def create(cls, name, value):

        obj = cls.__new__(cls, value)
        obj.name = name

        @singledispatch
        def eq(other):
            raise NotImplementedError("Attr: eq not implemented for {}".format(
                type(other)))

        @eq.register(Attr)
        def _(other):
            "Does _"
            return other.name == obj.name and other == obj

        obj.eq = eq

        lang.bind_methods(cls, obj)

        return obj

    @staticmethod
    def isa(other):
        return isinstance(other, Attr.__class__)

    def sibling(self, other):
        return isinstance(other.__class__, Attr.__class__)

    @classmethod
    def infer(cls, name, value):
        PrimType = Attr.infer_types[type(value)]
        return PrimType.create(name, value)

    @property
    def raw_value(self):
        "Cast value to primitive type."

        # NB: Special case to avoid infinite recursion.
        if self.PrimType is str:
            return str.__str__(self)

        else:
            return self.PrimType(self)

    @property
    def type_name(self):
        "Return name of primitive type"
        return self.PrimType.__name__

    def __str__(self):
        return "{}={}:{}".format(self.name, self.raw_value, self.type_name)

    def __repr__(self):
        return str(self)

    def __hash__(self):
        "Does __hash__"
        value = self.PrimType(self)
        return hashlib.sha1('{}:{}'.format(self.name, value)).hexdigest()


for name, PrimType in (('IntAttr', int),
                       ('FloatAttr', float),
                       ('TupleAttr', tuple),
                       ('ListAttr', list),
                       ('StrAttr', str)):  # yapf: disable
    AttrClass = lang.mkclass(name, (Attr, PrimType))
    AttrClass.PrimType = PrimType

    locals()[name] = AttrClass
    Attr.infer_types[PrimType] = AttrClass


[docs]def xget(obj:Any, idx:Any) -> Any: """Single point of entry function to fetch a value / attribute / element from an object. :param obj: The object to find attribute / value in. :param idx: Symbolic index. """ if lang.isprimitive(obj): return obj elif callable(obj): return obj(idx) # ??? def attempt_many(): "Does attempt_many" vars_ = lang.pubvars(obj) attrs = fnmatch.filter(vars_, idx) if fy.is_seqcoll(obj) or isinstance(obj, set): return attrs else: return [Attr.infer(attr, xget(obj, attr)) for attr in attrs] try: try: return obj[idx] except KeyError: return attempt_many() except TypeError: try: return getattr(obj, idx) except AttributeError: return attempt_many()
def idig(obj:Any, path:Any) -> Any: "Recursive query for attributes from `obj` by a sequence spec." key = path.pop(0) point = xget(obj, key) if path: return idig(point, path) else: return point
[docs]def dig(obj:Any, path:str) -> Any: """Dig after object content from object content based on a string spec. :param obj: A live object that values should be digged from. :param path: String representation of the *”path”* """ return idig(obj, lang.detect_numbers(path.split('.')))