Skip to content

Commit

Permalink
Creating python API for Nox sessions (#1414)
Browse files Browse the repository at this point in the history
* Update config.yml - fix Circle CI pipeline

* Playground Implementation

* Playground Implementation

* Support CLI batching, and more

* Update CLI test

* Update CLI test

* Update CLI test

* CLI test Update

* A few fix on the playground test

* A few fix on the playground test

* A few fix on the playground test

* Playground with all command at #1368

* Playground with all command at #1368

* Playground with all command at #1368

* Playground with all command at #1368

* Playground with all command at #1368

* Playground with all command at #1368

* Session Parallelization on CLI test playground

* Session Parallelization on CLI test playground

* Session Parallelization on CLI test playground

* Session Parallelization on CLI test playground

* Session Parallelization on CLI test playground

* Session Parallelization on CLI test playground

* Session Parallelization on CLI test playground

* Session Parallelization on CLI test playground

* Session Parallelization on CLI test playground

* Session Parallelization on CLI test playground

* Session Parallelization on CLI test playground

* Session Parallelization on CLI test playground

* Session Parallelization on CLI test playground

* Session Parallelization on CLI test playground with pytest dependency

* Rich lib integration pyproject config

* Make the serializer use the fields from the header (#1406)

* Generalize Standard Run  (#1411)

* Modify header calculation to choose from predefined example output file or standard example output file

* Remove the readiness function from SCRA because it is redundant, since those checks are also performed by the amenable function

* Remove unused method

* Make csv serialization work for any kind of model api response

* Remove the standard flag from the CLI since it is now the default run

* Update tests

* Creating python for Nox sessions

* Creating python for Nox sessions

* Generalize Standard Run  (#1411)

* Modify header calculation to choose from predefined example output file or standard example output file

* Remove the readiness function from SCRA because it is redundant, since those checks are also performed by the amenable function

* Remove unused method

* Make csv serialization work for any kind of model api response

* Remove the standard flag from the CLI since it is now the default run

* Update tests

* Generalize Standard Run  (#1411)

* Modify header calculation to choose from predefined example output file or standard example output file

* Remove the readiness function from SCRA because it is redundant, since those checks are also performed by the amenable function

* Remove unused method

* Make csv serialization work for any kind of model api response

* Remove the standard flag from the CLI since it is now the default run

* Update tests

* Make the serializer use the fields from the header (#1406)

* Merge sample command with the example command (#1422)

* Merge sample command with the example command

* Fix example command usage

* Generalize Standard Run  (#1411)

* Modify header calculation to choose from predefined example output file or standard example output file

* Remove the readiness function from SCRA because it is redundant, since those checks are also performed by the amenable function

* Remove unused method

* Make csv serialization work for any kind of model api response

* Remove the standard flag from the CLI since it is now the default run

* Update tests

* Conflict resolved with upstream

* Conflict resolved with upstream

* Generalize Standard Run  (#1411)

* Modify header calculation to choose from predefined example output file or standard example output file

* Remove the readiness function from SCRA because it is redundant, since those checks are also performed by the amenable function

* Remove unused method

* Make csv serialization work for any kind of model api response

* Remove the standard flag from the CLI since it is now the default run

* Update tests

* Make the serializer use the fields from the header (#1406)

* Generalize Standard Run  (#1411)

* Modify header calculation to choose from predefined example output file or standard example output file

* Remove the readiness function from SCRA because it is redundant, since those checks are also performed by the amenable function

* Remove unused method

* Make csv serialization work for any kind of model api response

* Remove the standard flag from the CLI since it is now the default run

* Update tests

* Conflict resolved with upstream

* Conflict resolved with upstream

* Unnecessary files removed

* Generalize Standard Run  (#1411)

* Modify header calculation to choose from predefined example output file or standard example output file

* Remove the readiness function from SCRA because it is redundant, since those checks are also performed by the amenable function

* Remove unused method

* Make csv serialization work for any kind of model api response

* Remove the standard flag from the CLI since it is now the default run

* Update tests

* Unnecessary files removed

* Generalize Standard Run  (#1411)

* Modify header calculation to choose from predefined example output file or standard example output file

* Remove the readiness function from SCRA because it is redundant, since those checks are also performed by the amenable function

* Remove unused method

* Make csv serialization work for any kind of model api response

* Remove the standard flag from the CLI since it is now the default run

* Update tests

* Make the serializer use the fields from the header (#1406)

* Unnecessary files removed

* Unnecessary files removed

* Unnecessary files removed

* Unnecessary files removed

* Fix: Unnecessary files removed

---------

Co-authored-by: Dhanshree Arora <[email protected]>
  • Loading branch information
Abellegese and DhanshreeA committed Dec 10, 2024
1 parent ff9e2b6 commit 8c04b03
Show file tree
Hide file tree
Showing 13 changed files with 124 additions and 43 deletions.
18 changes: 12 additions & 6 deletions .github/scripts/airtableops.py
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@ def insert_metadata_to_airtable(model, contributor, api_key):
if r.status_code == 200:
text = r.content
data = yaml.safe_load(text)

airtable_data = {}
airtable_data["Identifier"] = model
airtable_data["Slug"] = data["Slug"]
Expand Down Expand Up @@ -286,9 +286,15 @@ def update_readme_from_airtable(repo, path):
subparsers = parser.add_subparsers(dest="command")

# Main commands
airtable_insert = subparsers.add_parser("airtable-insert", help="Insert metadata to AirTable")
airtable_update = subparsers.add_parser("airtable-update", help="Update metadata to AirTable")
readme_update = subparsers.add_parser("readme-update", help="Update README from AirTable")
airtable_insert = subparsers.add_parser(
"airtable-insert", help="Insert metadata to AirTable"
)
airtable_update = subparsers.add_parser(
"airtable-update", help="Update metadata to AirTable"
)
readme_update = subparsers.add_parser(
"readme-update", help="Update README from AirTable"
)

# Options for airtable-insert
airtable_insert.add_argument("--model", type=str, required=True)
Expand All @@ -310,7 +316,7 @@ def update_readme_from_airtable(repo, path):
if args.command == "airtable-insert":
print("Inserting metadata to AirTable")
insert_metadata_to_airtable(args.model, args.contributor, args.api_key)

elif args.command == "airtable-update":
print("Updating metadata to AirTable")
# update_metadata_to_airtable(args.user, args.repo, args.branch, args.api_key)
Expand All @@ -322,4 +328,4 @@ def update_readme_from_airtable(repo, path):
else:
print("Invalid command")
parser.print_help()
exit(1)
exit(1)
18 changes: 9 additions & 9 deletions ersilia/cli/commands/catalog.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ def catalog(
except Exception as e:
click.echo(click.style(f"Error fetching model metadata: {e}", fg="red"))
return

# The idea here is to deter the user from running ersilia catalog --local --hub
if local and hub:
click.echo(
Expand All @@ -96,36 +96,36 @@ def catalog(
err=True,
)
return

mc = ModelCatalog()
mc.only_identifier = False if more else True

if hub:
if browser:
mc.airtable()
return

catalog_table = mc.hub()

else: # This will work even if the user doesn't explicitly specify the --local flag
else: # This will work even if the user doesn't explicitly specify the --local flag
if browser:
click.echo(
click.style(
"Error: Cannot show local models in the browser.\nPlease use the --hub option to see models in the browser.",
fg="red"
fg="red",
)
)
return
catalog_table = mc.local()
if not catalog_table.data:
click.echo(
click.style(
"No local models available. Please fetch a model by running 'ersilia fetch' command",
fg="red",
click.style(
"No local models available. Please fetch a model by running 'ersilia fetch' command",
fg="red",
)
)
return

if file_name is None:
catalog = catalog_table.as_table() if as_table else catalog_table.as_json()
else:
Expand Down
13 changes: 7 additions & 6 deletions ersilia/cli/commands/example.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ def example_cmd():

# Example usage: ersilia example {MODEL} -n 10 [--file_name {FILE_NAME} --simple/--complete]
@ersilia_cli.group(
short_help="Generate sample of Ersilia models or model inputs",
help="""This command can sample both ersilia models, or inputs for a given or currently running model.\n
short_help="Generate sample of Ersilia models or model inputs",
help="""This command can sample both ersilia models, or inputs for a given or currently running model.\n
For the model input, the number of examples can be specified, as well as a file name.\n
Simple inputs only contain the essential information, while complete inputs contain key and other fields, potentially.\n
For ersilia models, only model identifiers are returned for a given sample size.
Expand All @@ -24,7 +24,6 @@ def example_cmd():
def example():
pass


@example.command()
@click.argument("model", required=False, default=None, type=click.STRING)
@click.option("--n_samples", "-n", default=5, type=click.INT)
Expand All @@ -39,7 +38,10 @@ def inputs(model, n_samples, file_name, simple, predefined):
model_id = session.current_model_id()
if model_id is None:
click.echo(
click.style("Error: No model id given and no model found running in this shell.", fg="red"),
click.style(
"Error: No model id given and no model found running in this shell.",
fg="red",
),
err=True,
)
return
Expand All @@ -54,7 +56,6 @@ def inputs(model, n_samples, file_name, simple, predefined):
else:
eg.example(n_samples, file_name, simple, try_predefined=predefined)


@example.command()
@click.option("--n_samples", "-n", default=5, type=click.INT)
@click.option("--file_name", "-f", default=None, type=click.STRING)
Expand All @@ -64,4 +65,4 @@ def models(n_samples, file_name):
if file_name is None:
echo(json.dumps(sampler.sample(n_samples), indent=4))
else:
sampler.sample(n_samples, file_name)
sampler.sample(n_samples, file_name)
11 changes: 6 additions & 5 deletions ersilia/hub/content/catalog.py
Original file line number Diff line number Diff line change
Expand Up @@ -236,8 +236,7 @@ def _get_service_class(self, card):
def airtable(self):
"""List models available in AirTable Ersilia Model Hub base"""
if webbrowser:
webbrowser.open("https://airtable.com/shrUcrUnd7jB9ChZV") #TODO Hardcoded

webbrowser.open("https://airtable.com/shrUcrUnd7jB9ChZV") # TODO Hardcoded

def hub(self):
"""List models available in Ersilia model hub from the S3 JSON"""
Expand All @@ -249,7 +248,7 @@ def hub(self):
status = self._get_status(model)
if status == "In Progress":
continue

identifier = self._get_item(model, "identifier")
if self.only_identifier:
R += [[identifier]]
Expand All @@ -258,9 +257,11 @@ def hub(self):
title = self._get_title(model)
R += [[identifier, slug, title]]

columns = ["Identifier"] if self.only_identifier else ["Identifier", "Slug", "Title"]
columns = (
["Identifier"] if self.only_identifier else ["Identifier", "Slug", "Title"]
)
return CatalogTable(R, columns=columns)

def local(self):
"""List models available locally"""
mc = ModelCard()
Expand Down
2 changes: 1 addition & 1 deletion ersilia/io/input.py
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@ def example(self, n_samples, file_name, simple, try_predefined):
if try_predefined is True and file_name is not None:
self.logger.debug("Trying with predefined input")
predefined_available = self.predefined_example(file_name)

if predefined_available:
with open(file_name, "r") as f:
return f.read()
Expand Down
2 changes: 1 addition & 1 deletion ersilia/serve/standard_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,6 @@ def _read_information_file(self):
with open(os.path.join(self.path, INFORMATION_FILE), "r") as f:
info = json.load(f)
return info

except FileNotFoundError:
self.logger.debug(
f"Error: File '{INFORMATION_FILE}' not found in the path '{self.path}'"
Expand Down Expand Up @@ -295,6 +294,7 @@ def serialize_to_csv(self, input_data, result, output_data):
result[idx] = item[list(item.keys())[0]]

assert len(input_data) == len(result)

with open(output_data, "w") as f:
writer = csv.writer(f)
writer.writerow(self.header)
Expand Down
1 change: 1 addition & 0 deletions ersilia/utils/exceptions_utils/card_exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ def __init__(self):
self.hints = ""
ErsiliaError.__init__(self, self.message, self.hints)


# TODO Unused - remove
class BaseInformationError(ErsiliaError):
def __init__(self):
Expand Down
4 changes: 3 additions & 1 deletion ersilia/utils/exceptions_utils/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,9 @@ def run_from_terminal(self):
output_file = os.path.join(framework_dir, "example_output.csv")
tmp_folder = make_temp_dir(prefix="ersilia-")
log_file = os.path.join(tmp_folder, "terminal.log")
run_command("ersilia example inputs {0} -n 3 -f {1}".format(self.model_id, input_file))
run_command(
"ersilia example inputs {0} -n 3 -f {1}".format(self.model_id, input_file)
)
cmd = "bash {0} {1} {2} {3} 2>&1 | tee -a {4}".format(
exec_file, framework_dir, input_file, output_file, log_file
)
Expand Down
1 change: 1 addition & 0 deletions ersilia/utils/exceptions_utils/hubdata_exceptions.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from .exceptions import ErsiliaError


# Note: Not really used anywhere right now except in the sanitize class
class InvalidUrlInAirtableError(ErsiliaError):
def __init__(self, url):
Expand Down
9 changes: 5 additions & 4 deletions test/cli/test_close.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,11 @@ def mock_close():

@pytest.fixture
def mock_session():
with patch.object(Session, "current_model_id", return_value=MODEL_ID), patch.object(
Session, "current_service_class", return_value="pulled_docker"
), patch.object(Session, "tracking_status", return_value=False), patch.object(
Session, "current_output_source", return_value="LOCAL_ONLY"
with (
patch.object(Session, "current_model_id", return_value=MODEL_ID),
patch.object(Session, "current_service_class", return_value="pulled_docker"),
patch.object(Session, "tracking_status", return_value=False),
patch.object(Session, "current_output_source", return_value="LOCAL_ONLY"),
):
yield

Expand Down
9 changes: 5 additions & 4 deletions test/cli/test_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,10 +118,11 @@ def mock_post_side_effect(input, output, output_source):

@pytest.fixture
def mock_session(compound_csv):
with patch.object(Session, "current_model_id", return_value=MODEL_ID), patch.object(
Session, "current_service_class", return_value="pulled_docker"
), patch.object(Session, "tracking_status", return_value=False), patch.object(
Session, "current_output_source", return_value="LOCAL_ONLY"
with (
patch.object(Session, "current_model_id", return_value=MODEL_ID),
patch.object(Session, "current_service_class", return_value="pulled_docker"),
patch.object(Session, "tracking_status", return_value=False),
patch.object(Session, "current_output_source", return_value="LOCAL_ONLY"),
):
yield

Expand Down
68 changes: 68 additions & 0 deletions test/playground/runner.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import subprocess
import yaml
from pathlib import Path


class NoxSession:
def __init__(self, name):
self.name = name

def execute(self, noxfile):
try:
subprocess.run(
["nox", "-f", noxfile, "-s", self.name],
check=True,
)
print(f"Session '{self.name}' executed successfully.")
except subprocess.CalledProcessError as e:
print(f"Error executing session '{self.name}': {e}")


class NoxRunner:
def __init__(self, config_path="config.yml", noxfile="noxfile.py"):
self.original_dir = Path.cwd()
self.config_path = Path(config_path)
self.noxfile = noxfile
self.config = yaml.safe_load(self.config_path.read_text())
self.nox_command = "nox"
self.queue = []

def update_yaml_values(self, new_values: dict):
existing_config = yaml.safe_load(self.config_path.read_text())
existing_config.update(new_values)
self.config_path.write_text(yaml.dump(existing_config))

def get_python_version(self):
return self.config.get("python_version", "3.10.10")

def add_session(self, session_name):
self.queue.append(NoxSession(session_name))

def execute_all(self):
for session in self.queue:
session.execute(self.noxfile)
self.queue.clear()

def clear_queue(self):
self.queue.clear()

def setup(self):
self.add_session("setup")

def test_from_github(self):
self.add_session("test_from_github")

def test_from_dockerhub(self):
self.add_session("test_from_dockerhub")

def test_auto_fetcher_decider(self):
self.add_session("test_auto_fetcher_decider")

def test_fetch_multiple_models(self):
self.add_session("test_fetch_multiple_models")

def test_serve_multiple_models(self):
self.add_session("test_serve_multiple_models")

def test_conventional_run(self):
self.add_session("test_conventional_run")
11 changes: 5 additions & 6 deletions test/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,11 @@ def mock_set_apis():

@pytest.fixture
def mock_session():
with patch.object(
Session, "current_model_id", return_value=MODELS[1]
), patch.object(
Session, "current_service_class", return_value="docker"
), patch.object(Session, "tracking_status", return_value=False), patch.object(
Session, "current_output_source", return_value="LOCAL_ONLY"
with (
patch.object(Session, "current_model_id", return_value=MODELS[1]),
patch.object(Session, "current_service_class", return_value="docker"),
patch.object(Session, "tracking_status", return_value=False),
patch.object(Session, "current_output_source", return_value="LOCAL_ONLY"),
):
yield

Expand Down

0 comments on commit 8c04b03

Please sign in to comment.