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
171 changes: 94 additions & 77 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,125 +2,142 @@

# MD Python Client

A Python client for the Mass Dynamics API that provides a simple and type-safe interface for managing experiments and datasets.
A Python client for the Mass Dynamics API.

## Installation

```bash
pip install git+https://github.com/MassDynamics/md-python.git
```

## Available Resources

- **Experiments**: Create, retrieve, and update experiments
- **Datasets**: Create, retrieve, retry and delete datasets
- **Health**: Check API health status

## Quick Start

```python
from md_python import MDClient, Experiment, Dataset, SampleMetadata, ExperimentDesign
from md_python import MDClient

# Initialise client
client = MDClient(api_token="your_api_token")
```

# Check API health
health_status = client.health.check()
The client defaults to the v2 API. For v1 usage, see [V1.md](V1.md).

# Create an experiment
sample_metadata = SampleMetadata.from_csv("sample_metadata.csv")
experiment = Experiment(
name="My Experiment",
description="Test experiment",
sample_metadata=sample_metadata
)
experiment_id = client.experiments.create(experiment)
## Resources

# Get experiment by name
exp = client.experiments.get_by_name("My Experiment")
- **Uploads**: Create, retrieve, and manage file uploads
- **Datasets**: Create, list, retry, cancel, and delete datasets
- **Jobs**: List available dataset jobs
- **Health**: Check API health status

## Uploads

Uploads replace v1 experiments. They handle file ingestion and workflow triggering.

```python
from md_python import Upload, SampleMetadata

# Create an upload from S3
upload = Upload(
name="My Upload",
source="maxquant",
s3_bucket="my-bucket",
s3_prefix="data/",
filenames=["evidence.txt", "proteinGroups.txt"],
)
upload_id = client.uploads.create(upload)

# Create an upload from local files
upload = Upload(
name="My Upload",
source="maxquant",
file_location="/path/to/files",
filenames=["evidence.txt", "proteinGroups.txt"],
)
upload_id = client.uploads.create(upload)

# Get experiment by ID
exp = client.experiments.get_by_id(experiment_id)
# Get upload by ID or name
upload = client.uploads.get_by_id(upload_id)
upload = client.uploads.get_by_name("My Upload")

# Update experiment sample metadata
# Update sample metadata
sample_metadata = SampleMetadata(data=[
["sample_name", "dose"],
["1", "1"],
["2", "20"],
["sample_name", "condition"],
["sample1", "control"],
["sample2", "treated"],
])
success = client.experiments.update_sample_metadata(experiment_id, sample_metadata)
client.uploads.update_sample_metadata(upload_id, sample_metadata)

# Wait for upload processing to complete
upload = client.uploads.wait_until_complete(upload_id)
```

## Datasets

# Create a new dataset
```python
from uuid import UUID
new_dataset = Dataset(
from md_python import Dataset

# Create a dataset
dataset = Dataset(
input_dataset_ids=[UUID("existing-dataset-id")],
name="Processed Data",
job_slug="data_processing",
job_run_params={"parameter1": "value1", "parameter2": "value2"}
job_slug="pairwise_comparison",
job_run_params={"condition_column": "condition"},
)
dataset_id = client.datasets.create(new_dataset)
dataset_id = client.datasets.create(dataset)

# List datasets for an upload
datasets = client.datasets.list_by_upload(upload_id)

# Find the initial intensity dataset
initial = client.datasets.find_initial_dataset(upload_id)

# Retry a failed dataset
success = client.datasets.retry(dataset_id)
client.datasets.retry(dataset_id)

# Cancel a processing dataset
client.datasets.cancel(dataset_id)

# Delete a dataset
deleted = client.datasets.delete(dataset_id)
client.datasets.delete(dataset_id)

# Wait for a dataset to complete
ds = client.datasets.wait_until_complete(upload_id, dataset_id)
```

## Jobs

```python
# List available dataset jobs
jobs = client.jobs.list()
```

## Health

# List all datasets for an experiment
experiment_datasets = client.datasets.list_by_experiment(experiment_id)
```python
health_status = client.health.check()
```

## Examples
## Custom Base URL

Comprehensive examples demonstrating how to use the MD Python client are available in the `examples/` directory:
```python
client = MDClient(
api_token="your_api_token",
base_url="https://xxx.massdynamics-example-installation.com/api",
)
```

- **Experiment Examples** (`examples/experiment/`):
- Create experiments
- Retrieve experiments by ID or name
- Update sample metadata
## V1 API

- **Dataset Examples** (`examples/dataset/`):
- Create datasets
- Delete datasets
- Retry failed datasets
- List datasets by experiment
For v1 API usage, pass `version="v1"` or see [V1.md](V1.md).

- **Health Examples** (`examples/health/`):
- Check API health status
```python
client = MDClient(api_token="your_api_token", version="v1")
```

