Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 40 additions & 1 deletion dataconnect/models.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from __future__ import annotations

from collections.abc import Callable
from dataclasses import dataclass, field
from typing import Generic, TypeVar
from typing import Generic, Self, TypeVar
from uuid import UUID

import pandas as pd
Expand Down Expand Up @@ -37,6 +38,34 @@ class DatasetVersion:
dataset_version: str


class DatasetFrame:
"""Lazy dataset reference; fetching requires the originating client to remain open."""

__slots__ = ("_dataset_uuid", "_fetch_data")

def __init__(self, dataset_uuid: str, fetch_data: Callable[[UUID, int | None], pd.DataFrame]) -> None:
self._dataset_uuid = dataset_uuid
self._fetch_data = fetch_data

def head(self, count: int = 6) -> pd.DataFrame:
"""Fetch the first count rows, matching R's default of six rows."""
return self._fetch_data(UUID(self._dataset_uuid), count)

def collect(self) -> pd.DataFrame:
"""Fetch the complete dataset without retaining a previous head limit."""
return self._fetch_data(UUID(self._dataset_uuid), None)

def __repr__(self) -> str:
return f"DatasetFrame(dataset_uuid={self._dataset_uuid!r})"

def __copy__(self) -> Self:
return self

def __deepcopy__(self, memo: dict[int, object]) -> Self:
"""Keep this opaque reference intact when copying metadata with asdict()."""
return self


@dataclass(frozen=True)
class Dataset:
"""A dataset belonging to a study environment."""
Expand All @@ -45,6 +74,16 @@ class Dataset:
study_uuid: str
study_env_uuid: str
dataset_name: str
dataset_short_name: str | None = None
type: str | None = None
source: str | None = None
activation_status: str | None = None
dataset_status: str | None = None
collection: list[str] | None = field(default=None, hash=False)
last_updated: str | None = None
version: str | None = None
other_versions: list[dict[str, str]] | None = field(default=None, hash=False)
frame: DatasetFrame | None = field(default=None, repr=False, compare=False)


@dataclass(frozen=True)
Expand Down
10 changes: 9 additions & 1 deletion dataconnect/service/default.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from __future__ import annotations

import json
from dataclasses import replace
from datetime import UTC, datetime
from uuid import UUID

Expand All @@ -11,6 +12,7 @@
from dataconnect.exceptions import ErrorDetail, ValidationError
from dataconnect.models import (
Dataset,
DatasetFrame,
DatasetVersion,
DatetimeFormat,
DatetimeFormatsResult,
Expand Down Expand Up @@ -172,7 +174,13 @@ def get_datasets(

try:
resources = self._transport.list_resources(request)
items = [resource_to_dataset(r) for r in resources]
items = []
for resource in resources:
dataset = resource_to_dataset(resource)
frame = (
DatasetFrame(dataset.dataset_uuid, self.fetch_data) if dataset.dataset_uuid is not None else None
)
items.append(replace(dataset, frame=frame))
total_records = resources[0].total_records if resources else 0
total_pages = (total_records + page_size - 1) // page_size if page_size > 0 else 0
return PaginatedResponse(
Expand Down
9 changes: 9 additions & 0 deletions dataconnect/service/mappers.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,15 @@ def resource_to_dataset(resource: ResourceInfo) -> Dataset:
study_uuid=data.get("study_uuid", ""),
study_env_uuid=data.get("study_env_uuid", ""),
dataset_name=data.get("dataset_name", ""),
dataset_short_name=data.get("dataset_short_name"),
type=data.get("type"),
source=data.get("source"),
activation_status=data.get("activation_status"),
dataset_status=data.get("dataset_status"),
collection=data.get("collection"),
last_updated=data.get("last_updated"),
version=data.get("version"),
other_versions=data.get("other_versions"),
)


Expand Down
10 changes: 9 additions & 1 deletion readme/README-v1.1.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,15 @@ Retrieves datasets for a specific study environment and returns paginated result
| page_size | int | Optional. Number of results per page. Default: 50 |

#### Output
Returns a list containing `total_records` (total datasets available across all pages), `pagination` and `datasets` array.
Returns a `PaginatedResponse` containing `total_records` (total datasets available across all pages), `pagination`, and an `items` list. Each call retrieves one requested page; it does not automatically fetch subsequent pages.

Each dataset retains `dataset_uuid`, `study_uuid`, `study_env_uuid`, and `dataset_name`, and also exposes `dataset_short_name`, `type`, `source`, `activation_status`, `dataset_status`, `collection`, `last_updated`, `version`, `other_versions`, and `frame`.

Missing or null metadata is represented as `None`; empty collections remain `[]` and empty strings remain `""`. `collection` is a list of strings, and `other_versions` is a list of dictionaries with `version` and `dataset_uuid` keys, or `None`. Version labels and timestamps remain strings without conversion.

`dataset.frame` is a lazy reference: listing datasets does not fetch their rows. `dataset.frame.head(10)` returns the first ten rows as a pandas DataFrame (default: six), while `dataset.frame.collect()` fetches the complete dataset regardless of previous previews. Keep the originating client open while using its frames. A null dataset UUID has no usable frame and returns `frame=None`.

`dataclasses.asdict(dataset)` retains the frame as an opaque reference without copying its connection. Exclude `frame` when JSON-serializing the metadata.

---

Expand Down
Loading
Loading