Skip to content

SOURCE CODE pipda.context DOCS

"""Provides the context to evaluate f.A and f['A']

By default,
1. in the context of select, both f.A and f['A'] return 'A'
2. in the context of evaluation, f.A returns data.A and f['A'] returns data['A']
3. when it is pending, you will need to evaluate args and kwargs yourself.
"""
from __future__ import annotations

from abc import ABC, abstractmethod
from enum import Enum
from typing import Any, Union


class ContextError(Exception):DOCS
    """Any errors related to contexts"""


class ContextBase(ABC):DOCS
    """The context abstract class, defining how
    the Reference objects are evaluated

    - `getattr` defines how `f.A` is evaluated. Note that `f.A.B` will always
        be evaluated as `getattr(f.A, 'B')`
    - `getitem` defines how `f[item]` is evaluated. Note that the `item` here
        is an evaluated value defined by `getref`.
    - `ref` here defines how the reference/item in `f.item` is evaluated.
        Since we could do `f[f.A]`.
    """

    @abstractmethodDOCS
    def getattr(self, parent: Any, ref: str, level: int) -> Any:
        """Defines how `f.A` is evaluated"""

    @abstractmethodDOCS
    def getitem(self, parent: Any, ref: Any, level: int) -> Any:
        """Defines how `f[item]` is evaluated"""

    @propertyDOCS
    def ref(self) -> ContextBase:
        """Defines how `item` in `f[item]` is evaluated.

        This function should return a `ContextBase` object."""
        return self


class ContextSelect(ContextBase):DOCS
    """Context used in a select context

    In this kind of context,
    - `f.A` works as a shortcut of `'A'`;
    - `f[ref]` works as a shortcut of `ref`. However, `ref` is needed to be
        evaluated by a context returned by `getref`
    """

    def getattr(self, parent: Any, ref: str, level: int) -> str:DOCS
        """Get the `ref` directly, regardless of `data`"""
        return ref

    def getitem(self, parent: Any, ref: Any, level: int) -> Any:DOCS
        """Get the `ref` directly, which is already evaluated by `f[ref]`"""
        return ref


class ContextEval(ContextBase):DOCS
    """Context used in a data-evaluation context

    In this kind of context, the expression is evaluated as-is. That is,
    `f.A` is evaluated as `f.A` and `f[item]` is evaluated as `f[item]`
    """

    def getattr(self, parent: Any, ref: str, level: int) -> Any:DOCS
        """How to evaluate `f.A`"""
        return getattr(parent, ref)

    def getitem(self, parent: Any, ref: Any, level: int) -> Any:DOCS
        """How to evaluate `f[item]`"""
        return parent[ref]


class ContextPending(ContextBase):DOCS
    """Pending context, don't evaluate the expression,
    awaiting next avaiable context"""

    def getattr(self, parent: Any, ref: str, level: int) -> str:DOCS
        """Get the `ref` directly, regardless of `data`"""
        raise ContextError("Pending context cannot be used for evaluation.")

    def getitem(self, parent: Any, ref: Any, level: int) -> Any:DOCS
        """Get the `ref` directly, which is already evaluated by `f[ref]`"""
        raise ContextError("Pending context cannot be used for evaluation.")


class Context(Enum):DOCS
    """Context to solve f.A and f['A']

    PENDING: Context to leave the arguments to be evaluated inside
        the function
    SELECT: It select-based context
    EVAL: It evaluation-based context
    """
    PENDING = ContextPending()
    SELECT = ContextSelect()
    EVAL = ContextEval()


ContextType = Union[Context, ContextBase]