Module shaystack.providers.haystack_interface

Base of haystack implementation.

Expand source code
# -*- coding: utf-8 -*-
# Abstract interface
# See the accompanying LICENSE file.
# (C) 2021 Engie Digital
#
# vim: set ts=4 sts=4 et tw=78 sw=4 si:
"""
Base of haystack implementation.
"""
import logging
from abc import ABC, abstractmethod
from dataclasses import dataclass
from datetime import datetime, date, timedelta, tzinfo
from importlib import import_module
from typing import Any, Tuple, Dict, Optional, List, cast

import pytz
from pytz import BaseTzInfo
from tzlocal import get_localzone

from ..datatypes import Ref, Quantity, Uri
from ..grid import Grid, VER_3_0
from ..grid_filter import parse_hs_datetime_format

log = logging.getLogger("shaystack")


def _to_camel(snake_str: str) -> str:
    first, *others = snake_str.split("_")
    return "".join([first.lower(), *map(str.title, others)])


@dataclass
class HttpError(Exception):
    """
    Exception to propagate a specific HTTP error
    """
    error: int
    msg: str


# noinspection PyMethodMayBeStatic
class HaystackInterface(ABC):
    """
    Interface to implement to be compatible with Haystack REST protocol.
    The subclasses may be abstract (implemented only a part of methods),
    the 'ops' code detect that, and can calculate the set of implemented operations.
    """
    __slots__ = ['_envs']

    def __init__(self, envs: Dict[str, str]):
        assert envs is not None
        self._envs = envs

    @property
    @abstractmethod
    def name(self) -> str:
        """ The name of the provider. """
        raise NotImplementedError()

    def __repr__(self) -> str:
        return self.name

    def __str__(self) -> str:
        return self.__repr__()

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, exc_traceback):
        pass

    # noinspection PyMethodMayBeStatic
    def get_tz(self) -> BaseTzInfo:  # pylint: disable=no-self-use
        """ Return server time zone. """
        return get_localzone()

    def get_customer_id(self) -> str:  # pylint: disable=no-self-use
        """ Override this for multi-tenant.
        May be, extract the customer id from the current `Principal`.
        """
        return ''

    def values_for_tag(self, tag: str,
                       date_version: Optional[datetime] = None) -> List[Any]:
        """Get all values for a given tag.

        Args:
            tag: The tag to analyse.
            date_version: version date of the ontology.

        Returns:
            All unique values for a specific tag
        """
        raise NotImplementedError()

    def versions(self) -> List[datetime]:  # pylint: disable=no-self-use
        """
        Return a list of versions fot the current ontology.
        Returns:
            datetime for each version or empty array if unknown
        """
        return []

    @abstractmethod
    def about(self, home: str) -> Grid:
        """Implement the Haystack 'about' ops.

        The subclasse must complet the result with "productUri", "productVersion", "moduleName"
        and "moduleVersion"

        Args:
            home: Home url of the API

        Returns:
            The default 'about' grid.
        """
        grid = Grid(
            version=VER_3_0,
            columns=[
                "haystackVersion",  # Str version of REST implementation
                "tz",  # Str of server's default timezone
                "serverName",  # Str name of the server or project database
                "serverTime",
                "serverBootTime",
                "productName",  # Str name of the server software product
                "productUri",
                "productVersion",
                # module which implements Haystack server protocol
                "moduleName",
                # if its a plug-in to the product
                "moduleVersion"  # Str version of moduleName
            ],
        )
        grid.append(
            {
                "haystackVersion": str(VER_3_0),
                "tz": str(self.get_tz()),
                "serverName": "haystack_" + self._envs.get("AWS_REGION", "local"),
                "serverTime": datetime.now(tz=self.get_tz()).replace(microsecond=0),
                "serverBootTime": datetime.now(tz=self.get_tz()).replace(
                    microsecond=0
                ),
                "productName": "Haystack Provider",
                "productUri": Uri(home),
                "productVersion": "0.1",
                "moduleName": "AbstractProvider",
                "moduleVersion": "0.1",
            }
        )
        return grid

    # noinspection PyUnresolvedReferences
    def ops(self) -> Grid:
        """ Implement the Haystack 'ops' ops.

        Notes:
            Automatically calculate the implemented version.

        Returns:
            A Grid containing 'ops' name operations and its related description
        """
        grid = Grid(
            version=VER_3_0,
            columns={
                "name": {},
                "summary": {},
            },
        )
        all_haystack_ops = {
            "about": "Summary information for server",
            "ops": "Operations supported by this server",
            "formats": "Grid data formats supported by this server",
            "read": "The read op is used to read a set of entity records either by their unique "
                    "identifier or using a filter.",
            "nav": "The nav op is used navigate a project for learning and discovery",
            "watch_sub": "The watch_sub operation is used to create new watches "
                         "or add entities to an existing watch.",
            "watch_unsub": "The watch_unsub operation is used to close a watch entirely "
                           "or remove entities from a watch.",
            "watch_poll": "The watch_poll operation is used to poll a watch for "
                          "changes to the subscribed entity records.",
            "point_write": "The point_write_read op is used to: read the current status of a "
                           "writable point's priority array "
                           "or write to a given level",
            "his_read": "The his_read op is used to read a time-series data "
                        "from historized point.",
            "his_write": "The his_write op is used to post new time-series "
                         "data to a historized point.",
            "invoke_action": "The invoke_action op is used to invoke a "
                             "user action on a target record.",
        }
        # Remove abstract method
        # noinspection PyUnresolvedReferences
        for abstract_method in self.__class__.__base__.__abstractmethods__:
            all_haystack_ops.pop(abstract_method, None)
        if (
                "point_write_read" in self.__class__.__base__.__abstractmethods__
                or "point_write_write" in self.__class__.__base__.__abstractmethods__
        ):
            all_haystack_ops.pop("point_write", None)
        all_haystack_ops = {_to_camel(k): v for k, v in all_haystack_ops.items()}

        grid.extend(
            [
                {"name": name, "summary": summary}
                for name, summary in all_haystack_ops.items()
            ]
        )
        return grid

    def formats(self) -> Optional[Grid]:  # pylint: disable=no-self-use
        """ Implement the Haystack 'formats' ops.

        Notes:
            Implement this method, only if you want to limit the format negotiation
        Returns:
            The grid format or None. If None, the API accept all formats ZINC, TRIO, JSON and CSV.
        """
        return None  # type: ignore

    @abstractmethod
    def read(
            self,
            limit: int,
            select: Optional[str],
            entity_ids: Optional[List[Ref]],
            grid_filter: Optional[str],
            date_version: Optional[datetime],
    ) -> Grid:  # pylint: disable=no-self-use
        """
        Implement the Haystack 'read' ops.

        Args:
            limit: The number of record to return or zero
            select: The selected tag separated with comma, else '' or '*'
            entity_ids: A list en ids. If set, grid_filter and limit are ignored.
            grid_filter: A filter to apply. Ignored if entity_ids is set.
            date_version: The date of the ontology version.

        Returns:
            The requested Grid
        """
        raise NotImplementedError()

    @abstractmethod
    def nav(self, nav_id: str) -> Any:  # pylint: disable=no-self-use
        """ Implement the Haystack 'nav' ops.
        This operation allows servers to expose the database in a human-friendly tree (or graph)
        that can be explored

        Args:
             nav_id: The string for nav id column
        """
        raise NotImplementedError()

    @abstractmethod
    def watch_sub(
            self,
            watch_dis: str,
            watch_id: Optional[str],
            ids: List[Ref],
            lease: Optional[int],
    ) -> Grid:  # pylint: disable=no-self-use
        """
        Implement the Haystack 'watchSub' ops.

        Args:
            watch_dis: Watch description
            watch_id: The user watch_id to update or None.
            ids: The list of ids to watch.
            lease: Lease to apply.

        Returns:
            A Grid
        """
        raise NotImplementedError()

    @abstractmethod
    def watch_unsub(
            self, watch_id: str, ids: List[Ref], close: bool
    ) -> Grid:  # pylint: disable=no-self-use
        """
        Implement the Haystack 'watchUnsub' ops.

        Args:
            watch_id: The user watch_id to update or None
            ids: The list of ids to watch
            close: Set to True to close

        Returns:
            A Grid
        """
        raise NotImplementedError()

    @abstractmethod
    def watch_poll(
            self, watch_id: str, refresh: bool
    ) -> Grid:  # pylint: disable=no-self-use
        """ Implement the Haystack 'watchPoll' ops.

        Args:
            watch_id: The user watch_id to update or None
            refresh: Set to True for refreshing the data

        Returns:
            A Grid where each row corresponds to a watched entity.
        """
        raise NotImplementedError()

    @abstractmethod
    def point_write_read(
            self, entity_id: Ref, date_version: Optional[datetime]
    ) -> Grid:  # pylint: disable=no-self-use
        """ Implement the Haystack 'pointWrite' ops.

        Args:
            entity_id: The entity to update
            date_version: The optional date version to update

        Returns:
            A Grid
        """
        raise NotImplementedError()

    @abstractmethod
    def point_write_write(
            self,
            entity_id: Ref,
            level: int,
            val: Optional[Any],
            duration: Quantity,
            who: Optional[str],
            date_version: Optional[datetime] = None,
    ) -> None:  # pylint: disable=no-self-use
        """ Implement the Haystack 'pointWrite' ops.

        Args:
            entity_id: The entity to update
            level: Number from 1-17 for level to write
            val: Value to write or null to auto the level
            duration: Number with duration unit if setting level 8
            who: Optional username performing the write, otherwise user dis is used
            date_version: The optional date version to update

        Returns:
            None
        """
        raise NotImplementedError()

    # Date dates_range must be:
    # "today"
    # "yesterday"
    # "{date}"
    # "{date},{date}"
    # "{dateTime},{dateTime}"
    # "{dateTime}"
    @abstractmethod
    def his_read(
            self,
            entity_id: Ref,
            dates_range: Tuple[datetime, datetime],
            date_version: Optional[datetime] = None,
    ) -> Grid:  # pylint: disable=no-self-use
        """ Implement the Haystack 'hisRead' ops.

        Args:
            entity_id: The entity to read
            dates_range: May be "today", "yesterday", {date}, ({date},{date}), ({datetime},{datetime}),
            {dateTime}
            date_version: The optional date version to read

        Returns:
            A grid
        """
        raise NotImplementedError()

    @abstractmethod
    def his_write(
            self,
            entity_id: Ref,
            time_series: Grid,
            date_version: Optional[datetime] = None
    ) -> Grid:  # pylint: disable=no-self-use
        """ Implement the Haystack 'hisWrite' ops.

        Args:
            entity_id: The entity to read
            time_series: A grid with a time series
            date_version: The optional date version to update

        Returns:
            A grid
        """
        raise NotImplementedError()

    @abstractmethod
    def invoke_action(
            self,
            entity_id: Ref,
            action: str,
            params: Dict[str, Any],
            date_version: Optional[datetime] = None
    ) -> Grid:  # pylint: disable=no-self-use
        """ Implement the Haystack 'invokeAction' ops.

        Args:
            entity_id: The entity to read
            action: The action string
            params: A dictionary with parameters
            date_version: The optional date version to update

        Returns:
            A grid
        """
        raise NotImplementedError()


