Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update scheduling queries and unit tests to conform to Aerie v2.6 #129

Merged
merged 11 commits into from
Apr 8, 2024
Merged
2 changes: 1 addition & 1 deletion .env
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
DOCKER_TAG=v2.2.0
DOCKER_TAG=v2.6.0
REPOSITORY_DOCKER_URL=ghcr.io/nasa-ammos

AERIE_USERNAME=aerie
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ jobs:
strategy:
matrix:
python-version: ["3.6.15", "3.11"]
aerie-version: ["2.2.0"]
aerie-version: ["2.6.0"]
steps:
- uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python-version }}
Expand Down
54 changes: 38 additions & 16 deletions src/aerie_cli/aerie_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -1488,30 +1488,25 @@ def get_scheduling_goals_by_specification(self, spec_id):
scheduling_specification_goals(where: {
specification_id:{_eq:$spec}
}){
goal{
goal_metadata {
id
model_id
name
description
author
last_modified_by
created_date
modified_date
}
joshhaug marked this conversation as resolved.
Show resolved Hide resolved
}
}
"""
resp = self.aerie_host.post_to_graphql(list_all_goals_by_spec_query, spec=spec_id)

return resp

def upload_scheduling_goal(self, model_id, name, definition):
obj = dict()
obj["name"] = name
obj["model_id"] = model_id
#obj["metadata"] = {"data": {"name": name, "models_using": {"data": {"model_id": model_id}}}}
#obj["name"] = name
#obj["model_id"] = model_id
joshhaug marked this conversation as resolved.
Show resolved Hide resolved
obj["definition"] = definition

return self.upload_scheduling_goals([obj])


def upload_scheduling_goals(self, upload_object):
"""
Expand All @@ -1527,16 +1522,24 @@ def upload_scheduling_goals(self, upload_object):
]
"""

# Note that as of Aerie v2.3.0, the metadata (incl. model_id and goal name) are stored in a separate table
formatted_upload_objects = []
for entry in upload_object:
new_entry = {}
new_entry["definition"] = entry["definition"]
new_entry["metadata"] = {"data": {"name": entry["name"], "models_using": {"data": {"model_id": entry["model_id"]}}}}
formatted_upload_objects.append(new_entry)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For long-term maintainability, would it be better to break the format of upload_object than to hard-code this mapping? Who are the users of this method?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only user of this method is scheduling.upload. Is it preferable to deliver the upload_object to this method as it should be uploaded?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that's preferable. I'd say we should avoid breaking changes to the API, but I don't know of anyone using the scheduling rule Python API.

upload_scheduling_goals_query = """
mutation InsertGoals($input:[scheduling_goal_insert_input!]!){
insert_scheduling_goal(objects: $input){
returning {id}
mutation InsertGoal($input:[scheduling_goal_definition_insert_input]!){
insert_scheduling_goal_definition(objects: $input){
returning {goal_id}
}
}"""

resp = self.aerie_host.post_to_graphql(
upload_scheduling_goals_query,
input=upload_object
input=formatted_upload_objects
)

return resp["returning"]
Expand Down Expand Up @@ -1593,9 +1596,28 @@ def delete_scheduling_goal(self, goal_id):
return self.delete_scheduling_goals(list([goal_id]))

def delete_scheduling_goals(self, goal_id_list):
# We must remove the goal(s) from any specifications before deleting them
delete_scheduling_goals_from_all_specs_query = """
mutation DeleteSchedulingGoalsFromAllSpecs($id_list: [Int!]!) {
delete_scheduling_model_specification_goals (where: {goal_id: {_in:$id_list}}){
returning {goal_id}
}
delete_scheduling_specification_goals (where: {goal_id: {_in:$id_list}}){
returning {goal_id}
}
}
"""

resp_for_deleting_from_specs = self.aerie_host.post_to_graphql(
delete_scheduling_goals_from_all_specs_query,
id_list=goal_id_list
)

# Note that deleting the scheduling goal metadata entry will take care of the
# scheduling goal definition entry too
delete_scheduling_goals_query = """
mutation DeleteSchedulingGoals($id_list: [Int!]!) {
delete_scheduling_goal (where: {id: {_in:$id_list}}){
delete_scheduling_goal_metadata (where: {id: {_in:$id_list}}){
returning {id}
}
}
Expand Down
10 changes: 5 additions & 5 deletions src/aerie_cli/commands/scheduling.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ def upload(

typer.echo(f"Uploaded scheduling goals to venue.")

uploaded_ids = [kv["id"] for kv in resp]
uploaded_ids = [kv["goal_id"] for kv in resp]

#priority order is order of filenames in decreasing priority order
#will append to existing goals in specification priority order
Expand All @@ -53,7 +53,7 @@ def delete(
)
):
"""Delete scheduling goal"""
client = CommandContext.get_client()
client = CommandContext.get_client()

resp = client.delete_scheduling_goal(goal_id)
typer.echo("Successfully deleted Goal ID: " + str(resp))
Expand All @@ -77,8 +77,8 @@ def delete_all_goals_for_plan(
typer.echo("Deleting goals for Plan ID {plan}: ".format(plan=plan_id), nl=False)
goal_ids = []
for goal in clear_goals:
goal_ids.append(goal["goal"]["id"])
typer.echo(str(goal["goal"]["id"]) + " ", nl=False)
goal_ids.append(goal["goal_metadata"]["id"])
typer.echo(str(goal["goal_metadata"]["id"]) + " ", nl=False)
typer.echo()

client.delete_scheduling_goals(goal_ids)
6 changes: 3 additions & 3 deletions src/aerie_cli/persistent.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import json
import shutil
import pickle
from datetime import datetime, timedelta
from datetime import datetime, timedelta, timezone

from appdirs import AppDirs

Expand Down Expand Up @@ -142,7 +142,7 @@ def _load_active_session(cls) -> None:
raise RuntimeError(f"Cannot parse session timestamp: {session_file.name}. Try deactivating your session.")

# If session hasn't been used since timeout, mark as inactive
if (datetime.utcnow() - t) > SESSION_TIMEOUT:
if (datetime.now(timezone.utc).replace(tzinfo=None) - t) > SESSION_TIMEOUT:
session_file.unlink()
raise NoActiveSessionError

Expand Down Expand Up @@ -177,7 +177,7 @@ def set_active_session(cls, session: AerieHost) -> bool:

cls._active_session = session

session_file = datetime.utcnow().strftime(SESSION_TIMESTAMP_FSTRING) + '.aerie_cli.session'
session_file = datetime.now(timezone.utc).strftime(SESSION_TIMESTAMP_FSTRING) + '.aerie_cli.session'
session_file = SESSION_FILE_DIRECTORY.joinpath(session_file)

with open(session_file, 'wb') as fid:
Expand Down
Binary file not shown.
10 changes: 5 additions & 5 deletions tests/unit_tests/test_persistent_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ def test_get_session_expired(persistent_path: Path):

# Create a mock old session with an "expired" session
old_session = MockAerieHost()
old_time = datetime.datetime.utcnow() - persistent.SESSION_TIMEOUT - \
old_time = datetime.datetime.now(datetime.timezone.utc).replace(tzinfo=None) - persistent.SESSION_TIMEOUT - \
datetime.timedelta(seconds=1)
old_fn = old_time.strftime(
persistent.SESSION_TIMESTAMP_FSTRING) + '.aerie_cli.session'
Expand All @@ -70,7 +70,7 @@ def test_get_session_broken(persistent_path: Path):

# Create a mock old session which fails the ping test
old_session = MockAerieHost(False)
old_time = datetime.datetime.utcnow()
old_time = datetime.datetime.now(datetime.timezone.utc).replace(tzinfo=None)
old_fn = old_time.strftime(
persistent.SESSION_TIMESTAMP_FSTRING) + '.aerie_cli.session'
with open(persistent_path.joinpath(old_fn), 'wb') as fid:
Expand All @@ -89,7 +89,7 @@ def test_get_session(persistent_path: Path):

# Create a mock good session
old_session = MockAerieHost()
old_time = datetime.datetime.utcnow()
old_time = datetime.datetime.now(datetime.timezone.utc).replace(tzinfo=None)
old_fn = old_time.strftime(
persistent.SESSION_TIMESTAMP_FSTRING) + '.aerie_cli.session'
with open(persistent_path.joinpath(old_fn), 'wb') as fid:
Expand All @@ -109,7 +109,7 @@ def test_get_session_multiple(persistent_path: Path):
# Create a mock good session
for i in range(2):
old_session = MockAerieHost()
old_time = datetime.datetime.utcnow() - datetime.timedelta(minutes=i)
old_time = datetime.datetime.now(datetime.timezone.utc).replace(tzinfo=None) - datetime.timedelta(minutes=i)
old_fn = old_time.strftime(
persistent.SESSION_TIMESTAMP_FSTRING) + '.aerie_cli.session'
with open(persistent_path.joinpath(old_fn), 'wb') as fid:
Expand Down Expand Up @@ -182,7 +182,7 @@ def test_unset_session_startup_persistent(persistent_path: Path):

# Create a mock good session
old_session = MockAerieHost(name="Old Session")
old_time = datetime.datetime.utcnow()
old_time = datetime.datetime.now(datetime.timezone.utc).replace(tzinfo=None)
old_fn = old_time.strftime(
persistent.SESSION_TIMESTAMP_FSTRING) + '.aerie_cli.session'
with open(persistent_path.joinpath(old_fn), 'wb') as fid:
Expand Down
Loading