From d3e0e8bf765953061ccb043312d37577e3a3b0d5 Mon Sep 17 00:00:00 2001 From: Luke Zappia Date: Wed, 30 Oct 2024 11:39:18 +0100 Subject: [PATCH] Add Records container class (#59) * Add RecordsList class * Rename RecordsList to RelatedRecords * Add tests for RelatedRecords * Add limit_to_many to API$get_record() * Handle when error detail is a list in API * Order columns and return empty in RelatedRecords$df() * Add RelatedRecords to usage vignette And other fixes/additions * Add RelatedRecords tests * Update CHANGELOG * change the flow of the `get_value` function * Add RelatedRecords to architecture.qmd Plus various tidying/adjustments * Add missing Core --> Artifact link * Add more info about artifact * style vignette * Move Artifact to inherit from Record in diagram * Remove Field accessor from RelatedRecords * Remove Field from RelatedRecords test * Replace Quarto callout with Bootstrap alert box --------- Co-authored-by: Robrecht Cannoodt --- .Rbuildignore | 2 + .gitignore | 2 + CHANGELOG.md | 5 + R/InstanceAPI.R | 14 +- R/Record.R | 72 ++++++---- R/RelatedRecords.R | 134 ++++++++++++++++++ man/RelatedRecords.Rd | 104 ++++++++++++++ tests/testthat/test-RelatedRecords.R | 13 ++ tests/testthat/test-connect_lamindata.R | 3 +- vignettes/architecture.qmd | 178 +++++++++++++++++------- vignettes/laminr.Rmd | 34 +++-- 11 files changed, 470 insertions(+), 91 deletions(-) create mode 100644 R/RelatedRecords.R create mode 100644 man/RelatedRecords.Rd create mode 100644 tests/testthat/test-RelatedRecords.R diff --git a/.Rbuildignore b/.Rbuildignore index dd60def..3b5b59d 100644 --- a/.Rbuildignore +++ b/.Rbuildignore @@ -11,3 +11,5 @@ ^pkgdown$ ^vignettes/*_files$ ^vignettes/\.quarto$ +^doc$ +^Meta$ diff --git a/.gitignore b/.gitignore index 2e72980..b5baec7 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,5 @@ experiments docs +/doc/ +/Meta/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d1fb33..cdad822 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,8 @@ For more information, please visit the [package website](https://laminr.lamin.ai * Add `InstanceAPI$get_records()` and `Registry$df()` methods (PR #54) +* Add a `RelatedRecords` class and `RelatedRecords$df()` method (PR #59) + ## MAJOR CHANGES * Refactored the internal class data structures for better modularity and extensibility (PR #8). @@ -94,10 +96,13 @@ For more information, please visit the [package website](https://laminr.lamin.ai * Add alternative error message when no message is returned from the API (PR #30). +* Handle when error detail returned by the API is a list (PR #59) + * Manually install OpenBLAS on macOS (PR #62). * Switch to Python 3.12 for being able to install scipy on macOS (PR #66). + # laminr v0.0.1 Initial POC implementation of the LaminDB API client for R. diff --git a/R/InstanceAPI.R b/R/InstanceAPI.R index 75fbcb4..fddcacd 100644 --- a/R/InstanceAPI.R +++ b/R/InstanceAPI.R @@ -42,6 +42,7 @@ InstanceAPI <- R6::R6Class( # nolint object_name_linter get_record = function(module_name, registry_name, id_or_uid, + limit_to_many = 10, include_foreign_keys = FALSE, select = NULL, verbose = FALSE) { @@ -85,6 +86,8 @@ InstanceAPI <- R6::R6Class( # nolint object_name_linter id_or_uid, "?schema_id=", private$.instance_settings$schema_id, + "&limit_to_many=", + limit_to_many, "&include_foreign_keys=", tolower(include_foreign_keys) ) @@ -220,10 +223,17 @@ InstanceAPI <- R6::R6Class( # nolint object_name_linter content <- httr::content(response) if (httr::http_error(response)) { if (is.list(content) && "detail" %in% names(content)) { - cli_abort(content$detail) + detail <- content$detail + if (is.list(detail)) { + detail <- jsonlite::minify(jsonlite::toJSON(content$detail)) + } } else { - cli_abort("Failed to {request_type} from instance. Output: {content}") + detail <- content } + cli_abort(c( + "Failed to {request_type} from instance", + "i" = "Details: {detail}" + )) } content diff --git a/R/Record.R b/R/Record.R index daa486b..605ea32 100644 --- a/R/Record.R +++ b/R/Record.R @@ -148,36 +148,13 @@ Record <- R6::R6Class( # nolint object_name_linter .api = NULL, .data = NULL, get_value = function(key) { + # Return the value if it is in the data if (key %in% names(private$.data)) { - private$.data[[key]] - } else if (key %in% private$.registry$get_field_names()) { - field <- private$.registry$get_field(key) - - # refetch the record to get the related data - related_data <- private$.api$get_record( - module_name = field$module_name, - registry_name = field$registry_name, - id_or_uid = private$.data[["uid"]], - select = key - )[[key]] - - # return NULL if the related data is NULL - if (is.null(related_data)) { - return(NULL) - } - - # if the related data is not NULL, create a record class for it - related_module <- private$.instance$get_module(field$related_module_name) - related_registry <- related_module$get_registry(field$related_registry_name) - related_registry_class <- related_registry$get_record_class() - - # if the relation type is one-to-many or many-to-many, iterate over the list - if (field$relation_type %in% c("one-to-one", "many-to-one")) { - related_registry_class$new(related_data) - } else { - map(related_data, ~ related_registry_class$new(.x)) - } - } else { + return(private$.data[[key]]) + } + + # If the key is not in the data, check if it is a field in the registry + if (!key %in% private$.registry$get_field_names()) { cli_abort( paste0( "Field '", key, "' not found in registry '", @@ -185,6 +162,43 @@ Record <- R6::R6Class( # nolint object_name_linter ) ) } + + # Get the field from the registry + field <- private$.registry$get_field(key) + + # For *-to-many relationships, return a RelatedRecords object + if (field$relation_type %in% c("one-to-many", "many-to-many")) { + records_list <- RelatedRecords$new( + instance = private$.instance, + registry = private$.registry, + field = field, + related_to = self$uid, + api = private$.api + ) + + return(records_list) + } + + # refetch the record to get the related data + related_data <- private$.api$get_record( + module_name = field$module_name, + registry_name = field$registry_name, + id_or_uid = private$.data[["uid"]], + select = key + )[[key]] + + # return NULL if the related data is NULL + if (is.null(related_data)) { + return(NULL) + } + + # if the related data is not NULL, create a record class for it + related_module <- private$.instance$get_module(field$related_module_name) + related_registry <- related_module$get_registry(field$related_registry_name) + related_registry_class <- related_registry$get_record_class() + + # Return the related record class + related_registry_class$new(related_data) } ) ) diff --git a/R/RelatedRecords.R b/R/RelatedRecords.R new file mode 100644 index 0000000..0bb96d9 --- /dev/null +++ b/R/RelatedRecords.R @@ -0,0 +1,134 @@ +#' @title RelatedRecords +#' +#' @description +#' A container for accessing records with a one-to-many or many-to-many +#' relationship. +RelatedRecords <- R6::R6Class( # nolint object_name_linter + "RelatedRecords", + cloneable = FALSE, + public = list( + #' @description + #' Creates an instance of this R6 class. This class should not be instantiated directly, + #' but rather by connecting to a LaminDB instance using the [connect()] function. + #' + #' @param instance The instance the records list belongs to. + #' @param registry The registry the records list belongs to. + #' @param field The field associated with the records list. + #' @param related_to ID or UID of the parent that records are related to. + #' @param api The API for the instance. + initialize = function(instance, registry, field, related_to, api) { + private$.instance <- instance + private$.registry <- registry + private$.api <- api + private$.field <- field + private$.related_to <- related_to + }, + #' @description + #' Get a data frame summarising records in the registry + #' + #' @param limit Maximum number of records to return + #' @param verbose Boolean, whether to print progress messages + #' + #' @return A data.frame containing the available records + df = function(limit = 100, verbose = FALSE) { + private$get_records(as_df = TRUE) + }, + #' @description + #' Print a `RelatedRecords` + #' + #' @param style Logical, whether the output is styled using ANSI codes + print = function(style = TRUE) { + cli::cat_line(self$to_string(style)) + }, + #' @description + #' Create a string representation of a `RelatedRecords` + #' + #' @param style Logical, whether the output is styled using ANSI codes + #' + #' @return A `cli::cli_ansi_string` if `style = TRUE` or a character vector + to_string = function(style = FALSE) { + fields <- list( + field_name = private$.field$field_name, + relation_type = private$.field$relation_type, + related_to = private$.related_to + ) + + field_strings <- make_key_value_strings(fields) + + make_class_string( + "RelatedRecords", field_strings, + style = style + ) + } + ), + private = list( + .instance = NULL, + .registry = NULL, + .api = NULL, + .field = NULL, + .related_to = NULL, + get_records = function(as_df = FALSE) { + field <- private$.field + + # Fetch the field to get the related data + related_data <- private$.api$get_record( + module_name = field$module_name, + registry_name = field$registry_name, + id_or_uid = private$.related_to, + select = field$field_name, + limit_to_many = 100000L # Make this high to get all related records + )[[field$field_name]] + + if (as_df) { + # Get field names so output always has the same order and empty output + # has column names + related_module <- private$.instance$get_module(field$related_module_name) + related_registry <- related_module$get_registry(field$related_registry_name) + related_fields <- related_registry$get_field_names() + # Remove hidden and link fields + is_hidden <- grepl("^_", related_fields) + is_link <- grepl("^links_", related_fields) + related_fields <- related_fields[!is_hidden & !is_link] + + if (length(related_data) == 0) { + template_df <- as.data.frame( + matrix( + ncol = length(related_fields), nrow = 0, + dimnames = list(NULL, related_fields) + ) + ) + + return(template_df) + } + + values <- related_data |> + # Replace NULL with NA so columns aren't lost + purrr::modify_depth(2, \(x) ifelse(is.null(x), NA, x)) |> + # Convert each entry to a data.frame + purrr::map(as.data.frame) |> + # Bind entries as rows + purrr::list_rbind() + + purrr::map(related_fields, function(.field) { + if (.field %in% colnames(values)) { + return(values[, .field, drop = FALSE]) + } else { + column <- data.frame(rep(NA, nrow(values))) + colnames(column) <- .field + return(column) + } + }) |> + purrr::list_cbind() + } else { + # Get record class for records in the list + related_module <- private$.instance$get_module(field$related_module_name) + related_registry <- related_module$get_registry(field$related_registry_name) + related_registry_class <- related_registry$get_record_class() + + values <- map(related_data, ~ related_registry_class$new(.x)) + } + + return(values) + } + ) +) diff --git a/man/RelatedRecords.Rd b/man/RelatedRecords.Rd new file mode 100644 index 0000000..e7b5b87 --- /dev/null +++ b/man/RelatedRecords.Rd @@ -0,0 +1,104 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/RelatedRecords.R +\name{RelatedRecords} +\alias{RelatedRecords} +\title{RelatedRecords} +\description{ +A container for accessing records with a one-to-many or many-to-many +relationship. +} +\section{Methods}{ +\subsection{Public methods}{ +\itemize{ +\item \href{#method-RelatedRecords-new}{\code{RelatedRecords$new()}} +\item \href{#method-RelatedRecords-df}{\code{RelatedRecords$df()}} +\item \href{#method-RelatedRecords-print}{\code{RelatedRecords$print()}} +\item \href{#method-RelatedRecords-to_string}{\code{RelatedRecords$to_string()}} +} +} +\if{html}{\out{
}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-RelatedRecords-new}{}}} +\subsection{Method \code{new()}}{ +Creates an instance of this R6 class. This class should not be instantiated directly, +but rather by connecting to a LaminDB instance using the \code{\link[=connect]{connect()}} function. +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{RelatedRecords$new(instance, registry, field, related_to, api)}\if{html}{\out{
}} +} + +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{instance}}{The instance the records list belongs to.} + +\item{\code{registry}}{The registry the records list belongs to.} + +\item{\code{field}}{The field associated with the records list.} + +\item{\code{related_to}}{ID or UID of the parent that records are related to.} + +\item{\code{api}}{The API for the instance.} +} +\if{html}{\out{
}} +} +} +\if{html}{\out{
}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-RelatedRecords-df}{}}} +\subsection{Method \code{df()}}{ +Get a data frame summarising records in the registry +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{RelatedRecords$df(limit = 100, verbose = FALSE)}\if{html}{\out{
}} +} + +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{limit}}{Maximum number of records to return} + +\item{\code{verbose}}{Boolean, whether to print progress messages} +} +\if{html}{\out{
}} +} +\subsection{Returns}{ +A data.frame containing the available records +} +} +\if{html}{\out{
}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-RelatedRecords-print}{}}} +\subsection{Method \code{print()}}{ +Print a \code{RelatedRecords} +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{RelatedRecords$print(style = TRUE)}\if{html}{\out{
}} +} + +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{style}}{Logical, whether the output is styled using ANSI codes} +} +\if{html}{\out{
}} +} +} +\if{html}{\out{
}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-RelatedRecords-to_string}{}}} +\subsection{Method \code{to_string()}}{ +Create a string representation of a \code{RelatedRecords} +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{RelatedRecords$to_string(style = FALSE)}\if{html}{\out{
}} +} + +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{style}}{Logical, whether the output is styled using ANSI codes} +} +\if{html}{\out{
}} +} +\subsection{Returns}{ +A \code{cli::cli_ansi_string} if \code{style = TRUE} or a character vector +} +} +} diff --git a/tests/testthat/test-RelatedRecords.R b/tests/testthat/test-RelatedRecords.R new file mode 100644 index 0000000..864ae93 --- /dev/null +++ b/tests/testthat/test-RelatedRecords.R @@ -0,0 +1,13 @@ +skip_if_offline() + +test_that("RelatedRecord methods work", { + local_setup_lamindata_instance() + + db <- connect("laminlabs/lamindata") + artifact <- db$Artifact$get("mePviem4DGM4SFzvLXf3") + related <- artifact$experiments + + expect_s3_class(related, "RelatedRecords") + expect_s3_class(related$df(), "data.frame") + expect_true(length(colnames(related$df())) > 0) +}) diff --git a/tests/testthat/test-connect_lamindata.R b/tests/testthat/test-connect_lamindata.R index 126ac79..0506155 100644 --- a/tests/testthat/test-connect_lamindata.R +++ b/tests/testthat/test-connect_lamindata.R @@ -22,6 +22,5 @@ test_that("Connecting to lamindata works", { # access a related field which is empty for this record expect_null(artifact$type) # one to one - expect_type(artifact$wells, "list") # one-to-many - expect_length(artifact$wells, 0) + expect_s3_class(artifact$wells, "RelatedRecords") # one-to-many }) diff --git a/vignettes/architecture.qmd b/vignettes/architecture.qmd index f4dc47f..77c0455 100644 --- a/vignettes/architecture.qmd +++ b/vignettes/architecture.qmd @@ -102,11 +102,23 @@ classDiagram Module --> Registry Registry --> Field Registry --> Record + Field --> RelatedRecords + Record --> RelatedRecords + UserSettings --> InstanceSettings + InstanceSettings --> Instance + InstanceAPI --> Module + Instance --> Registry + InstanceAPI --> Registry + Instance --> Record + InstanceAPI --> Record + Instance --> RelatedRecords + InstanceAPI --> RelatedRecords + + %% Use #emsp; to create indents in the rendered diagram when necessary class laminr{ - +connect(String slug): Instance + +connect(String slug): RichInstance } - class UserSettings{ +initialize(...): UserSettings +email: String @@ -116,7 +128,6 @@ classDiagram +handle: String +name: String } - class InstanceSettings{ +initialize(...): InstanceSettings +owner: String @@ -126,7 +137,10 @@ classDiagram +api_url: String } class Instance{ - +initialize(InstanceSettings Instance_settings, API api, Map schema): Instance + +initialize( + #emsp;InstanceSettings Instance_settings, API api, + #emsp;Map schema + ): Instance +get_modules(): Module[] +get_module(String module_name): Module +get_module_names(): String[] @@ -137,14 +151,20 @@ classDiagram +get_record(...): Map } class Module{ - +initialize(Instance Instance, API api, String module_name, Map module_schema): Module + +initialize( + #emsp;Instance Instance, API api, String module_name, + #emsp;Map module_schema + ): Module +name: String +get_registries(): Registry[] +get_registry(String registry_name): Registry +get_registry_names(): String[] } class Registry{ - +initialize(Instance Instance, Module module, API api, String registry_name, Map registry_schema): Registry + +initialize( + #emsp;Instance Instance, Module module, API api, + #emsp;String registry_name, Map registry_schema + ): Registry +name: String +class_name: String +is_link_table: Bool @@ -153,9 +173,14 @@ classDiagram +get_field_names(): String[] +get(String id_or_uid, Bool include_foreign_keys, List~String~ select, Bool verbose): RichRecord +get_registry_class(): RichRecordClass + +df(Integer limit, Bool verbose): DataFrame } class Field{ - +initialize(...): Field + +initialize( + #emsp;String type, String through, String field_name, String registry_name, + #emsp;String column_name, String module_name, Bool is_link_table, String relation_type, + #emsp;String related_field_name, String related_registry_name, String related_module_name + ): Field +type: String +through: Map +field_name: String @@ -172,6 +197,14 @@ classDiagram +initialize(Instance Instance, Registry registry, API api, Map data): Record +get_value(String field_name): Any } + class RelatedRecords{ + +initialize( + #emsp;Instance instance, Registry registry, Field field, + #emsp;String related_to, API api + ): RelatedRecords + +df(): DataFrame + +field: Field + } %% # nolint end ``` @@ -209,27 +242,43 @@ The class diagram below illustrates the relationships between the sugar syntax c ```{mermaid} classDiagram %% # nolint start - laminr --> RichInstance + %% --- Copied from base diagram -------------------------------------------- laminr --> UserSettings laminr --> InstanceSettings - RichInstance --|> Instance Instance --> InstanceAPI Instance --> Module - Core --|> Module - Bionty --|> Module Module --> Registry Registry --> Field - Registry --> RichRecord - Artifact --|> Record + Field --> RelatedRecords + Record --> RelatedRecords + UserSettings --> InstanceSettings + InstanceSettings --> Instance + InstanceAPI --> Module + Instance --> Registry + InstanceAPI --> Registry + Instance --> Record + InstanceAPI --> Record + Instance --> RelatedRecords + InstanceAPI --> RelatedRecords + %% ------------------------------------------------------------------------- + + %% --- New links for Rich classes ------------------------------------------ + RichInstance --|> Instance + laminr --> RichInstance + Core --|> Module RichInstance --> Core + Bionty --|> Module RichInstance --> Bionty - Core --> Artifact + Registry --> RichRecord RichRecord --|> Record + Registry --> Artifact + Artifact --|> Record + %% ------------------------------------------------------------------------- + %% --- Copied from base diagram -------------------------------------------- class laminr{ +connect(String slug): RichInstance } - class UserSettings{ +initialize(...): UserSettings +email: String @@ -239,7 +288,6 @@ classDiagram +handle: String +name: String } - class InstanceSettings{ +initialize(...): InstanceSettings +owner: String @@ -249,7 +297,10 @@ classDiagram +api_url: String } class Instance{ - +initialize(InstanceSettings Instance_settings, API api, Map schema): Instance + +initialize( + #emsp;InstanceSettings Instance_settings, API api, + #emsp;Map schema + ): Instance +get_modules(): Module[] +get_module(String module_name): Module +get_module_names(): String[] @@ -259,35 +310,21 @@ classDiagram +get_schema(): Map +get_record(...): Map } - class RichInstance{ - +initialize(InstanceSettings Instance_settings, API api, Map schema): RichInstance - +Registry Artifact - +Registry Collection - +...registry accessors... - +Registry User - +Bionty bionty - } - class Core{ - +Registry Artifact - +Registry Collection - +...registry accessors... - +Registry User - } - class Bionty{ - +Registry CellLine - +Registry CellMarker - +...registry accessors... - +Registry Tissue - } class Module{ - +initialize(Instance Instance, API api, String module_name, Map module_schema): Module + +initialize( + #emsp;Instance Instance, API api, String module_name, + #emsp;Map module_schema + ): Module +name: String +get_registries(): Registry[] +get_registry(String registry_name): Registry +get_registry_names(): String[] } class Registry{ - +initialize(Instance Instance, Module module, API api, String registry_name, Map registry_schema): Registry + +initialize( + #emsp;Instance Instance, Module module, API api, + #emsp;String registry_name, Map registry_schema + ): Registry +name: String +class_name: String +is_link_table: Bool @@ -296,14 +333,14 @@ classDiagram +get_field_names(): String[] +get(String id_or_uid, Bool include_foreign_keys, List~String~ select, Bool verbose): RichRecord +get_registry_class(): RichRecordClass - } - class Artifact{ - +initialize(...): Artifact - +cache(): String - +load(): Any + +df(Integer limit, Bool verbose): DataFrame } class Field{ - +initialize(...): Field + +initialize( + #emsp;String type, String through, String field_name, String registry_name, + #emsp;String column_name, String module_name, Bool is_link_table, String relation_type, + #emsp;String related_field_name, String related_registry_name, String related_module_name + ): Field +type: String +through: Map +field_name: String @@ -316,12 +353,57 @@ classDiagram +related_registry_name: String +related_module_name: String } - class RichRecord{ - +...field value accessors... - } class Record{ +initialize(Instance Instance, Registry registry, API api, Map data): Record +get_value(String field_name): Any } + class RelatedRecords{ + +initialize( + #emsp;Instance instance, Registry registry, Field field, + #emsp;String related_to, API api + ): RelatedRecords + +df(): DataFrame + } + %% ------------------------------------------------------------------------- + + %% --- New Rich classes ---------------------------------------------------- + class RichInstance{ + +initialize( + #emsp;InstanceSettings Instance_settings, API api, + #emsp;Map schema + ): RichInstance + +Registry Artifact + +Registry Collection + +...registry accessors... + +Registry User + +Bionty bionty + } + style RichInstance fill:#ffe1c9 + class Core{ + +Registry Artifact + +Registry Collection + +...registry accessors... + +Registry User + } + style Core fill:#ffe1c9 + class Bionty{ + +Registry CellLine + +Registry CellMarker + +...registry accessors... + +Registry Tissue + } + style Bionty fill:#ffe1c9 + class RichRecord{ + +...field value accessors... + } + style RichRecord fill:#ffe1c9 + class Artifact{ + +...field value accessors... + +cache(): String + +load(): AnnData | DataFrame | ... + +describe(): NULL + } + style Artifact fill:#ffe1c9 + %% ------------------------------------------------------------------------- %% # nolint end ``` diff --git a/vignettes/laminr.Rmd b/vignettes/laminr.Rmd index 1a9d187..4a94f70 100644 --- a/vignettes/laminr.Rmd +++ b/vignettes/laminr.Rmd @@ -104,7 +104,15 @@ To see the available functions for the `Artifact` registry, print the registry o db$Artifact ``` -You can fetch a specific artifact using its ID or UID. For instance, to get the artifact with UID [KBW89Mf7IGcekja2hADu](https://lamin.ai/laminlabs/cellxgene/artifact/KBW89Mf7IGcekja2hADu): +You can also get a data frame summarising the records associated with a registry. + +```{r artifact_registry_df} +db$Artifact$df(limit = 5) +``` + +## Working with records + +You can fetch a specific record from a registry using its ID or UID. For instance, to get the artifact with UID [KBW89Mf7IGcekja2hADu](https://lamin.ai/laminlabs/cellxgene/artifact/KBW89Mf7IGcekja2hADu): ```{r get_artifact} artifact <- db$Artifact$get("KBW89Mf7IGcekja2hADu") @@ -116,13 +124,13 @@ This artifact contains an `AnnData` object with myeloid cell data. You can view artifact ``` -Or get more detailed information: +For artifact records, you can get more detailed information: ```{r describe_artifact} artifact$describe() ``` -Access specific fields of the artifact using the `$` operator: +Access specific fields of the record using the `$` operator: ```{r access_fields} artifact$id @@ -130,20 +138,26 @@ artifact$uid artifact$key ``` -You can also access related data: +Some fields of a record contain links to related information. -```{r access_related_data} +```{r artifact_related} artifact$storage -artifact$created_by +artifact$developmental_stages +``` + +When those that are one-to-many or many-to-many relationship, a summary of the related information can be retrieved as a data frame. + +```{r artifact_related_df} +artifact$developmental_stages$df() ``` -Finally, you can download the actual data associated with the artifact: +Finally, for artifact records only, you can download the associated data: ```{r cache_artifact} artifact$cache() # Cache the data locally artifact$load() # Load the data into memory ``` -:::{.callout-note} -Currently, laminr primarily supports S3 storage and AnnData objects. Support for other storage backends and data formats will be added in the future. For more information related to planned features and the roadmap, please refer to the Development vignette (`vignette("development", package = "laminr")`). -::: +