_providers = {}


def no_cache():
    """ Must be patched in unit test """
    return False


# noinspection PyProtectedMember,PyUnresolvedReferences
def get_provider(class_str: str, envs: Dict[str, str],  # pylint: disable=protected-access
                 use_cache=True  # pylint: disable=protected-access
                 ) -> HaystackInterface:
    """Return an instance of the provider.
    If the provider is an abstract class, create a sub class with all the implementation
    and return an instance of this subclass. Then, the 'ops' method can analyse the current instance
    and detect the implemented and abstract methods.

    Args:
        class_str: The name of the module that contains the provider.
        envs: Environement variable (os.env ?)
        use_cache: Use cached provider or create a new one

    Returns:
        A instance of this subclass if it exists
    """
    if not class_str.endswith(".Provider"):
        class_str += ".Provider"
    if use_cache and class_str in _providers:
        return _providers[class_str]
    module_path, class_name = class_str.rsplit(".", 1)
    module = import_module(module_path)
    # Get the abstract class name
    provider_class = getattr(module, class_name)

    # Implement all abstract method.
    # Then, it's possible to generate the Ops operator dynamically
    # pylint: disable=missing-function-docstring,useless-super-delegation
    # noinspection PyShadowingNames
    class FullInterface(provider_class):  # pylint: disable=missing-class-docstring
        def __init__(self, envs: Dict[str, str]):
            provider_class.__init__(self, envs)

        def name(self) -> str:
            return super().name()

        def about(
                self, home: str
        ) -> Grid:  # pylint: disable=missing-function-docstring,useless-super-delegation
            return super().about(home)

        def read(
                self,
                limit: int,
                select: Optional[str],
                entity_ids: Optional[List[Ref]],
                grid_filter: Optional[str],
                date_version: Optional[datetime],
        ) -> Grid:
            # pylint: disable=missing-function-docstring,useless-super-delegation
            return super().read(limit, select, entity_ids, grid_filter, date_version)

        def nav(self, nav_id: str) -> Any:
            # pylint: disable=missing-function-docstring,useless-super-delegation
            return super().nav(nav_id)

        def watch_sub(
                self,
                watch_dis: str,
                watch_id: Optional[str],
                ids: List[Ref],
                lease: Optional[int],
        ) -> Grid:
            # pylint: disable=missing-function-docstring,useless-super-delegation
            return super().watch_sub(watch_dis, watch_id, ids, lease)

        def watch_unsub(
                self, watch_id: str, ids: List[Ref], close_all: bool
        ) -> None:
            # pylint: disable=missing-function-docstring,useless-super-delegation
            return super().watch_unsub(watch_id, ids, close_all)

        def watch_poll(self, watch_id: str, refresh: bool) -> Grid:
            return super().watch_poll(watch_id, refresh)

        def point_write_read(  # pylint: disable=missing-function-docstring,useless-super-delegation
                self, entity_id: Ref, date_version: Optional[datetime]
        ) -> Grid:
            return super().point_write_read(entity_id, date_version)

        def point_write_write(  # pylint: disable=missing-function-docstring,useless-super-delegation
                self,
                entity_id: Ref,
                level: int,
                val: Optional[Any],
                duration: Quantity,
                who: Optional[str],
                date_version: Optional[datetime],
        ) -> None:  # pylint: disable=no-self-use
            return super().point_write_write(
                entity_id, level, val, duration, who, date_version
            )

        def his_read(  # pylint: disable=missing-function-docstring,useless-super-delegation
                self,
                entity_id: Ref,
                date_range: Optional[Tuple[datetime, datetime]],
                date_version: Optional[datetime],
        ) -> Grid:
            return super().his_read(entity_id, date_range, date_version)

        def his_write(  # pylint: disable=missing-function-docstring, useless-super-delegation
                self, entity_id: Ref, time_serie: Grid, date_version: Optional[datetime]
        ) -> Grid:
            return super().his_write(entity_id, time_serie, date_version)

        def invoke_action(  # pylint: disable=missing-function-docstring,useless-super-delegation
                self,
                entity_id: Ref,
                action: str,
                params: Dict[str, Any],
        ) -> Grid:
            return super().invoke_action(entity_id, action, params)

    _providers[class_str] = FullInterface(envs)
    return _providers[class_str]


