Skip to content

Commit

Permalink
1st draft of the preload API
Browse files Browse the repository at this point in the history
  • Loading branch information
forman committed Dec 16, 2024
1 parent d3f0b6c commit 810b380
Show file tree
Hide file tree
Showing 4 changed files with 209 additions and 0 deletions.
4 changes: 4 additions & 0 deletions xcube/core/store/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@
from .descriptor import new_data_descriptor
from .error import DataStoreError
from .fs.registry import new_fs_data_store
from .preload import PreloadEvent
from .preload import PreloadEventType
from .preload import PreloadHandle
from .preload import PreloadMonitor
from .search import DataSearcher
from .search import DefaultSearchMixin
from .store import DataStore
Expand Down
15 changes: 15 additions & 0 deletions xcube/core/store/accessor.py
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,21 @@ def open_data(self, data_id: str, **open_params) -> Any:
DataStoreError: If an error occurs.
"""

def close(self):
"""Closes this data opener.
Should be called if the data opener is no longer needed.
This method may close local files, remote connections, or release
allocated resources.
The default implementation does nothing.
"""

def __del__(self):
"""Overridden to call ``close()``."""
self.close()


class DataDeleter(ABC):
"""An interface that specifies a parameterized `delete_data()` operation.
Expand Down
134 changes: 134 additions & 0 deletions xcube/core/store/preload.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
# Copyright (c) 2018-2024 by xcube team and contributors
# Permissions are hereby granted under the terms of the MIT License:
# https://opensource.org/licenses/MIT.

from enum import Enum
from typing import Callable


class PreloadState(Enum):
"""Preload process state."""

created = "created"
started = "started"
stopped = "stopped"
cancelled = "cancelled"
failed = "failed"


class PreloadEventType(Enum):
"""Type of preload process event."""

state = "state"
progress = "progress"
info = "info"
warning = "warning"
error = "error"


class PreloadEvent:
"""Event to occur during the preload process."""

@classmethod
def state(cls, state: PreloadState):
"""Create an event of type ``state``."""
return PreloadEvent(PreloadEventType.state, state=state)

@classmethod
def progress(cls, progress: float):
"""Create an event of type ``process``."""
return PreloadEvent(PreloadEventType.progress, progress=progress)

@classmethod
def info(cls, message: str):
"""Create an event of type ``info``."""
return PreloadEvent(PreloadEventType.info, message=message)

@classmethod
def warning(cls, message: str, warning: Warning | None = None):
"""Create an event of type ``warning``."""
return PreloadEvent(PreloadEventType.warning, message=message, warning=warning)

@classmethod
def error(cls, message: str, exception: Exception | None = None):
"""Create an event of type ``error``."""
return PreloadEvent(
PreloadEventType.error, message=message, exception=exception
)

# noinspection PyShadowingBuiltins
def __init__(
self,
type: PreloadEventType,
state: PreloadState | None = None,
progress: float | None = None,
message: str | None = None,
warning: Warning | None = None,
exception: Exception | None = None,
):
self.type = type
self.state = state
self.progress = progress
self.message = message
self.warning = warning
self.exception = exception


class PreloadMonitor:

def __init__(
self,
on_event: Callable[["PreloadMonitor", PreloadEvent], None] | None = None,
on_done: Callable[["PreloadMonitor"], None] | None = None,
):
self._is_cancelled = False
if on_event:
self.on_event = on_event
if on_done:
self.on_done = on_done

@property
def is_cancelled(self):
"""Is cancellation requested?"""
return self._is_cancelled

def cancel(self):
"""Request cancellation."""
self._is_cancelled = True

def on_event(self, event: PreloadEvent):
"""Called when an event occurs."""

def on_done(self):
"""Called when the preload process is done and
the data is ready to be accessed.
The method is not called only on success.
"""


class PreloadHandle:
"""Represents an ongoing preload process."""

def close(self):
"""Closes the preload.
Should be called if the preloaded data is no longer needed.
This method usually cleans the cache associated with
this preload object.
The default implementation does nothing.
"""

def __enter__(self) -> "PreloadHandle":
"""Enter the context.
Returns:
This object.
"""
return self

def __exit__(self, exc_type, exc_val, exc_tb):
"""Exit the context. Calls ``close()``."""
self.close()
56 changes: 56 additions & 0 deletions xcube/core/store/store.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
from .datatype import DataTypeLike
from .descriptor import DataDescriptor
from .error import DataStoreError
from .preload import PreloadHandle
from .preload import PreloadMonitor
from .search import DataSearcher


Expand Down Expand Up @@ -464,6 +466,60 @@ def open_data(
DataStoreError: If an error occurs.
"""

# noinspection PyMethodMayBeStatic
def preload_data(
self,
*data_ids: str,
monitor: PreloadMonitor | None = None,
**preload_params: Any,
) -> PreloadHandle:
"""Preload the given data items for faster access.
Warning: This is an experimental and potentially unstable API
introduced in xcube 1.8.
Many implementations of this ``DataStore`` interface rely on remote
data APIs. Such API may provide only limited data access performance.
Hence, the approach taken by ``store.open_data(data_id, ...)`` alone
is suboptimal for a user's perspective. This is because the method is
blocking as it is not asynchronous, it may take long time before it
returns, it cannot report any progress while doing so.
The reasons for slow and unresponsive data APIs are manifold: intended
access is by file download, access is bandwidth limited, or not allowing
for sub-setting.
Data stores may differently implement the ``preload_data()`` method,
usually not at all. If preloading is required, the data will be
downloaded in most cases and made available via some temporary cache.
Args:
data_ids: Data identifiers to be preloaded.
monitor: A monitor that can be used to observe and/or cancel
the preload process.
preload_params: data store specific preload parameters.
See method ``get_preload_data_params_schema()`` for information
on the possible options.
Returns:
A handle for the preload process. The default implementation
returns an empty process handle.
"""
return NullPreloadHandle()

# noinspection PyMethodMayBeStatic
def get_preload_data_params_schema(self) -> JsonObjectSchema:
"""Get the JSON schema that describes the keyword
arguments that can be passed to ``preload_data()``.
Warning: This is an experimental and potentially unstable API
introduced in xcube 1.8.
Returns:
A ``JsonObjectSchema`` object whose properties describe
the parameters of ``preload_data()``.
"""
return JsonObjectSchema(additional_properties=False)


class MutableDataStore(DataStore, DataWriter, ABC):
"""A mutable data store is a data store that also allows for adding,
Expand Down

0 comments on commit 810b380

Please sign in to comment.