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

Add pydantic support #136

Open
wants to merge 28 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
bb60acb
Refactor linter
nuwang Jul 26, 2024
082b76d
Initial use of pydantic for entity validation
nuwang Aug 8, 2024
14ace41
Add black configuration
nuwang Aug 12, 2024
4e3410a
Fix invalid tag tests
nuwang Aug 12, 2024
c8bb8f9
Fix loader propagation and other minor fixes
nuwang Aug 12, 2024
105c57b
Gpus can only be whole numbers
nuwang Aug 12, 2024
135e4df
Process inheritance of loaded entities
nuwang Aug 12, 2024
9f5ebd9
Only apply Rule properties if instance of Rule
nuwang Aug 12, 2024
e5935e6
Change factory for GlobalConfig
nuwang Aug 12, 2024
f7ce2e4
Allow cores and mem to be int as well
nuwang Aug 12, 2024
3a058f9
Fix bug in setting tags
nuwang Aug 12, 2024
b83abc2
Fix incorrect variable reference
nuwang Aug 12, 2024
288f402
Change field ordering
nuwang Aug 12, 2024
00239ea
Fix pattern for tag filtering
nuwang Aug 12, 2024
535b3ad
Add error message when failing to load a file
nuwang Aug 13, 2024
40bb79f
Allow any datatype for params and convert env to str
nuwang Aug 13, 2024
973a52c
Fix tag_values_match function in helpers
nuwang Aug 13, 2024
8981c91
Add back entity.filter() and fix score function
nuwang Aug 14, 2024
e3a567b
Make sure min_cores/mem etc. types match specified
nuwang Aug 15, 2024
d52c44a
Restore original tag_values_match helper function
nuwang Aug 15, 2024
f2a2ef2
Allow bool or str for if conditions
nuwang Aug 15, 2024
edaa37e
Fix score function
nuwang Aug 15, 2024
9b1b32f
Fix tests for invalidly tagged users
nuwang Aug 15, 2024
4766f0e
Switch tests to python 3.11
nuwang Aug 15, 2024
0076bc3
Refactor code evaluation interface
nuwang Aug 15, 2024
e337fe3
Reactivate lru cache for inherit_matching_entities
nuwang Aug 27, 2024
1412029
Drop commented code and restore tests
nuwang Sep 13, 2024
52f274f
Bump major version
nuwang Sep 13, 2024
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
7 changes: 7 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,10 @@ match=^[Tt]est

[bdist_wheel]
universal = 1

[black]
line-length = 88

[isort]
line_length = 88
profile = black
2 changes: 1 addition & 1 deletion tests/fixtures/linter/linter-invalid-regex.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ tools:
cores: 2
params:
native_spec: "--mem {mem} --cores {cores} --gpus {gpus}"
bwa[0-9]++:
bwa[0-9]++\:
gpus: 2

destinations:
Expand Down
6 changes: 0 additions & 6 deletions tests/fixtures/mapping-basic.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,6 @@ tools:
scheduling:
require:
- non_existent
invalidly_tagged_tool:
scheduling:
require:
- general
reject:
- general
regex_tool.*:
scheduling:
require:
Expand Down
2 changes: 1 addition & 1 deletion tests/fixtures/mapping-destinations.yml
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ destinations:
TEST_ENTITY_PRIORITY: "{cores*2}"
params:
memory_requests: "{mem*2}"
k8s_walltime_limit: 20
k8s_walltime_limit: "20"
rules:
- if: input_size > 20
execute: |
Expand Down
6 changes: 0 additions & 6 deletions tests/fixtures/mapping-invalid-regex.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,6 @@ tools:
scheduling:
require:
- non_existent
invalidly_tagged_tool:
scheduling:
require:
- general
reject:
- general
regex_tool.*:
scheduling:
require:
Expand Down
40 changes: 40 additions & 0 deletions tests/fixtures/mapping-invalid-tags.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
global:
default_inherits: default

tools:
default:
abstract: true
cores: 2
mem: 8
env: {}
scheduling:
require: []
prefer:
- general
accept:
reject:
- pulsar
rules: []
invalidly_tagged_tool:
scheduling:
require:
- general
reject:
- general

destinations:
local:
runner: local
max_accepted_cores: 4
max_accepted_mem: 16
scheduling:
prefer:
- general
k8s_environment:
runner: k8s
max_accepted_cores: 16
max_accepted_mem: 64
max_accepted_gpus: 2
scheduling:
prefer:
- pulsar
6 changes: 0 additions & 6 deletions tests/fixtures/mapping-rank.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,6 @@ users:
scheduling:
require:
- earth
[email protected]:
scheduling:
require:
- pulsar
reject:
- pulsar
.*@vortex.org:
scheduling:
require:
Expand Down
6 changes: 0 additions & 6 deletions tests/fixtures/mapping-role.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,12 +68,6 @@ users:
scheduling:
require:
- earth
[email protected]:
scheduling:
require:
- pulsar
reject:
- pulsar
.*@vortex.org:
scheduling:
require:
Expand Down
6 changes: 0 additions & 6 deletions tests/fixtures/mapping-rules-changed.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,6 @@ users:
max_mem: cores * 6
- if: input_size >= 5
fail: Just because
[email protected]:
scheduling:
require:
- pulsar
reject:
- pulsar
.*@vortex.org:
scheduling:
require:
Expand Down
6 changes: 0 additions & 6 deletions tests/fixtures/mapping-rules.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,12 +60,6 @@ users:
max_mem: cores * 6
- if: input_size >= 5
fail: Just because
[email protected]:
scheduling:
require:
- pulsar
reject:
- pulsar
.*@vortex.org:
scheduling:
require:
Expand Down
6 changes: 0 additions & 6 deletions tests/fixtures/mapping-syntax-error.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,6 @@ users:
scheduling:
require:
- earth
[email protected]:
scheduling:
require:
- pulsar
reject:
- pulsar
.*@vortex.org:
scheduling:
require:
Expand Down
50 changes: 50 additions & 0 deletions tests/fixtures/mapping-user-invalid-tags.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
global:
default_inherits: default