SINGLETON_PROVIDER = None


# noinspection PyProtectedMember
def get_singleton_provider(envs: Dict[str, str]  # pylint: disable=protected-access
                           ) -> HaystackInterface:
    """
    Return the current provider, deduce from the `HAYSTACK_PROVIDER` variable.
    Returns:
        The current provider for the process.
    """
    global SINGLETON_PROVIDER  # pylint: disable=global-statement
    provider = envs.get("HAYSTACK_PROVIDER", "shaystack.providers.db")
    if not SINGLETON_PROVIDER or no_cache():
        log.debug("Provider=%s", provider)
        SINGLETON_PROVIDER = get_provider(provider, envs)
    return SINGLETON_PROVIDER


_DATETIME_MIN_TZ = datetime.min.replace(tzinfo=pytz.utc)
_DATETIME_MAX_TZ = datetime.max.replace(tzinfo=pytz.utc)


def parse_date_range(date_range: str, timezone: tzinfo) -> Tuple[datetime, datetime]:
    """
    Parse a date_range string.
    Args:
        date_range: The string with different acceptable format.
        timezone: The current time-zone to convert the date to datetime.

    Returns:
        A tuple with the begin (inclusive) and end datetime (exclusive).
    """
    if not date_range:
        return datetime.min.replace(tzinfo=pytz.UTC), \
               datetime.max.replace(tzinfo=pytz.UTC)
    if date_range not in ("today", "yesterday"):
        str_date = date_range.split(",")
        split_date = [parse_hs_datetime_format(x, timezone) if x else None for x in str_date]
        if len(str_date) > 1:
            if str_date[1] in ("today", "yesterday"):
                split_date[1] += timedelta(days=1)

            # Convert to same type
            if isinstance(split_date[0], datetime) or isinstance(split_date[1], datetime):
                # One is a datetime. The other must be convert to datetime
                if not split_date[0]:
                    split_date[0] = _DATETIME_MIN_TZ
                if not isinstance(split_date[0], datetime):
                    split_date[0] = datetime.combine(split_date[0], datetime.min.time())
                if not split_date[1]:
                    split_date[1] = _DATETIME_MAX_TZ
                if not isinstance(split_date[1], datetime):
                    split_date[1] = datetime.combine(split_date[1], datetime.min.time())

            # Add missing tzinfo
            if isinstance(split_date[0], datetime) and not split_date[0].tzinfo:
                split_date[0] = split_date[0].replace(tzinfo=timezone)
            if len(str_date) > 1 and isinstance(split_date[1], datetime) and not split_date[1].tzinfo:
                split_date[1] = split_date[1].replace(tzinfo=timezone)

            # Add missing part
            if isinstance(split_date[0], datetime) or isinstance(split_date[1], datetime):
                if not split_date[0]:
                    split_date[0] = _DATETIME_MIN_TZ
                if not split_date[1]:
                    split_date[1] = _DATETIME_MAX_TZ
            elif isinstance(split_date[0], date) or isinstance(split_date[1], date):
                if not split_date[0]:
                    split_date[0] = _DATETIME_MIN_TZ
                if not split_date[1]:
                    split_date[1] = _DATETIME_MAX_TZ
                if not isinstance(split_date[0], datetime):
                    if split_date[0] == date.min:
                        split_date[0] = _DATETIME_MIN_TZ
                    else:
                        split_date[0] = datetime.combine(split_date[0],
                                                         datetime.min.time()).replace(tzinfo=timezone)

                if not isinstance(split_date[1], datetime):
                    if split_date[1] == date.max:
                        split_date[1] = _DATETIME_MAX_TZ
                    else:
                        split_date[1] = datetime.combine(split_date[1],
                                                         datetime.max.time()).replace(tzinfo=timezone)
            return cast(Tuple[datetime, datetime], (split_date[0], split_date[1]))
        if isinstance(split_date[0], datetime):
            if not split_date[0].tzinfo:
                split_date[0] = split_date[0].replace(tzinfo=timezone)
            return split_date[0], _DATETIME_MAX_TZ
        assert isinstance(split_date[0], date)
        split_date[0] = datetime.combine(split_date[0], datetime.min.time()).replace(tzinfo=timezone)
        return split_date[0], split_date[0] + timedelta(days=1)

    if date_range == "today":
        today = datetime.combine(date.today(), datetime.min.time()) \
            .replace(tzinfo=timezone)
        return today, today + timedelta(days=1)
    if date_range == "yesterday":
        yesterday = datetime.combine(date.today() - timedelta(days=1), datetime.min.time()) \
            .replace(tzinfo=timezone)
        return yesterday, yesterday + timedelta(days=1)
    raise ValueError(f"date_range {date_range} unknown")

Functions

def get_provider(class_str: str, envs: Dict[str, str], use_cache=True) ‑> HaystackInterface

Return an instance of the provider. If the provider is an abstract class, create a sub class with all the implementation and return an instance of this subclass. Then, the 'ops' method can analyse the current instance and detect the implemented and abstract methods.

Args

class_str
The name of the module that contains the provider.
envs
Environement variable (os.env ?)
use_cache
Use cached provider or create a new one

Returns

A instance of this subclass if it exists

