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
2 changes: 1 addition & 1 deletion .github/workflows/auth-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,10 @@ jobs:
working-directory: .
run: |
python -m pip install --upgrade pip
pip install -e cli/
pip install -r cli/test-requirements.txt
pip install -r server/requirements.txt
pip install -r server/test-requirements.txt
pip install -e cli/

- name: Set server environment vars
working-directory: ./server
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/docker-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,10 @@ jobs:
working-directory: .
run: |
python -m pip install --upgrade pip
pip install -e cli/
pip install -r cli/test-requirements.txt
pip install -r server/requirements.txt
pip install -r server/test-requirements.txt
pip install -e cli/

- name: Set server environment vars
working-directory: ./server
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/encrypted-containers-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,10 @@ jobs:
working-directory: .
run: |
python -m pip install --upgrade pip
pip install -e cli/
pip install -r cli/test-requirements.txt
pip install -r server/requirements.txt
pip install -r server/test-requirements.txt
pip install -e cli/

- name: Set server environment vars
working-directory: ./server
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/local-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,10 @@ jobs:
working-directory: .
run: |
python -m pip install --upgrade pip
pip install -e cli/
pip install -r cli/test-requirements.txt
pip install -r server/requirements.txt
pip install -r server/test-requirements.txt
pip install -e cli/

- name: Set server environment vars
working-directory: ./server
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/train-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ jobs:
working-directory: .
run: |
python -m pip install --upgrade pip
pip install -e cli/
pip install -r cli/test-requirements.txt
pip install -r server/requirements.txt
pip install -r server/test-requirements.txt
pip install -e cli/

- name: Set server environment vars
working-directory: ./server
Expand Down
5 changes: 3 additions & 2 deletions .github/workflows/unittests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,11 @@ jobs:
run: |
python -m pip install --upgrade pip
pip install flake8 pytest
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
if [ -f cli/requirements.txt ]; then pip install -e cli; fi
pip install -r cli/test-requirements.txt
pip install -r server/requirements.txt
pip install -r server/test-requirements.txt
pip install -e cli/

- name: Lint with flake8
run: |
# stop the build if there are Python syntax errors or undefined names
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/webui-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ jobs:
working-directory: .
run: |
python -m pip install --upgrade pip
pip install -e cli/
pip install -r cli/test-requirements.txt
pip install -r server/requirements.txt
pip install -r server/test-requirements.txt
pip install -e cli/

- name: Set server environment vars
working-directory: ./server
Expand Down
26 changes: 26 additions & 0 deletions cli/medperf/commands/benchmark/benchmark.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from medperf.commands.benchmark.update_associations_poilcy import (
UpdateAssociationsPolicy,
)
from medperf.commands.benchmark.update_committee_members import UpdateCommitteeMembers

app = typer.Typer()

Expand Down Expand Up @@ -256,3 +257,28 @@ def update_associations_policy(
model_mode=model_auto_approve_mode,
model_emails_file=model_auto_approve_file,
)


@app.command("update_committee_members")
@clean_except
def update_committee_members(
benchmark_uid: int = typer.Option(
..., "--benchmark", "-b", help="UID of the desired benchmark"
),
committee_emails_file: str = typer.Option(
None,
"--committee_emails_file",
help="File containing list of committee member emails",
),
committee_emails: str = typer.Option(
None,
"--committee_emails",
help="Space-separated list of committee member emails",
),
):
"""Updates the committee members for a benchmark"""
UpdateCommitteeMembers.run(
benchmark_uid,
committee_emails_file=committee_emails_file,
committee_emails=committee_emails,
)
75 changes: 75 additions & 0 deletions cli/medperf/commands/benchmark/update_committee_members.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import os

import medperf.config as config
from medperf.exceptions import InvalidArgumentError
from medperf.utils import sanitize_path, validate_and_normalize_emails


class UpdateCommitteeMembers:
@classmethod
def run(
cls,
benchmark_uid: int,
committee_emails_file: str = None,
committee_emails: str = None,
):
"""
committee_emails: a string containing space-separated list of emails
"""
update_members = cls(
benchmark_uid,
committee_emails_file,
committee_emails,
)
update_members.validate()
update_members.read_emails()
update_members.validate_emails()
update_members.update()

def __init__(
self,
benchmark_uid: int,
committee_emails_file: str = None,
committee_emails: str = None,
):
self.benchmark_uid = benchmark_uid
self.committee_emails_file = sanitize_path(committee_emails_file)
self.committee_emails = committee_emails
self.committee_emails_list = None

def validate(self):
if self.committee_emails_file is not None and self.committee_emails is not None:
raise InvalidArgumentError("Both a file and a list of emails are provided.")
if self.committee_emails_file is None and self.committee_emails is None:
raise InvalidArgumentError("No emails provided.")