tools:
default:
cores: 2
mem: 8
gpus: 1
env: {}
scheduling:
require: []
prefer:
- general
accept:
reject:
- pulsar
params:
native_spec: "--mem {mem} --cores {cores}"
rules: []

users:
default:
max_cores: 3
max_mem: 4
env: {}
scheduling:
require: []
prefer:
- general
accept:
reject:
- pulsar
rules: []
[email protected]:
scheduling:
require:
- pulsar
reject:
- pulsar

destinations:
local:
runner: local
max_accepted_cores: 4
max_accepted_mem: 16
scheduling:
prefer:
- general
accept:
- pulsar
6 changes: 0 additions & 6 deletions tests/fixtures/mapping-user.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,12 +60,6 @@ users:
- earth
reject:
- pulsar
[email protected]:
scheduling:
require:
- pulsar
reject:
- pulsar
[email protected]:
max_cores: 4
max_mem: 32
Expand Down
3 changes: 1 addition & 2 deletions tests/fixtures/scenario-too-many-highmem-jobs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,7 @@ users:
rule_helper = RuleHelper(app)
# Find all destinations that support highmem
destinations = [d.dest_name for d in mapper.destinations.values()
if any(d.tpv_dest_tags.filter(tag_value='highmem',
tag_type=[TagType.REQUIRE, TagType.PREFER, TagType.ACCEPT]))]
if 'highmem' in (d.tpv_dest_tags.require + d.tpv_dest_tags.prefer + d.tpv_dest_tags.accept)]
Copy link
Member Author

@nuwang nuwang Sep 5, 2024

Choose a reason for hiding this comment

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

Just flagging these for release notes. This is a simplification we can use over the existing code, but the existing filter method will also still work.

count = rule_helper.job_count(for_user_email=user.email, for_destinations=destinations)
if count > 4:
retval = True
Expand Down
30 changes: 14 additions & 16 deletions tests/test_entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,21 +37,20 @@ def test_all_entities_refer_to_same_loader(self):
}
# make sure we are still referring to the same loader after evaluation
evaluated_entity = gateway.ACTIVE_DESTINATION_MAPPER.match_combine_evaluate_entities(context, tool, user)
assert evaluated_entity.loader == original_loader
assert evaluated_entity.evaluator == original_loader
for rule in evaluated_entity.rules:
assert rule.loader == original_loader
assert rule.evaluator == original_loader

def test_destination_to_dict(self):

tpv_config = os.path.join(os.path.dirname(__file__), 'fixtures/mapping-rule-argument-based.yml')
loader = TPVConfigLoader.from_url_or_path(tpv_config)

# create a destination
destination = loader.destinations["k8s_environment"]
destination = loader.config.destinations["k8s_environment"]
# serialize the destination
serialized_destination = destination.to_dict()
serialized_destination = destination.dict()
# deserialize the same destination
deserialized_destination = Destination.from_dict(loader, serialized_destination)
deserialized_destination = Destination(evaluator=loader, **serialized_destination)
# make sure the deserialized destination is the same as the original
self.assertEqual(deserialized_destination, destination)

Expand All @@ -60,24 +59,23 @@ def test_tool_to_dict(self):
loader = TPVConfigLoader.from_url_or_path(tpv_config)

# create a tool
tool = loader.tools["limbo"]
tool = loader.config.tools["limbo"]
# serialize the tool
serialized_destination = tool.to_dict()
serialized_tool = tool.dict()
# deserialize the same tool
deserialized_destination = Tool.from_dict(loader, serialized_destination)
deserialized_tool = Tool(evaluator=loader, **serialized_tool)
# make sure the deserialized tool is the same as the original
self.assertEqual(deserialized_destination, tool)
self.assertEqual(deserialized_tool, tool)