Expand source code
def get_provider(class_str: str, envs: Dict[str, str],  # pylint: disable=protected-access
                 use_cache=True  # pylint: disable=protected-access
                 ) -> HaystackInterface:
    """Return an instance of the provider.
    If the provider is an abstract class, create a sub class with all the implementation
    and return an instance of this subclass. Then, the 'ops' method can analyse the current instance
    and detect the implemented and abstract methods.

    Args:
        class_str: The name of the module that contains the provider.
        envs: Environement variable (os.env ?)
        use_cache: Use cached provider or create a new one

    Returns:
        A instance of this subclass if it exists
    """
    if not class_str.endswith(".Provider"):
        class_str += ".Provider"
    if use_cache and class_str in _providers:
        return _providers[class_str]
    module_path, class_name = class_str.rsplit(".", 1)
    module = import_module(module_path)
    # Get the abstract class name
    provider_class = getattr(module, class_name)

    # Implement all abstract method.
    # Then, it's possible to generate the Ops operator dynamically
    # pylint: disable=missing-function-docstring,useless-super-delegation
    # noinspection PyShadowingNames
    class FullInterface(provider_class):  # pylint: disable=missing-class-docstring
        def __init__(self, envs: Dict[str, str]):
            provider_class.__init__(self, envs)

        def name(self) -> str:
            return super().name()

        def about(
                self, home: str
        ) -> Grid:  # pylint: disable=missing-function-docstring,useless-super-delegation
            return super().about(home)

        def read(
                self,
                limit: int,
                select: Optional[str],
                entity_ids: Optional[List[Ref]],
                grid_filter: Optional[str],
                date_version: Optional[datetime],
        ) -> Grid:
            # pylint: disable=missing-function-docstring,useless-super-delegation
            return super().read(limit, select, entity_ids, grid_filter, date_version)

        def nav(self, nav_id: str) -> Any:
            # pylint: disable=missing-function-docstring,useless-super-delegation
            return super().nav(nav_id)

        def watch_sub(
                self,
                watch_dis: str,
                watch_id: Optional[str],
                ids: List[Ref],
                lease: Optional[int],
        ) -> Grid:
            # pylint: disable=missing-function-docstring,useless-super-delegation
            return super().watch_sub(watch_dis, watch_id, ids, lease)

        def watch_unsub(
                self, watch_id: str, ids: List[Ref], close_all: bool
        ) -> None:
            # pylint: disable=missing-function-docstring,useless-super-delegation
            return super().watch_unsub(watch_id, ids, close_all)

        def watch_poll(self, watch_id: str, refresh: bool) -> Grid:
            return super().watch_poll(watch_id, refresh)

        def point_write_read(  # pylint: disable=missing-function-docstring,useless-super-delegation
                self, entity_id: Ref, date_version: Optional[datetime]
        ) -> Grid:
            return super().point_write_read(entity_id, date_version)

        def point_write_write(  # pylint: disable=missing-function-docstring,useless-super-delegation
                self,
                entity_id: Ref,
                level: int,
                val: Optional[Any],
                duration: Quantity,
                who: Optional[str],
                date_version: Optional[datetime],
        ) -> None:  # pylint: disable=no-self-use
            return super().point_write_write(
                entity_id, level, val, duration, who, date_version
            )

        def his_read(  # pylint: disable=missing-function-docstring,useless-super-delegation
                self,
                entity_id: Ref,
                date_range: Optional[Tuple[datetime, datetime]],
                date_version: Optional[datetime],
        ) -> Grid:
            return super().his_read(entity_id, date_range, date_version)

        def his_write(  # pylint: disable=missing-function-docstring, useless-super-delegation
                self, entity_id: Ref, time_serie: Grid, date_version: Optional[datetime]
        ) -> Grid:
            return super().his_write(entity_id, time_serie, date_version)

        def invoke_action(  # pylint: disable=missing-function-docstring,useless-super-delegation
                self,
                entity_id: Ref,
                action: str,
                params: Dict[str, Any],
        ) -> Grid:
            return super().invoke_action(entity_id, action, params)

    _providers[class_str] = FullInterface(envs)
    return _providers[class_str]
def get_singleton_provider(envs: Dict[str, str]) ‑> HaystackInterface

Return the current provider, deduce from the HAYSTACK_PROVIDER variable.

Returns

The current provider for the process.

Expand source code
def get_singleton_provider(envs: Dict[str, str]  # pylint: disable=protected-access
                           ) -> HaystackInterface:
    """
    Return the current provider, deduce from the `HAYSTACK_PROVIDER` variable.
    Returns:
        The current provider for the process.
    """
    global SINGLETON_PROVIDER  # pylint: disable=global-statement
    provider = envs.get("HAYSTACK_PROVIDER", "shaystack.providers.db")
    if not SINGLETON_PROVIDER or no_cache():
        log.debug("Provider=%s", provider)
        SINGLETON_PROVIDER = get_provider(provider, envs)
    return SINGLETON_PROVIDER
def no_cache()

Must be patched in unit test

Expand source code
def no_cache():
    """ Must be patched in unit test """
    return False
def parse_date_range(date_range: str, timezone: datetime.tzinfo) ‑> Tuple[datetime.datetime, datetime.datetime]

Parse a date_range string.

Args

date_range
The string with different acceptable format.
timezone
The current time-zone to convert the date to datetime.

Returns

A tuple with the begin (inclusive) and end datetime (exclusive).