if self.committee_emails_file and not os.path.isfile(
self.committee_emails_file
):
raise InvalidArgumentError(
f"File {self.committee_emails_file} does not exist or is a directory"
)

def __read_emails_file(self, file):
with open(file) as f:
contents = f.read()
return contents.strip().split("\n")

def read_emails(self):
if self.committee_emails_file is not None:
self.committee_emails_list = self.__read_emails_file(
self.committee_emails_file
)
else:
self.committee_emails_list = self.committee_emails.strip().split(" ")

def validate_emails(self):
self.committee_emails_list = validate_and_normalize_emails(
self.committee_emails_list
)

def update(self):
config.comms.update_benchmark(
self.benchmark_uid,
{"committee_member_emails": self.committee_emails_list},
)
14 changes: 13 additions & 1 deletion cli/medperf/entities/benchmark.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,18 @@ def __init__(self, **kwargs):
self.dataset_auto_approval_mode = self._model.dataset_auto_approval_mode
self.model_auto_approval_allow_list = self._model.model_auto_approval_allow_list
self.model_auto_approval_mode = self._model.model_auto_approval_mode
self.committee_member_emails = self._model.committee_member_emails

def user_can_manage(self, user_data=None):
user_data = user_data or get_medperf_user_data()
if self.owner == user_data["id"]:
return True

user_email = user_data["email"].lower()
committee_emails = [email.lower() for email in self.committee_member_emails]
if user_email in committee_emails:
return True
return False

@property
def local_id(self):
Expand Down Expand Up @@ -168,7 +180,7 @@ def get_datasets_associations(cls, benchmark_uid: int) -> List[dict]:
associations = get_user_associations(
experiment_type=experiment_type,
component_type=component_type,
approval_status=None, # TODO
approval_status=None,
)

associations = [a for a in associations if a["benchmark"] == benchmark_uid]
Expand Down
1 change: 1 addition & 0 deletions cli/medperf/entities/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ class BenchmarkSchema(MedperfSchema):
dataset_auto_approval_mode: str = "NEVER"
model_auto_approval_allow_list: list[str] = []
model_auto_approval_mode: str = "NEVER"
committee_member_emails: list[str] = []


class CASchema(MedperfSchema):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
from medperf.exceptions import InvalidArgumentError
import pytest

from medperf.commands.benchmark.update_committee_members import UpdateCommitteeMembers


def test_both_file_and_list_raises():
# Act & Assert
with pytest.raises(InvalidArgumentError):
UpdateCommitteeMembers(
1,
committee_emails_file="/somefile",
committee_emails="test@example.com",
).validate()


def test_committee_email_list_filled(mocker, fs):
# Arrange
file = "/somefile"
fs.create_file(file, contents="test@example.com\nmember@org.com")
obj = UpdateCommitteeMembers(1, committee_emails_file=file)

# Act
obj.read_emails()
obj.validate_emails()

# Assert
assert obj.committee_emails_list == ["test@example.com", "member@org.com"]


def test_invalid_email_list(mocker, fs):
# Arrange
file = "/somefile"
fs.create_file(file, contents="invalidemail\nmember@org.com")
obj = UpdateCommitteeMembers(1, committee_emails_file=file)
obj.read_emails()

# Act & Assert
with pytest.raises(InvalidArgumentError):
obj.validate_emails()


def test_update_calls_comms_with_file_emails(mocker, comms):
# Arrange
spy = mocker.patch.object(comms, "update_benchmark")
obj = UpdateCommitteeMembers(1)
obj.committee_emails_list = ["test@example.com", "member@org.com"]

# Act
obj.update()

# Assert
spy.assert_called_once_with(
1,
{"committee_member_emails": ["test@example.com", "member@org.com"]},
)


def test_update_calls_comms_with_inline_emails(mocker, comms):
# Arrange
spy = mocker.patch.object(comms, "update_benchmark")
obj = UpdateCommitteeMembers(
1, committee_emails="member@example.com other@example.com"
)
obj.read_emails()
obj.validate_emails()

# Act
obj.update()

# Assert
spy.assert_called_once_with(
1,
{"committee_member_emails": ["member@example.com", "other@example.com"]},
)


def test_update_fails_when_no_emails_provided(mocker, comms):
# Arrange
obj = UpdateCommitteeMembers(1)

# Act & Assert
with pytest.raises(InvalidArgumentError):
obj.validate()


def test_update_clears_members_with_empty_list(mocker, comms):
# Arrange
spy = mocker.patch.object(comms, "update_benchmark")
obj = UpdateCommitteeMembers(1, committee_emails="")
obj.read_emails()
obj.validate_emails()

# Act
obj.update()

# Assert
spy.assert_called_once_with(1, {"committee_member_emails": []})
Loading
Loading