Source code for xnatctl.services.projects
"""Project service for XNAT project operations."""
from __future__ import annotations
import builtins
from typing import Any
from xnatctl.core.exceptions import ResourceNotFoundError
from xnatctl.models.project import Project
from .base import BaseService
from .hierarchy import HierarchyService
[docs]
class ProjectService(BaseService):
"""Service for XNAT project operations."""
[docs]
def list(
self,
accessible: bool = True,
limit: int | None = None,
) -> builtins.list[Project]:
"""List projects.
Args:
accessible: Only list accessible projects
limit: Maximum number of results
Returns:
List of Project objects
"""
path = "/data/projects"
params: dict[str, Any] = {"format": "json"}
if accessible:
params["accessible"] = "true"
data = self._get(path, params=params)
results = HierarchyService.extract_rows(data)
if limit:
results = results[:limit]
return [Project(**r) for r in results]
[docs]
def get(self, project_id: str) -> Project:
"""Get project details.
Args:
project_id: Project ID
Returns:
Project object
Raises:
ResourceNotFoundError: If project not found
"""
path = f"/data/projects/{project_id}"
params = {"format": "json"}
try:
data = self._get(path, params=params)
item = HierarchyService.extract_first_item(data) if isinstance(data, dict) else None
if item is not None:
fields, _meta = item
return Project.model_validate(fields)
results = HierarchyService.extract_rows(data)
if results:
return Project.model_validate(results[0])
raise ResourceNotFoundError("project", project_id)
except Exception as e:
if "404" in str(e):
raise ResourceNotFoundError("project", project_id) from e
raise
[docs]
def create(
self,
project_id: str,
name: str | None = None,
description: str | None = None,
keywords: str | None = None,
pi_firstname: str | None = None,
pi_lastname: str | None = None,
accessibility: str = "private",
) -> Project:
"""Create a new project.
Args:
project_id: Project ID (must be unique)
name: Project name (defaults to project_id)
description: Project description
keywords: Comma-separated keywords
pi_firstname: PI first name
pi_lastname: PI last name
accessibility: Access level (private, protected, public)
Returns:
Created Project object
"""
path = f"/data/projects/{project_id}"
params: dict[str, Any] = {}
if name:
params["name"] = name
if description:
params["description"] = description
if keywords:
params["keywords"] = keywords
if pi_firstname:
params["pi_firstname"] = pi_firstname
if pi_lastname:
params["pi_lastname"] = pi_lastname
if accessibility:
params["accessibility"] = accessibility
self._put(path, params=params)
return self.get(project_id)
[docs]
def delete(
self,
project_id: str,
remove_files: bool = False,
) -> bool:
"""Delete a project.
Args:
project_id: Project ID
remove_files: Also remove files from filesystem
Returns:
True if successful
"""
path = f"/data/projects/{project_id}"
params: dict[str, Any] = {}
if remove_files:
params["removeFiles"] = "true"
return self._delete(path, params=params)
[docs]
def get_subjects(
self,
project_id: str,
limit: int | None = None,
) -> builtins.list[dict[str, Any]]:
"""Get subjects in a project.
Args:
project_id: Project ID
limit: Maximum number of results
Returns:
List of subject data dicts
"""
path = f"/data/projects/{project_id}/subjects"
params = {"format": "json"}
data = self._get(path, params=params)
results = HierarchyService.extract_rows(data)
if limit:
results = results[:limit]
return results
[docs]
def get_sessions(
self,
project_id: str,
limit: int | None = None,
) -> builtins.list[dict[str, Any]]:
"""Get sessions/experiments in a project.
Args:
project_id: Project ID
limit: Maximum number of results
Returns:
List of session data dicts
"""
path = f"/data/projects/{project_id}/experiments"
params = {"format": "json"}
data = self._get(path, params=params)
results = HierarchyService.extract_rows(data)
if limit:
results = results[:limit]
return results
[docs]
def set_accessibility(
self,
project_id: str,
accessibility: str,
) -> bool:
"""Set project accessibility level.
Args:
project_id: Project ID
accessibility: Access level (private, protected, public)
Returns:
True if successful
"""
path = f"/data/projects/{project_id}/accessibility/{accessibility}"
self._put(path)
return True