Expand source code
def parse_date_range(date_range: str, timezone: tzinfo) -> Tuple[datetime, datetime]:
    """
    Parse a date_range string.
    Args:
        date_range: The string with different acceptable format.
        timezone: The current time-zone to convert the date to datetime.

    Returns:
        A tuple with the begin (inclusive) and end datetime (exclusive).
    """
    if not date_range:
        return datetime.min.replace(tzinfo=pytz.UTC), \
               datetime.max.replace(tzinfo=pytz.UTC)
    if date_range not in ("today", "yesterday"):
        str_date = date_range.split(",")
        split_date = [parse_hs_datetime_format(x, timezone) if x else None for x in str_date]
        if len(str_date) > 1:
            if str_date[1] in ("today", "yesterday"):
                split_date[1] += timedelta(days=1)

            # Convert to same type
            if isinstance(split_date[0], datetime) or isinstance(split_date[1], datetime):
                # One is a datetime. The other must be convert to datetime
                if not split_date[0]:
                    split_date[0] = _DATETIME_MIN_TZ
                if not isinstance(split_date[0], datetime):
                    split_date[0] = datetime.combine(split_date[0], datetime.min.time())
                if not split_date[1]:
                    split_date[1] = _DATETIME_MAX_TZ
                if not isinstance(split_date[1], datetime):
                    split_date[1] = datetime.combine(split_date[1], datetime.min.time())

            # Add missing tzinfo
            if isinstance(split_date[0], datetime) and not split_date[0].tzinfo:
                split_date[0] = split_date[0].replace(tzinfo=timezone)
            if len(str_date) > 1 and isinstance(split_date[1], datetime) and not split_date[1].tzinfo:
                split_date[1] = split_date[1].replace(tzinfo=timezone)

            # Add missing part
            if isinstance(split_date[0], datetime) or isinstance(split_date[1], datetime):
                if not split_date[0]:
                    split_date[0] = _DATETIME_MIN_TZ
                if not split_date[1]:
                    split_date[1] = _DATETIME_MAX_TZ
            elif isinstance(split_date[0], date) or isinstance(split_date[1], date):
                if not split_date[0]:
                    split_date[0] = _DATETIME_MIN_TZ
                if not split_date[1]:
                    split_date[1] = _DATETIME_MAX_TZ
                if not isinstance(split_date[0], datetime):
                    if split_date[0] == date.min:
                        split_date[0] = _DATETIME_MIN_TZ
                    else:
                        split_date[0] = datetime.combine(split_date[0],
                                                         datetime.min.time()).replace(tzinfo=timezone)

                if not isinstance(split_date[1], datetime):
                    if split_date[1] == date.max:
                        split_date[1] = _DATETIME_MAX_TZ
                    else:
                        split_date[1] = datetime.combine(split_date[1],
                                                         datetime.max.time()).replace(tzinfo=timezone)
            return cast(Tuple[datetime, datetime], (split_date[0], split_date[1]))
        if isinstance(split_date[0], datetime):
            if not split_date[0].tzinfo:
                split_date[0] = split_date[0].replace(tzinfo=timezone)
            return split_date[0], _DATETIME_MAX_TZ
        assert isinstance(split_date[0], date)
        split_date[0] = datetime.combine(split_date[0], datetime.min.time()).replace(tzinfo=timezone)
        return split_date[0], split_date[0] + timedelta(days=1)

    if date_range == "today":
        today = datetime.combine(date.today(), datetime.min.time()) \
            .replace(tzinfo=timezone)
        return today, today + timedelta(days=1)
    if date_range == "yesterday":
        yesterday = datetime.combine(date.today() - timedelta(days=1), datetime.min.time()) \
            .replace(tzinfo=timezone)
        return yesterday, yesterday + timedelta(days=1)
    raise ValueError(f"date_range {date_range} unknown")

Classes

class HaystackInterface (envs: Dict[str, str])

Interface to implement to be compatible with Haystack REST protocol. The subclasses may be abstract (implemented only a part of methods), the 'ops' code detect that, and can calculate the set of implemented operations.

