Source code for pydeephaven.ticket

#
# Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending
#
from __future__ import annotations

from abc import ABC, abstractmethod
from typing import Optional
from uuid import uuid4

from pydeephaven.dherror import DHError
from deephaven_core.proto import ticket_pb2


[docs]class Ticket(ABC): """A Ticket references an object on the server. """ _ticket_bytes: bytes @abstractmethod def __init__(self, ticket_bytes: bytes): self._ticket_bytes = ticket_bytes @property def bytes(self) -> bytes: """ Returns the ticket as raw bytes. """ return self._ticket_bytes @property def pb_ticket(self) -> ticket_pb2.Ticket: """ Returns the ticket as a gRPC protobuf ticket. """ return ticket_pb2.Ticket(ticket=self._ticket_bytes)
[docs]class SharedTicket(Ticket): """ A SharedTicket is a ticket that can be shared with other sessions. """ def __init__(self, ticket_bytes: bytes): """Initializes a SharedTicket. Args: ticket_bytes (bytes): the raw bytes for the ticket """ if not ticket_bytes: raise DHError('SharedTicket: ticket_bytes is None') elif not ticket_bytes.startswith(b'h'): raise DHError(f'SharedTicket: ticket {ticket_bytes} is not a shared ticket') super().__init__(ticket_bytes)
[docs] @classmethod def random_ticket(cls) -> SharedTicket: """Generates a random shared ticket. To minimize the probability of collision, the ticket is made from a generated UUID. Returns: a SharedTicket """ bytes_ = uuid4().int.to_bytes(16, byteorder='little', signed=False) return cls(ticket_bytes=b'h' + bytes_)
[docs]class ExportTicket(Ticket): """An ExportTicket is a ticket that references an object exported from the server such as a table or a plugin widget. An exported server object will remain available on the server until the ticket is released or the session is closed. Many types of server objects are exportable and can be fetched to the client as an export ticket or as an instance of wrapper class (such as :class:`~.table.Table`, :class:`~.plugin_client.PluginClient`, etc.) that wraps the export ticket. An export ticket can be published to a :class:`.SharedTicket` so that the exported server object can be shared with other sessions. Note: Users should not create ExportTickets directly. They are managed by the Session and are automatically created when exporting objects from the server via. :meth:`.Session.open_table`, :meth:`.Session.fetch`, and any operations that return a Table. """ def __init__(self, ticket_bytes: bytes): """Initializes an ExportTicket. Args: ticket_bytes (bytes): the raw bytes for the ticket """ if not ticket_bytes: raise DHError('ExportTicket: ticket is None') elif not ticket_bytes.startswith(b'e'): raise DHError(f'ExportTicket: ticket {ticket_bytes} is not an export ticket') super().__init__(ticket_bytes)
[docs] @classmethod def export_ticket(cls, ticket_no: int) -> ExportTicket: """Creates an export ticket from a ticket number. Args: ticket_no (int): the export ticket number Returns: an ExportTicket """ ticket_bytes = ticket_no.to_bytes(4, byteorder='little', signed=True) return cls(b'e' + ticket_bytes)
[docs]class ScopeTicket(Ticket): """A ScopeTicket is a ticket that references a scope variable on the server. Scope variables are variables in the global scope of the server and are accessible to all sessions. Scope variables can be fetched to the client as an export ticket or a Deephaven :class:`~.table.Table` that wraps the export ticket. """ def __init__(self, ticket_bytes: bytes): """Initializes a ScopeTicket. Args: ticket_bytes (bytes): the raw bytes for the ticket """ if not ticket_bytes: raise DHError('ScopeTicket: ticket is None') elif not ticket_bytes.startswith(b's/'): raise DHError(f'ScopeTicket: ticket {ticket_bytes} is not a scope ticket') super().__init__(ticket_bytes)
[docs] @classmethod def scope_ticket(cls, name: str) -> ScopeTicket: """Creates a scope ticket that references a scope variable by name. Args: name (str): the name of the scope variable Returns: a ScopeTicket """ if not name: raise DHError('scope_ticket: name must be a non-empty string') return cls(ticket_bytes=f's/{name}'.encode(encoding='ascii'))
[docs]class ApplicationTicket(Ticket): """An ApplicationTicket is a ticket that references a field of an application on the server. Please refer to the documentation on 'Application Mode' for detailed information on applications and their use cases.""" def __init__(self, ticket_bytes: bytes): """Initializes an ApplicationTicket. Args: ticket_bytes (bytes): the raw bytes for the ticket """ if not ticket_bytes: raise DHError('ApplicationTicket: ticket is None') elif not ticket_bytes.startswith(b'a/'): raise DHError(f'ApplicationTicket: ticket {ticket_bytes} is not an application ticket') elif len(ticket_bytes.split(b'/')) != 4: raise DHError(f'ApplicationTicket: ticket {ticket_bytes} is not in the correct format') self.app_id = ticket_bytes.split(b'/')[1].decode(encoding='ascii') self.field = ticket_bytes.split(b'/')[3].decode(encoding='ascii') super().__init__(ticket_bytes)
[docs] @classmethod def app_ticket(cls, app_id: str, field: str) -> ApplicationTicket: """Creates an application ticket that references a field of an application. Args: app_id (str): the application id field (str): the name of the application field Returns: an ApplicationTicket """ if not app_id: raise DHError('app_ticket: app_id must be a non-empty string') if not field: raise DHError('app_ticket: field must be a non-empty string') return cls(ticket_bytes=f'a/{app_id}/f/{field}'.encode(encoding='ascii'))
def _ticket_from_proto(ticket: ticket_pb2.Ticket) -> Ticket: """Creates a Ticket from a gRPC protobuf ticket. Args: ticket (ticket_pb2.Ticket): the gRPC protobuf ticket Returns: a Ticket Raises: DHError: if the ticket type is unknown """ ticket_bytes = ticket.ticket if ticket_bytes.startswith(b'h'): return SharedTicket(ticket_bytes) elif ticket_bytes.startswith(b'e'): return ExportTicket(ticket_bytes) elif ticket_bytes.startswith(b's/'): return ScopeTicket(ticket_bytes) elif ticket_bytes.startswith(b'a/'): return ApplicationTicket(ticket_bytes) else: raise DHError(f'Unknown ticket type: {ticket_bytes}')
[docs]class ServerObject: """ A ServerObject is a typed ticket that represents objects existing on the server that can be referenced by the client. It is presently used to enable client API users to send and receive references to server-side plugins. """ type: Optional[str] """The type of the object. May be None, indicating that the instance cannot be connected to or otherwise directly used from the client.""" ticket: Ticket """The ticket that points to the object on the server.""" def __init__(self, type: Optional[str], ticket: Ticket): self.type = type self.ticket = ticket @property def pb_ticket(self) -> ticket_pb2.Ticket: """Returns the ticket as a gRPC protobuf ticket object.""" return self.ticket.pb_ticket @property def pb_typed_ticket(self) -> ticket_pb2.TypedTicket: """Returns a protobuf typed ticket, suitable for use in communicating with an ObjectType plugin on the server. """ return ticket_pb2.TypedTicket(type=self.type, ticket=self.pb_ticket)
def _server_object_from_proto(typed_ticket: ticket_pb2.TypedTicket) -> ServerObject: """ Creates a ServerObject from a gRPC protobuf typed ticket object. """ return ServerObject(type=typed_ticket.type, ticket=_ticket_from_proto(typed_ticket.ticket))