What is artutils?
artutils is the data access layer for the Artalytics platform. It provides the bridge between your application code and the database/CDN infrastructure.
┌─────────────────────────────────────┐
│ Your App (appPlatform, mod*) │ ← Application logic
├─────────────────────────────────────┤
│ artutils │ ← Data access (this package)
├─────────────────────────────────────┤
│ artcore │ ← Infrastructure (DB, CDN, auth)
└─────────────────────────────────────┘
Key principle: artutils handles what data you need. artcore handles how to connect to databases and CDN. Your app handles why (business logic).
A Complete Workflow: Artist Profile Page
Let’s walk through building an artist profile page - a real workflow that demonstrates how artutils functions work together.
Step 1: Resolve the URL slug
When a user visits /artist/jane-doe, you need to convert
that slug to data:
library(artutils)
# The URL gives us a slug
slug <- "jane-doe"
# Convert slug to artist record
artist_record <- get_artist_by_slug(slug)
if (is.null(artist_record)) {
# Handle 404 - artist doesn't exist
stop("Artist not found")
}
# Extract UUID for subsequent queries
# Convention: 'artist' holds the UUID, 'artist_record' holds the full row
artist <- artist_record$artist_uuid
cat("Found:", artist_record$artist_name, "\n")
cat("UUID:", artist, "\n")
cat("Bio:", substr(artist_record$bio, 1, 100), "...\n")Step 2: Gather profile data
With the artist UUID, fetch everything needed for the profile header:
# Get aggregate statistics
stats <- get_artist_stats(artist)
cat("Artworks:", stats$artworks, "\n")
cat("Collections:", stats$collections, "\n")
cat("Member since:", format(stats$member_since, "%B %Y"), "\n")
# Get collections for navigation
collections <- get_artist_collections_summary(artist)
print(collections[, .(collection_name, artwork_count)])
# Get recent works for the gallery grid
recent <- get_artist_recent_works(artist, limit = 9)
print(recent[, .(art_title, created_utc)])Step 3: Build asset URLs
Path functions construct CDN URLs without database queries - they’re pure string operations:
# Artist avatar for profile header
avatar_url <- pathArtistThumb(artist)
cat("Avatar:", avatar_url, "\n")
# Thumbnails for the gallery grid
for (i in seq_len(min(3, nrow(recent)))) {
artwork <- recent$art_uuid[i]
thumb <- pathArtworkThumb(artist, artwork)
cat(recent$art_title[i], "->", thumb, "\n")
}Step 4: Render the page
Now you have everything needed to render the profile:
profile_data <- list(
header = list(
name = artist_record$artist_name,
bio = artist_record$bio,
avatar = pathArtistThumb(artist),
location = paste(artist_record$city, artist_record$state, sep = ", "),
social = list(
instagram = artist_record$url_ig,
website = artist_record$url_site
)
),
stats = list(
artworks = stats$artworks,
collections = stats$collections,
member_since = stats$member_since
),
collections = collections,
gallery = lapply(seq_len(nrow(recent)), function(i) {
list(
uuid = recent$art_uuid[i],
title = recent$art_title[i],
thumbnail = pathArtworkThumb(artist, recent$art_uuid[i])
)
})
)
# Pass to your Shiny UI or template engine
# renderArtistProfile(profile_data)A Complete Workflow: Artwork Detail Page
Now let’s build the artwork detail view - the richest page in the platform.
The Easy Way: getAppdata()
For Shiny apps, getAppdata() aggregates everything in
one call:
artist <- "746b8207-72f5-4ab6-8d19-a91d03daec3d"
artwork <- "99a61148-1d3b-4340-8cf6-92ad26046b0f"
# One call gets everything
appdata <- getAppdata(artist, artwork)
# Artist context (for header)
appdata$artist$info$artist_name
appdata$artist$stats$tot_artworks
appdata$artist$thumb
# Artwork details
appdata$artwork$info$basic$art_title
appdata$artwork$stats$brush_strokes
appdata$artwork$stats$drawing_hours
appdata$artwork$stats$n_unique_colors
# Benchmark scores (percentile vs artist's portfolio)
appdata$artwork$benchmarks$time_effort$score
appdata$artwork$benchmarks$time_effort$confidence
# Media paths for replay player
appdata$paths$frames # Frame sequence prefix
appdata$config$n_frames # Total frame count
# Certificate (if issued)
appdata$certificate$cert_id
appdata$certificate$path_pdfThe Manual Way: Individual Queries
For more control, query each piece separately:
# Core artwork record
index <- getArtworkIndex(artist, artwork)
cat("Title:", index$art_title, "\n")
cat("Created:", format(index$created_utc, "%Y-%m-%d"), "\n")
# Performance metrics
art_stats <- getArtworkStats(artist, artwork)
cat("Brush strokes:", format(art_stats$brush_strokes, big.mark = ","), "\n")
cat("Drawing hours:", round(art_stats$drawing_hours, 1), "\n")
cat("Unique colors:", art_stats$n_unique_colors, "\n")
# Image dimensions
meta <- getArtworkMeta(artist, artwork)
cat("Size:", meta$image_width, "x", meta$image_height, "\n")
# Style and description
profile <- getArtworkProfile(artist, artwork)
cat("Category:", profile$category, "\n")
# Frame-by-frame analytics (for replay player)
frames <- getFrameAnalytics(artist, artwork)
cat("Frames:", nrow(frames), "\n")
cat("Technique phases:", paste(unique(frames$technique_phase), collapse = ", "), "\n")Connection Management
Auto-managed (Simple)
Most functions handle connections automatically:
# Connection opens, query runs, connection closes
stats <- get_artist_stats(artist)
# Nothing to clean upShared Connection (Efficient)
For multiple queries, share a connection:
# Open once
cn <- artcore::..dbc()
on.exit(artcore::..dbd(cn)) # Ensures cleanup on error
# Reuse for multiple queries
artist_record <- get_artist_by_slug("jane-doe", cn = cn)
artist <- artist_record$artist_uuid
stats <- get_artist_stats(artist, cn = cn)
collections <- get_artist_collections_summary(artist, cn = cn)
recent <- get_artist_recent_works(artist, limit = 6, cn = cn)
# Connection closes when function exitsError Handling
artutils follows fail-fast philosophy. Missing data is an error, not a silent NULL:
# This will error if artwork doesn't exist
tryCatch({
stats <- getArtworkStats(artist, "invalid-uuid")
}, error = function(e) {
cat("Error:", e$message, "\n")
# Log and investigate - don't silently ignore
})Why fail-fast? Missing data usually means: 1. Invalid input (bad UUID) - fix the caller 2. Pipeline failure - data should exist but doesn’t 3. Race condition - record deleted between queries
Silent NULLs hide these issues. Errors surface them immediately.
Next Steps
Now that you understand the basics:
Data Access Patterns - Real-world query workflows including visibility filtering, search, and batch operations
Data Modification Workflows - Creating collections, adding artwork, and recalculating statistics
Reference - Complete function documentation organized by category