Expand source code
class HaystackInterface(ABC):
    """
    Interface to implement to be compatible with Haystack REST protocol.
    The subclasses may be abstract (implemented only a part of methods),
    the 'ops' code detect that, and can calculate the set of implemented operations.
    """
    __slots__ = ['_envs']

    def __init__(self, envs: Dict[str, str]):
        assert envs is not None
        self._envs = envs

    @property
    @abstractmethod
    def name(self) -> str:
        """ The name of the provider. """
        raise NotImplementedError()

    def __repr__(self) -> str:
        return self.name

    def __str__(self) -> str:
        return self.__repr__()

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, exc_traceback):
        pass

    # noinspection PyMethodMayBeStatic
    def get_tz(self) -> BaseTzInfo:  # pylint: disable=no-self-use
        """ Return server time zone. """
        return get_localzone()

    def get_customer_id(self) -> str:  # pylint: disable=no-self-use
        """ Override this for multi-tenant.
        May be, extract the customer id from the current `Principal`.
        """
        return ''

    def values_for_tag(self, tag: str,
                       date_version: Optional[datetime] = None) -> List[Any]:
        """Get all values for a given tag.

        Args:
            tag: The tag to analyse.
            date_version: version date of the ontology.

        Returns:
            All unique values for a specific tag
        """
        raise NotImplementedError()

    def versions(self) -> List[datetime]:  # pylint: disable=no-self-use
        """
        Return a list of versions fot the current ontology.
        Returns:
            datetime for each version or empty array if unknown
        """
        return []

    @abstractmethod
    def about(self, home: str) -> Grid:
        """Implement the Haystack 'about' ops.

        The subclasse must complet the result with "productUri", "productVersion", "moduleName"
        and "moduleVersion"

        Args:
            home: Home url of the API

        Returns:
            The default 'about' grid.
        """
        grid = Grid(
            version=VER_3_0,
            columns=[
                "haystackVersion",  # Str version of REST implementation
                "tz",  # Str of server's default timezone
                "serverName",  # Str name of the server or project database
                "serverTime",
                "serverBootTime",
                "productName",  # Str name of the server software product
                "productUri",
                "productVersion",
                # module which implements Haystack server protocol
                "moduleName",
                # if its a plug-in to the product
                "moduleVersion"  # Str version of moduleName
            ],
        )
        grid.append(
            {
                "haystackVersion": str(VER_3_0),
                "tz": str(self.get_tz()),
                "serverName": "haystack_" + self._envs.get("AWS_REGION", "local"),
                "serverTime": datetime.now(tz=self.get_tz()).replace(microsecond=0),
                "serverBootTime": datetime.now(tz=self.get_tz()).replace(
                    microsecond=0
                ),
                "productName": "Haystack Provider",
                "productUri": Uri(home),
                "productVersion": "0.1",
                "moduleName": "AbstractProvider",
                "moduleVersion": "0.1",
            }
        )
        return grid

    # noinspection PyUnresolvedReferences
    def ops(self) -> Grid:
        """ Implement the Haystack 'ops' ops.

        Notes:
            Automatically calculate the implemented version.

        Returns:
            A Grid containing 'ops' name operations and its related description
        """
        grid = Grid(
            version=VER_3_0,
            columns={
                "name": {},
                "summary": {},
            },
        )
        all_haystack_ops = {
            "about": "Summary information for server",
            "ops": "Operations supported by this server",
            "formats": "Grid data formats supported by this server",
            "read": "The read op is used to read a set of entity records either by their unique "
                    "identifier or using a filter.",
            "nav": "The nav op is used navigate a project for learning and discovery",
            "watch_sub": "The watch_sub operation is used to create new watches "
                         "or add entities to an existing watch.",
            "watch_unsub": "The watch_unsub operation is used to close a watch entirely "
                           "or remove entities from a watch.",
            "watch_poll": "The watch_poll operation is used to poll a watch for "
                          "changes to the subscribed entity records.",
            "point_write": "The point_write_read op is used to: read the current status of a "
                           "writable point's priority array "
                           "or write to a given level",
            "his_read": "The his_read op is used to read a time-series data "
                        "from historized point.",
            "his_write": "The his_write op is used to post new time-series "
                         "data to a historized point.",
            "invoke_action": "The invoke_action op is used to invoke a "
                             "user action on a target record.",
        }
        # Remove abstract method
        # noinspection PyUnresolvedReferences
        for abstract_method in self.__class__.__base__.__abstractmethods__:
            all_haystack_ops.pop(abstract_method, None)
        if (
                "point_write_read" in self.__class__.__base__.__abstractmethods__
                or "point_write_write" in self.__class__.__base__.__abstractmethods__
        ):
            all_haystack_ops.pop("point_write", None)
        all_haystack_ops = {_to_camel(k): v for k, v in all_haystack_ops.items()}

        grid.extend(
            [
                {"name": name, "summary": summary}
                for name, summary in all_haystack_ops.items()
            ]
        )
        return grid

    def formats(self) -> Optional[Grid]:  # pylint: disable=no-self-use
        """ Implement the Haystack 'formats' ops.

        Notes:
            Implement this method, only if you want to limit the format negotiation
        Returns:
            The grid format or None. If None, the API accept all formats ZINC, TRIO, JSON and CSV.
        """
        return None  # type: ignore

    @abstractmethod
    def read(
            self,
            limit: int,
            select: Optional[str],
            entity_ids: Optional[List[Ref]],
            grid_filter: Optional[str],
            date_version: Optional[datetime],
    ) -> Grid:  # pylint: disable=no-self-use
        """
        Implement the Haystack 'read' ops.

        Args:
            limit: The number of record to return or zero
            select: The selected tag separated with comma, else '' or '*'
            entity_ids: A list en ids. If set, grid_filter and limit are ignored.
            grid_filter: A filter to apply. Ignored if entity_ids is set.
            date_version: The date of the ontology version.

        Returns:
            The requested Grid
        """
        raise NotImplementedError()

    @abstractmethod
    def nav(self, nav_id: str) -> Any:  # pylint: disable=no-self-use
        """ Implement the Haystack 'nav' ops.
        This operation allows servers to expose the database in a human-friendly tree (or graph)
        that can be explored

        Args:
             nav_id: The string for nav id column
        """
        raise NotImplementedError()

    @abstractmethod
    def watch_sub(
            self,
            watch_dis: str,
            watch_id: Optional[str],
            ids: List[Ref],
            lease: Optional[int],
    ) -> Grid:  # pylint: disable=no-self-use
        """
        Implement the Haystack 'watchSub' ops.

        Args:
            watch_dis: Watch description
            watch_id: The user watch_id to update or None.
            ids: The list of ids to watch.
            lease: Lease to apply.

        Returns:
            A Grid
        """
        raise NotImplementedError()

    @abstractmethod
    def watch_unsub(
            self, watch_id: str, ids: List[Ref], close: bool
    ) -> Grid:  # pylint: disable=no-self-use
        """
        Implement the Haystack 'watchUnsub' ops.

        Args:
            watch_id: The user watch_id to update or None
            ids: The list of ids to watch
            close: Set to True to close

        Returns:
            A Grid
        """
        raise NotImplementedError()

    @abstractmethod
    def watch_poll(
            self, watch_id: str, refresh: bool
    ) -> Grid:  # pylint: disable=no-self-use
        """ Implement the Haystack 'watchPoll' ops.

        Args:
            watch_id: The user watch_id to update or None
            refresh: Set to True for refreshing the data

        Returns:
            A Grid where each row corresponds to a watched entity.
        """
        raise NotImplementedError()

    @abstractmethod
    def point_write_read(
            self, entity_id: Ref, date_version: Optional[datetime]
    ) -> Grid:  # pylint: disable=no-self-use
        """ Implement the Haystack 'pointWrite' ops.

        Args:
            entity_id: The entity to update
            date_version: The optional date version to update

        Returns:
            A Grid
        """
        raise NotImplementedError()

    @abstractmethod
    def point_write_write(
            self,
            entity_id: Ref,
            level: int,
            val: Optional[Any],
            duration: Quantity,
            who: Optional[str],
            date_version: Optional[datetime] = None,
    ) -> None:  # pylint: disable=no-self-use
        """ Implement the Haystack 'pointWrite' ops.

        Args:
            entity_id: The entity to update
            level: Number from 1-17 for level to write
            val: Value to write or null to auto the level
            duration: Number with duration unit if setting level 8
            who: Optional username performing the write, otherwise user dis is used
            date_version: The optional date version to update

        Returns:
            None
        """
        raise NotImplementedError()

    # Date dates_range must be:
    # "today"
    # "yesterday"
    # "{date}"
    # "{date},{date}"
    # "{dateTime},{dateTime}"
    # "{dateTime}"
    @abstractmethod
    def his_read(
            self,
            entity_id: Ref,
            dates_range: Tuple[datetime, datetime],
            date_version: Optional[datetime] = None,
    ) -> Grid:  # pylint: disable=no-self-use
        """ Implement the Haystack 'hisRead' ops.

        Args:
            entity_id: The entity to read
            dates_range: May be "today", "yesterday", {date}, ({date},{date}), ({datetime},{datetime}),
            {dateTime}
            date_version: The optional date version to read

        Returns:
            A grid
        """
        raise NotImplementedError()

    @abstractmethod
    def his_write(
            self,
            entity_id: Ref,
            time_series: Grid,
            date_version: Optional[datetime] = None
    ) -> Grid:  # pylint: disable=no-self-use
        """ Implement the Haystack 'hisWrite' ops.

        Args:
            entity_id: The entity to read
            time_series: A grid with a time series
            date_version: The optional date version to update

        Returns:
            A grid
        """
        raise NotImplementedError()

    @abstractmethod
    def invoke_action(
            self,
            entity_id: Ref,
            action: str,
            params: Dict[str, Any],
            date_version: Optional[datetime] = None
    ) -> Grid:  # pylint: disable=no-self-use
        """ Implement the Haystack 'invokeAction' ops.

        Args:
            entity_id: The entity to read
            action: The action string
            params: A dictionary with parameters
            date_version: The optional date version to update

        Returns:
            A grid
        """
        raise NotImplementedError()

Ancestors

  • abc.ABC

Subclasses

Instance variables

var name : str

The name of the provider.

Expand source code
@property
@abstractmethod
def name(self) -> str:
    """ The name of the provider. """
    raise NotImplementedError()

Methods

def about(self, home: str) ‑> shaystack.grid.Grid

Implement the Haystack 'about' ops.

The subclasse must complet the result with "productUri", "productVersion", "moduleName" and "moduleVersion"

Args

home
Home url of the API

Returns

The default 'about' grid.

Expand source code
@abstractmethod
def about(self, home: str) -> Grid:
    """Implement the Haystack 'about' ops.

    The subclasse must complet the result with "productUri", "productVersion", "moduleName"
    and "moduleVersion"

    Args:
        home: Home url of the API

    Returns:
        The default 'about' grid.
    """
    grid = Grid(
        version=VER_3_0,
        columns=[
            "haystackVersion",  # Str version of REST implementation
            "tz",  # Str of server's default timezone
            "serverName",  # Str name of the server or project database
            "serverTime",
            "serverBootTime",
            "productName",  # Str name of the server software product
            "productUri",
            "productVersion",
            # module which implements Haystack server protocol
            "moduleName",
            # if its a plug-in to the product
            "moduleVersion"  # Str version of moduleName
        ],
    )
    grid.append(
        {
            "haystackVersion": str(VER_3_0),
            "tz": str(self.get_tz()),
            "serverName": "haystack_" + self._envs.get("AWS_REGION", "local"),
            "serverTime": datetime.now(tz=self.get_tz()).replace(microsecond=0),
            "serverBootTime": datetime.now(tz=self.get_tz()).replace(
                microsecond=0
            ),
            "productName": "Haystack Provider",
            "productUri": Uri(home),
            "productVersion": "0.1",
            "moduleName": "AbstractProvider",
            "moduleVersion": "0.1",
        }
    )
    return grid