## Development

```bash
# Clone the repository
git clone https://github.com/MassDynamics/md-python.git
cd md-python

# Install development dependencies
pip install -e ".[dev]"

# Run tests
pytest

# Run type checking
mypy .

# Format code with Black
black .

# Sort imports
isort .
```

### Using Custom Base URL

When developing or testing against an environment, you can specify a custom base URL:

```python
from md_python import MDClient

client = MDClient(
api_token="your_api_token",
base_url="https://xxx.massdynamics-example-installation.com/api"
)
```
90 changes: 90 additions & 0 deletions V1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# V1 API Client

The v1 client uses the `experiments` and `datasets` resources with the `application/vnd.md-v1+json` accept header.

## Initialisation

```python
from md_python import MDClient

client = MDClient(api_token="your_api_token", version="v1")
```

## Experiments

```python
from md_python import Experiment, SampleMetadata, ExperimentDesign

# Create an experiment
sample_metadata = SampleMetadata.from_csv("sample_metadata.csv")
experiment = Experiment(
name="My Experiment",
description="Test experiment",
sample_metadata=sample_metadata,
)
experiment_id = client.experiments.create(experiment)

# Get experiment by name
exp = client.experiments.get_by_name("My Experiment")

# Get experiment by ID
exp = client.experiments.get_by_id(experiment_id)

# Update sample metadata
sample_metadata = SampleMetadata(data=[
["sample_name", "dose"],
["1", "1"],
["2", "20"],
])
client.experiments.update_sample_metadata(experiment_id, sample_metadata)

# Wait for experiment to complete
exp = client.experiments.wait_until_complete(experiment_id)
```

## Datasets

```python
from uuid import UUID
from md_python import Dataset

# Create a dataset
dataset = Dataset(
input_dataset_ids=[UUID("existing-dataset-id")],
name="Processed Data",
job_slug="data_processing",
job_run_params={"parameter1": "value1"},
)
dataset_id = client.datasets.create(dataset)

# List datasets for an experiment
datasets = client.datasets.list_by_experiment(experiment_id)

# Find the initial intensity dataset
initial = client.datasets.find_initial_dataset(experiment_id)

# Retry a failed dataset
client.datasets.retry(dataset_id)

# Delete a dataset
client.datasets.delete(dataset_id)

# Wait for a dataset to complete
ds = client.datasets.wait_until_complete(experiment_id, dataset_id)
```

## Health

```python
health_status = client.health.check()
```

## Custom Base URL

```python
client = MDClient(
api_token="your_api_token",
version="v1",
base_url="https://xxx.massdynamics-example-installation.com/api",
)
```
8 changes: 8 additions & 0 deletions src/md_python/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@
MD Python Client - A Python client for the Mass Dynamics API
"""

from .base_client import BaseMDClient
from .client import MDClient
from .client_v1 import MDClientV1
from .client_v2 import MDClientV2
from .models import (
Dataset,
Experiment,
Expand All @@ -11,12 +14,17 @@
NormalisationImputationDataset,
PairwiseComparisonDataset,
SampleMetadata,
Upload,
)
from .resources import Datasets, Experiments, Health

__all__ = [
"MDClient",
"MDClientV1",
"MDClientV2",
"BaseMDClient",
"Experiment",
"Upload",
"Dataset",
"SampleMetadata",
"ExperimentDesign",
Expand Down
55 changes: 55 additions & 0 deletions src/md_python/base_client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
"""
Base client class for the MD Python client
"""

import os
from typing import Optional

import requests
from dotenv import load_dotenv

load_dotenv()

DEFAULT_BASE_URL = "https://app.massdynamics.com/api"


class BaseMDClient:
"""Base client with shared auth, base URL, and HTTP transport"""

ACCEPT_HEADER: str

base_url: str
api_token: str

def __init__(self, api_token: Optional[str] = None, base_url: Optional[str] = None):
base = base_url or os.getenv("MD_API_BASE_URL") or DEFAULT_BASE_URL
token = api_token or os.getenv("MD_AUTH_TOKEN")

if not token:
raise ValueError("MD_AUTH_TOKEN must be set or passed as api_token")

self.base_url: str = base
self.api_token: str = token

def _get_headers(self) -> dict:
"""Get common headers for API requests"""
return {
"accept": self.ACCEPT_HEADER,
"Authorization": f"Bearer {self.api_token}",
}

def _make_request(
self,
method: str,
endpoint: str,
headers: Optional[dict] = None,
json: Optional[dict] = None,
) -> requests.Response:
"""Make HTTP request to the API"""
url = f"{self.base_url}{endpoint}"
request_headers = self._get_headers()

if headers:
request_headers.update(headers)

return requests.request(method, url, headers=request_headers, json=json)
Loading
Loading