A Python library and command-line interface for interacting with the Open Science Framework
—
Project, storage, file, and folder management classes for navigating and manipulating OSF project structures. These classes provide the core functionality for working with OSF data and files.
Represents an OSF project containing multiple storage providers and associated metadata.
class Project:
def storage(self, provider='osfstorage'):
"""
Return specific storage provider.
Args:
provider (str): Storage provider name (default: 'osfstorage')
Options: 'osfstorage', 'github', 'figshare', 'googledrive', 'owncloud'
Returns:
Storage: Storage instance for the specified provider
Raises:
RuntimeError: If project has no storage provider with the given name
"""
@property
def storages(self):
"""
Iterate over all storage providers for this project.
Yields:
Storage: Storage instances for each available provider
"""
@property
def id(self):
"""Project ID (GUID)."""
@property
def title(self):
"""Project title."""
@property
def description(self):
"""Project description."""
@property
def date_created(self):
"""Project creation date."""
@property
def date_modified(self):
"""Project last modified date."""Represents a storage provider within a project, such as OSF Storage, GitHub, Figshare, etc.
class Storage:
def create_file(self, path, fp, force=False, update=False):
"""
Store a new file at path in this storage.
Args:
path (str): Full path where to store the file
fp (file): File descriptor opened in 'rb' mode
force (bool): Force overwrite of existing file
update (bool): Overwrite existing file only if files differ
Raises:
ValueError: If file not opened in binary mode
FileExistsError: If file exists and neither force nor update is True
RuntimeError: If upload fails or file cannot be created/updated
"""
def create_folder(self, name, exist_ok=False):
"""
Create a new folder.
Args:
name (str): Folder name
exist_ok (bool): Don't raise exception if folder already exists
Returns:
Folder: Created folder instance
Raises:
FolderExistsException: If folder exists and exist_ok is False
RuntimeError: If folder creation fails
"""
@property
def files(self):
"""
Iterate over all files in this storage.
Recursively lists all files in all subfolders.
Yields:
File: File instances for each file in storage
"""
@property
def folders(self):
"""
Iterate over top-level folders in this storage.
Yields:
Folder: Folder instances for each top-level folder
"""
@property
def id(self):
"""Storage ID."""
@property
def name(self):
"""Storage name."""
@property
def provider(self):
"""Storage provider name ('osfstorage', 'github', etc.)."""
@property
def path(self):
"""Storage path."""
@property
def node(self):
"""Associated project node."""Represents an individual file in OSF storage with download, update, and deletion capabilities.
class File:
def write_to(self, fp):
"""
Write contents of this file to a local file.
Args:
fp (file): File pointer opened for writing in binary mode
Raises:
ValueError: If file not opened in binary mode
RuntimeError: If download fails
"""
def remove(self):
"""
Remove this file from remote storage.
Raises:
RuntimeError: If deletion fails
"""
def update(self, fp):
"""
Update the remote file from a local file.
Args:
fp (file): File pointer opened for reading in binary mode
Raises:
ValueError: If file not opened in binary mode
RuntimeError: If update fails
"""
@property
def id(self):
"""File ID."""
@property
def name(self):
"""File name."""
@property
def path(self):
"""Materialized file path."""
@property
def osf_path(self):
"""OSF internal path."""
@property
def size(self):
"""File size in bytes."""
@property
def date_created(self):
"""File creation date."""
@property
def date_modified(self):
"""File last modified date."""
@property
def hashes(self):
"""
Dictionary of file hashes.
Returns:
dict: Hash values keyed by algorithm ('md5', 'sha256', etc.)
"""Represents a folder in OSF storage with file and subfolder access capabilities.
class Folder:
def create_folder(self, name, exist_ok=False):
"""
Create a subfolder within this folder.
Args:
name (str): Subfolder name
exist_ok (bool): Don't raise exception if folder already exists
Returns:
Folder: Created subfolder instance
Raises:
FolderExistsException: If folder exists and exist_ok is False
RuntimeError: If folder creation fails
"""
@property
def files(self):
"""
Iterate over files in this folder.
Unlike Storage.files, this does not recursively find all files.
Only lists files directly in this folder.
Yields:
File: File instances for each file in folder
"""
@property
def folders(self):
"""
Iterate over subfolders in this folder.
Yields:
Folder: Folder instances for each subfolder
"""
@property
def id(self):
"""Folder ID."""
@property
def name(self):
"""Folder name."""
@property
def path(self):
"""Materialized folder path."""
@property
def osf_path(self):
"""OSF internal path."""
@property
def date_created(self):
"""Folder creation date."""
@property
def date_modified(self):
"""Folder last modified date."""from osfclient import OSF
osf = OSF(token='your_token')
project = osf.project('project_id')
print(f"Project: {project.title}")
print(f"Created: {project.date_created}")
print(f"Description: {project.description}")
# List all storage providers
for storage in project.storages:
print(f"Storage: {storage.name} ({storage.provider})")
# Get default OSF Storage
osf_storage = project.storage() # defaults to 'osfstorage'
# Get specific storage provider
try:
github_storage = project.storage('github')
print(f"GitHub storage available: {github_storage.name}")
except RuntimeError:
print("No GitHub storage configured for this project")# List all files recursively
storage = project.storage()
for file in storage.files:
print(f"File: {file.path}")
print(f" Size: {file.size} bytes")
print(f" Modified: {file.date_modified}")
print(f" MD5: {file.hashes.get('md5', 'N/A')}")
# Download a specific file
target_file = next((f for f in storage.files if f.name == 'data.csv'), None)
if target_file:
with open('local_data.csv', 'wb') as local_file:
target_file.write_to(local_file)
print("File downloaded successfully")
# Upload a new file
with open('local_upload.txt', 'rb') as upload_file:
storage.create_file('remote_folder/uploaded_file.txt', upload_file)
print("File uploaded successfully")
# Update existing file
existing_file = next((f for f in storage.files if f.name == 'update_me.txt'), None)
if existing_file:
with open('new_content.txt', 'rb') as content:
existing_file.update(content)
print("File updated successfully")
# Delete a file
file_to_delete = next((f for f in storage.files if f.name == 'delete_me.txt'), None)
if file_to_delete:
file_to_delete.remove()
print("File deleted successfully")# Create folder structure
storage = project.storage()
main_folder = storage.create_folder('data_analysis', exist_ok=True)
sub_folder = main_folder.create_folder('raw_data', exist_ok=True)
# Navigate folder structure
for folder in storage.folders:
print(f"Top-level folder: {folder.name}")
for subfolder in folder.folders:
print(f" Subfolder: {subfolder.name}")
for file in folder.files:
print(f" File: {file.name}")
# Upload file to specific folder structure
with open('experiment_data.csv', 'rb') as data_file:
storage.create_file('data_analysis/raw_data/experiment_data.csv', data_file)with open('existing_file.txt', 'rb') as upload_file:
try:
# This will fail if file already exists
storage.create_file('path/existing_file.txt', upload_file)
except FileExistsError:
print("File already exists")
# Force overwrite
upload_file.seek(0) # Reset file pointer
storage.create_file('path/existing_file.txt', upload_file, force=True)
print("File overwritten")
# Or use update mode (only overwrites if files differ)
with open('maybe_changed.txt', 'rb') as upload_file:
storage.create_file('path/maybe_changed.txt', upload_file, update=True)
print("File uploaded or updated if changed")Install with Tessl CLI
npx tessl i tessl/pypi-osfclient