def formats(self) ‑> Union[shaystack.grid.Grid, NoneType]

Implement the Haystack 'formats' ops.

Notes

Implement this method, only if you want to limit the format negotiation

Returns

The grid format or None. If None, the API accept all formats ZINC, TRIO, JSON and CSV.

Expand source code
def formats(self) -> Optional[Grid]:  # pylint: disable=no-self-use
    """ Implement the Haystack 'formats' ops.

    Notes:
        Implement this method, only if you want to limit the format negotiation
    Returns:
        The grid format or None. If None, the API accept all formats ZINC, TRIO, JSON and CSV.
    """
    return None  # type: ignore
def get_customer_id(self) ‑> str

Override this for multi-tenant. May be, extract the customer id from the current Principal.

Expand source code
def get_customer_id(self) -> str:  # pylint: disable=no-self-use
    """ Override this for multi-tenant.
    May be, extract the customer id from the current `Principal`.
    """
    return ''
def get_tz(self) ‑> pytz.tzinfo.BaseTzInfo

Return server time zone.

Expand source code
def get_tz(self) -> BaseTzInfo:  # pylint: disable=no-self-use
    """ Return server time zone. """
    return get_localzone()
def his_read(self, entity_id: shaystack.datatypes.Ref, dates_range: Tuple[datetime.datetime, datetime.datetime], date_version: Union[datetime.datetime, NoneType] = None) ‑> shaystack.grid.Grid

Implement the Haystack 'hisRead' ops.

Args

entity_id
The entity to read
dates_range
May be "today", "yesterday", {date}, ({date},{date}), ({datetime},{datetime}),
{dateTime}
date_version
The optional date version to read

Returns

A grid

Expand source code
@abstractmethod
def his_read(
        self,
        entity_id: Ref,
        dates_range: Tuple[datetime, datetime],
        date_version: Optional[datetime] = None,
) -> Grid:  # pylint: disable=no-self-use
    """ Implement the Haystack 'hisRead' ops.

    Args:
        entity_id: The entity to read
        dates_range: May be "today", "yesterday", {date}, ({date},{date}), ({datetime},{datetime}),
        {dateTime}
        date_version: The optional date version to read

    Returns:
        A grid
    """
    raise NotImplementedError()
def his_write(self, entity_id: shaystack.datatypes.Ref, time_series: shaystack.grid.Grid, date_version: Union[datetime.datetime, NoneType] = None) ‑> shaystack.grid.Grid

Implement the Haystack 'hisWrite' ops.

Args

entity_id
The entity to read
time_series
A grid with a time series
date_version
The optional date version to update

Returns

A grid

Expand source code
@abstractmethod
def his_write(
        self,
        entity_id: Ref,
        time_series: Grid,
        date_version: Optional[datetime] = None
) -> Grid:  # pylint: disable=no-self-use
    """ Implement the Haystack 'hisWrite' ops.

    Args:
        entity_id: The entity to read
        time_series: A grid with a time series
        date_version: The optional date version to update

    Returns:
        A grid
    """
    raise NotImplementedError()
def invoke_action(self, entity_id: shaystack.datatypes.Ref, action: str, params: Dict[str, Any], date_version: Union[datetime.datetime, NoneType] = None) ‑> shaystack.grid.Grid

Implement the Haystack 'invokeAction' ops.

Args

entity_id
The entity to read
action
The action string
params
A dictionary with parameters
date_version
The optional date version to update

Returns

A grid

Expand source code
@abstractmethod
def invoke_action(
        self,
        entity_id: Ref,
        action: str,
        params: Dict[str, Any],
        date_version: Optional[datetime] = None
) -> Grid:  # pylint: disable=no-self-use
    """ Implement the Haystack 'invokeAction' ops.

    Args:
        entity_id: The entity to read
        action: The action string
        params: A dictionary with parameters
        date_version: The optional date version to update

    Returns:
        A grid
    """
    raise NotImplementedError()
def nav(self, nav_id: str) ‑> Any

Implement the Haystack 'nav' ops. This operation allows servers to expose the database in a human-friendly tree (or graph) that can be explored

Args

nav_id
The string for nav id column
Expand source code
@abstractmethod
def nav(self, nav_id: str) -> Any:  # pylint: disable=no-self-use
    """ Implement the Haystack 'nav' ops.
    This operation allows servers to expose the database in a human-friendly tree (or graph)
    that can be explored

    Args:
         nav_id: The string for nav id column
    """
    raise NotImplementedError()
def ops(self) ‑> shaystack.grid.Grid

Implement the Haystack 'ops' ops.

Notes

Automatically calculate the implemented version.

Returns

A Grid containing 'ops' name operations and its related description

Expand source code
def ops(self) -> Grid:
    """ Implement the Haystack 'ops' ops.

    Notes:
        Automatically calculate the implemented version.

    Returns:
        A Grid containing 'ops' name operations and its related description
    """
    grid = Grid(
        version=VER_3_0,
        columns={
            "name": {},
            "summary": {},
        },
    )
    all_haystack_ops = {
        "about": "Summary information for server",
        "ops": "Operations supported by this server",
        "formats": "Grid data formats supported by this server",
        "read": "The read op is used to read a set of entity records either by their unique "
                "identifier or using a filter.",
        "nav": "The nav op is used navigate a project for learning and discovery",
        "watch_sub": "The watch_sub operation is used to create new watches "
                     "or add entities to an existing watch.",
        "watch_unsub": "The watch_unsub operation is used to close a watch entirely "
                       "or remove entities from a watch.",
        "watch_poll": "The watch_poll operation is used to poll a watch for "
                      "changes to the subscribed entity records.",
        "point_write": "The point_write_read op is used to: read the current status of a "
                       "writable point's priority array "
                       "or write to a given level",
        "his_read": "The his_read op is used to read a time-series data "
                    "from historized point.",
        "his_write": "The his_write op is used to post new time-series "
                     "data to a historized point.",
        "invoke_action": "The invoke_action op is used to invoke a "
                         "user action on a target record.",
    }
    # Remove abstract method
    # noinspection PyUnresolvedReferences
    for abstract_method in self.__class__.__base__.__abstractmethods__:
        all_haystack_ops.pop(abstract_method, None)
    if (
            "point_write_read" in self.__class__.__base__.__abstractmethods__
            or "point_write_write" in self.__class__.__base__.__abstractmethods__
    ):
        all_haystack_ops.pop("point_write", None)
    all_haystack_ops = {_to_camel(k): v for k, v in all_haystack_ops.items()}

    grid.extend(
        [
            {"name": name, "summary": summary}
            for name, summary in all_haystack_ops.items()
        ]
    )
    return grid