def test_tag_equivalence(self):
tag1 = Tag("tag_name", "tag_value", TagType.REQUIRE)
tag2 = Tag("tag_name2", "tag_value", TagType.REQUIRE)
tag3 = Tag("tag_name", "tag_value1", TagType.REQUIRE)
tag4 = Tag("tag_name", "tag_value1", TagType.PREFER)
same_as_tag1 = Tag("tag_name", "tag_value", TagType.REQUIRE)
tag1 = Tag(value="tag_value", tag_type=TagType.REQUIRE)
tag2 = Tag(value="tag_value", tag_type=TagType.REQUIRE)
tag3 = Tag(value="tag_value1", tag_type=TagType.REQUIRE)
tag4 = Tag(value="tag_value1", tag_type=TagType.PREFER)
same_as_tag1 = Tag(value="tag_value", tag_type=TagType.REQUIRE)

self.assertEqual(tag1, tag1)
self.assertEqual(tag1, same_as_tag1)
self.assertNotEqual(tag1, tag2)
self.assertNotEqual(tag1, tag3)
self.assertNotEqual(tag1, tag4)
self.assertNotEqual(tag1, "hello")
5 changes: 3 additions & 2 deletions tests/test_mapper_basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,9 @@ def test_map_unschedulable_tool(self):

def test_map_invalidly_tagged_tool(self):
tool = mock_galaxy.Tool('invalidly_tagged_tool')
with self.assertRaisesRegex(JobMappingException, "No destinations are available to fulfill request"):
self._map_to_destination(tool)
config = os.path.join(os.path.dirname(__file__), 'fixtures/mapping-invalid-tags.yml')
with self.assertRaisesRegex(Exception, r"Duplicate tags found: 'general' in \['require', 'reject'\]"):
self._map_to_destination(tool, tpv_config_path=config)

def test_map_tool_by_regex(self):
tool = mock_galaxy.Tool('regex_tool_test')
Expand Down
9 changes: 5 additions & 4 deletions tests/test_mapper_user.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@
class TestMapperUser(unittest.TestCase):

@staticmethod
def _map_to_destination(tool, user):
def _map_to_destination(tool, user, tpv_config_path=None):
galaxy_app = mock_galaxy.App(job_conf=os.path.join(os.path.dirname(__file__), 'fixtures/job_conf.yml'))
job = mock_galaxy.Job()
tpv_config = os.path.join(os.path.dirname(__file__), 'fixtures/mapping-user.yml')
tpv_config = tpv_config_path or os.path.join(os.path.dirname(__file__), 'fixtures/mapping-user.yml')
gateway.ACTIVE_DESTINATION_MAPPER = None
return gateway.map_tool_to_destination(galaxy_app, job, tool, user, tpv_config_files=[tpv_config])

Expand Down Expand Up @@ -40,8 +40,9 @@ def test_map_invalidly_tagged_user(self):
tool = mock_galaxy.Tool('bwa')
user = mock_galaxy.User('infinitely', '[email protected]')

with self.assertRaises(IncompatibleTagsException):
self._map_to_destination(tool, user)
config = os.path.join(os.path.dirname(__file__), 'fixtures/mapping-user-invalid-tags.yml')
with self.assertRaisesRegex(Exception, r"Duplicate tags found: 'pulsar' in \['require', 'reject'\]"):
self._map_to_destination(tool, user, tpv_config_path=config)

def test_map_user_by_regex(self):
tool = mock_galaxy.Tool('bwa')
Expand Down
2 changes: 1 addition & 1 deletion tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
# running the tests.

[tox]
envlist = py3.10,lint
envlist = py3.11,lint

[testenv]
commands = # see setup.cfg for options sent to nosetests and coverage
Expand Down
2 changes: 1 addition & 1 deletion tpv/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""Total Perspective Vortex library setup."""

# Current version of the library
__version__ = "2.4.0"
__version__ = "3.0.0"


def get_version():
Expand Down
16 changes: 13 additions & 3 deletions tpv/commands/linter.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,13 @@ def lint(self):
except Exception as e:
log.error(f"Linting failed due to syntax errors in yaml file: {e}")
raise TPVLintError("Linting failed due to syntax errors in yaml file: ") from e
default_inherits = loader.global_settings.get('default_inherits')
for tool_regex, tool in loader.tools.items():
self.lint_tools(loader)
self.lint_destinations(loader)
self.print_errors_and_warnings()

def lint_tools(self, loader):
default_inherits = loader.config.global_config.default_inherits
for tool_regex, tool in loader.config.tools.items():
try:
re.compile(tool_regex)
except re.error:
Expand All @@ -34,7 +39,10 @@ def lint(self):
f"The tool named: {default_inherits} is marked globally as the tool to inherit from "
"by default. You may want to mark it as abstract if it is not an actual tool and it "
"will be excluded from scheduling decisions.")
for destination in loader.destinations.values():

def lint_destinations(self, loader):
default_inherits = loader.config.global_config.default_inherits
for destination in loader.config.destinations.values():
if not destination.runner and not destination.abstract:
self.errors.append(f"Destination '{destination.id}' does not define the runner parameter. "
"The runner parameter is mandatory.")
Expand All @@ -51,6 +59,8 @@ def lint(self):
f"The destination named: {default_inherits} is marked globally as the destination to inherit from "
"by default. You may want to mark it as abstract if it is not meant to be dispatched to, and it "
"will be excluded from scheduling decisions.")

def print_errors_and_warnings(self):
if self.warnings:
for w in self.warnings:
log.warning(w)
Expand Down
Loading
Loading