def point_write_read(self, entity_id: shaystack.datatypes.Ref, date_version: Union[datetime.datetime, NoneType]) ‑> shaystack.grid.Grid

Implement the Haystack 'pointWrite' ops.

Args

entity_id
The entity to update
date_version
The optional date version to update

Returns

A Grid

Expand source code
@abstractmethod
def point_write_read(
        self, entity_id: Ref, date_version: Optional[datetime]
) -> Grid:  # pylint: disable=no-self-use
    """ Implement the Haystack 'pointWrite' ops.

    Args:
        entity_id: The entity to update
        date_version: The optional date version to update

    Returns:
        A Grid
    """
    raise NotImplementedError()
def point_write_write(self, entity_id: shaystack.datatypes.Ref, level: int, val: Union[Any, NoneType], duration: shaystack.datatypes.Quantity, who: Union[str, NoneType], date_version: Union[datetime.datetime, NoneType] = None) ‑> NoneType

Implement the Haystack 'pointWrite' ops.

Args

entity_id
The entity to update
level
Number from 1-17 for level to write
val
Value to write or null to auto the level
duration
Number with duration unit if setting level 8
who
Optional username performing the write, otherwise user dis is used
date_version
The optional date version to update

Returns

None

Expand source code
@abstractmethod
def point_write_write(
        self,
        entity_id: Ref,
        level: int,
        val: Optional[Any],
        duration: Quantity,
        who: Optional[str],
        date_version: Optional[datetime] = None,
) -> None:  # pylint: disable=no-self-use
    """ Implement the Haystack 'pointWrite' ops.

    Args:
        entity_id: The entity to update
        level: Number from 1-17 for level to write
        val: Value to write or null to auto the level
        duration: Number with duration unit if setting level 8
        who: Optional username performing the write, otherwise user dis is used
        date_version: The optional date version to update

    Returns:
        None
    """
    raise NotImplementedError()
def read(self, limit: int, select: Union[str, NoneType], entity_ids: Union[List[shaystack.datatypes.Ref], NoneType], grid_filter: Union[str, NoneType], date_version: Union[datetime.datetime, NoneType]) ‑> shaystack.grid.Grid

Implement the Haystack 'read' ops.

Args

limit
The number of record to return or zero
select
The selected tag separated with comma, else '' or '*'
entity_ids
A list en ids. If set, grid_filter and limit are ignored.
grid_filter
A filter to apply. Ignored if entity_ids is set.
date_version
The date of the ontology version.

Returns

The requested Grid

Expand source code
@abstractmethod
def read(
        self,
        limit: int,
        select: Optional[str],
        entity_ids: Optional[List[Ref]],
        grid_filter: Optional[str],
        date_version: Optional[datetime],
) -> Grid:  # pylint: disable=no-self-use
    """
    Implement the Haystack 'read' ops.

    Args:
        limit: The number of record to return or zero
        select: The selected tag separated with comma, else '' or '*'
        entity_ids: A list en ids. If set, grid_filter and limit are ignored.
        grid_filter: A filter to apply. Ignored if entity_ids is set.
        date_version: The date of the ontology version.

    Returns:
        The requested Grid
    """
    raise NotImplementedError()
def values_for_tag(self, tag: str, date_version: Union[datetime.datetime, NoneType] = None) ‑> List[Any]

Get all values for a given tag.

Args

tag
The tag to analyse.
date_version
version date of the ontology.

Returns

All unique values for a specific tag

Expand source code
def values_for_tag(self, tag: str,
                   date_version: Optional[datetime] = None) -> List[Any]:
    """Get all values for a given tag.

    Args:
        tag: The tag to analyse.
        date_version: version date of the ontology.

    Returns:
        All unique values for a specific tag
    """
    raise NotImplementedError()
def versions(self) ‑> List[datetime.datetime]

Return a list of versions fot the current ontology.

Returns

datetime for each version or empty array if unknown

Expand source code
def versions(self) -> List[datetime]:  # pylint: disable=no-self-use
    """
    Return a list of versions fot the current ontology.
    Returns:
        datetime for each version or empty array if unknown
    """
    return []
def watch_poll(self, watch_id: str, refresh: bool) ‑> shaystack.grid.Grid

Implement the Haystack 'watchPoll' ops.

Args

watch_id
The user watch_id to update or None
refresh
Set to True for refreshing the data

Returns

A Grid where each row corresponds to a watched entity.

Expand source code
@abstractmethod
def watch_poll(
        self, watch_id: str, refresh: bool
) -> Grid:  # pylint: disable=no-self-use
    """ Implement the Haystack 'watchPoll' ops.

    Args:
        watch_id: The user watch_id to update or None
        refresh: Set to True for refreshing the data

    Returns:
        A Grid where each row corresponds to a watched entity.
    """
    raise NotImplementedError()
def watch_sub(self, watch_dis: str, watch_id: Union[str, NoneType], ids: List[shaystack.datatypes.Ref], lease: Union[int, NoneType]) ‑> shaystack.grid.Grid

Implement the Haystack 'watchSub' ops.

Args

watch_dis
Watch description
watch_id
The user watch_id to update or None.
ids
The list of ids to watch.
lease
Lease to apply.

Returns

A Grid

Expand source code
@abstractmethod
def watch_sub(
        self,
        watch_dis: str,
        watch_id: Optional[str],
        ids: List[Ref],
        lease: Optional[int],
) -> Grid:  # pylint: disable=no-self-use
    """
    Implement the Haystack 'watchSub' ops.

    Args:
        watch_dis: Watch description
        watch_id: The user watch_id to update or None.
        ids: The list of ids to watch.
        lease: Lease to apply.

    Returns:
        A Grid
    """
    raise NotImplementedError()
def watch_unsub(self, watch_id: str, ids: List[shaystack.datatypes.Ref], close: bool) ‑> shaystack.grid.Grid

Implement the Haystack 'watchUnsub' ops.

Args

watch_id
The user watch_id to update or None
ids
The list of ids to watch
close
Set to True to close

Returns

A Grid

Expand source code
@abstractmethod
def watch_unsub(
        self, watch_id: str, ids: List[Ref], close: bool
) -> Grid:  # pylint: disable=no-self-use
    """
    Implement the Haystack 'watchUnsub' ops.

    Args:
        watch_id: The user watch_id to update or None
        ids: The list of ids to watch
        close: Set to True to close

    Returns:
        A Grid
    """
    raise NotImplementedError()
class HttpError (error: int, msg: str)

Exception to propagate a specific HTTP error

Expand source code
class HttpError(Exception):
    """
    Exception to propagate a specific HTTP error
    """
    error: int
    msg: str

Ancestors

  • builtins.Exception
  • builtins.BaseException

Class variables

var error : int
var msg : str