-
Notifications
You must be signed in to change notification settings - Fork 31
[uss_qualifier/resources] Update naming and documentation for ResourceModifyingResource #1474
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
base: main
Are you sure you want to change the base?
Changes from 5 commits
afc93ca
8ad1a4d
cb3c10b
2fb5507
c75b95d
44881aa
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,6 @@ | ||
| from .noop import NoOpResource as NoOpResource | ||
| from .test_exclusions import TestExclusionsResource as TestExclusionsResource | ||
| from .test_modifier import TestModifierModifierResource as TestModifierModifierResource | ||
| from .test_modifier import TestModifierResource as TestModifierResource | ||
| from .test_modifier import ( | ||
| NumberGeneratorModifierResource as NumberGeneratorModifierResource, | ||
| ) | ||
| from .test_modifier import NumberGeneratorResource as NumberGeneratorResource |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,20 +1,23 @@ | ||
| from implicitdict import ImplicitDict | ||
|
|
||
| from monitoring.uss_qualifier.resources.resource import Resource, ResourceModifier | ||
| from monitoring.uss_qualifier.resources.resource import ( | ||
| Resource, | ||
| ResourceProvidingResource, | ||
| ) | ||
|
|
||
|
|
||
| class TestModifierSpecification(ImplicitDict): | ||
| class NumberGeneratorSpecification(ImplicitDict): | ||
| base_id: int | ||
|
|
||
|
|
||
| class TestModifierResource(Resource[TestModifierSpecification]): | ||
| """TestModifierResource is a simple resource returing 10 number, starting from base_id. Used for unit tests.""" | ||
| class NumberGeneratorResource(Resource[NumberGeneratorSpecification]): | ||
| """A simple resource returing 10 numbers, starting from base_id. Used for unit tests.""" | ||
|
|
||
| _spec: TestModifierSpecification | ||
| _spec: NumberGeneratorSpecification | ||
|
|
||
| def __init__( | ||
| self, | ||
| specification: TestModifierSpecification, | ||
| specification: NumberGeneratorSpecification, | ||
| resource_origin: str, | ||
| ): | ||
| super().__init__(specification, resource_origin) | ||
|
|
@@ -24,22 +27,42 @@ def build_ids(self) -> list[int]: | |
| return list(range(self._spec.base_id, self._spec.base_id + 10)) | ||
|
|
||
|
|
||
| class TestModifierModifierSpecification(ImplicitDict): | ||
| class NumberGeneratorModifierSpecification(ImplicitDict): | ||
| shift_interval: int | ||
|
|
||
|
|
||
| class TestModifierModifierResource( | ||
| ResourceModifier[TestModifierModifierSpecification, TestModifierResource] | ||
| class NumberGeneratorModifierResource( | ||
| ResourceProvidingResource[ | ||
| NumberGeneratorModifierSpecification, NumberGeneratorResource | ||
| ] | ||
| ): | ||
| """Modifier for a TestModifierResource. Used for unit tests.""" | ||
| """Modifier for a NumberGeneratorResource. Used for unit tests.""" | ||
|
|
||
| def adjust(self, index: int) -> TestModifierResource: | ||
| _spec: NumberGeneratorModifierSpecification | ||
| base_resource: NumberGeneratorResource | ||
|
|
||
| # 'Clone' the resource with new specs | ||
| return TestModifierResource( | ||
| TestModifierSpecification( | ||
| def __init__( | ||
| self, | ||
| specification: NumberGeneratorModifierSpecification, | ||
| resource_origin: str, | ||
| base_resource: NumberGeneratorResource, | ||
| ): | ||
| super().__init__(specification, resource_origin) | ||
| self._spec = specification | ||
| self.base_resource = base_resource | ||
|
|
||
| def _modified_resource_origin(self, index: int) -> str: | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This one need to be updated with new signature
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This function returns a resource origin string for the specified index. It isn't part of the ResourceProvidingResource abstraction. Why would it have a different signature?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ho ok, I confused it with |
||
| return f"Modification {index} of {self.base_resource.resource_origin} by {self.resource_origin}" | ||
|
|
||
| def provide_resource_for(self, **kwargs) -> NumberGeneratorResource: | ||
| index = kwargs["index"] | ||
| assert isinstance(index, int) | ||
|
|
||
| # 'Clone' the base resource with new specs | ||
| return NumberGeneratorResource( | ||
| NumberGeneratorSpecification( | ||
| base_id=self.base_resource._spec.base_id | ||
| + self._spec.shift_interval * index, | ||
| ), | ||
| resource_origin=self.base_resource.resource_origin, | ||
| resource_origin=self._modified_resource_origin(index), | ||
| ) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -43,34 +43,20 @@ def is_type(self, resource_type: str) -> bool: | |
| ResourceType = TypeVar("ResourceType", bound=Resource) | ||
|
|
||
|
|
||
| class ResourceModifier[SpecificationType: ImplicitDict, ResourceType]( | ||
| Resource[SpecificationType], ABC | ||
| ): | ||
| """A specifc type of resources that can return adjusted an resource that shall unique based on a specifc 'index'. | ||
| The underlying resource shall be a dependency named 'base_resource'. | ||
|
|
||
| Concrete subclass must implement 'adjust' as needed. | ||
| """ | ||
|
|
||
| _spec: SpecificationType | ||
| base_resource: ResourceType | ||
|
|
||
| def __init__( | ||
| self, | ||
| specification: SpecificationType, | ||
| resource_origin: str, | ||
| base_resource: ResourceType, | ||
| ): | ||
| super().__init__(specification, resource_origin) | ||
| self._spec = specification | ||
| self.base_resource = base_resource | ||
| class ResourceProvidingResource[ | ||
| SpecificationType: ImplicitDict, | ||
| ResourceType: Resource, | ||
| ](Resource[SpecificationType], ABC): | ||
| """Resource capable of spawning ResourceType resources according to a desired key, such as an index.""" | ||
|
|
||
| @abstractmethod | ||
| def adjust(self, index: int) -> ResourceType: | ||
| """ | ||
| Return a new instance of the base resource, modified to be unique based on 'index' value. | ||
| """ | ||
| pass | ||
| def provide_resource_for(self, **kwargs) -> ResourceType: | ||
| """Provide a resource corresponding with the provided key(s) (e.g., index, USS pair, etc).""" | ||
| raise NotImplementedError() | ||
|
|
||
| def _provided_resource_origin(self, key_name: str) -> str: | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What the keyname there? Shouldn't it be the same as
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's the name of the key -- the thing that determined the generated resource (index, USS pair, etc). A ResourceProvidingResource can accept potentially many keyword arguments, but it needs to select one of them to actually use. And, a key doesn't necessarily have a native string representation (e.g., a USS pair). So, the implementation needs to provide a string representation to get a resource origin string. We changed |
||
| """Method that should generally be used to describe the origin of a resource provided by this resource.""" | ||
| return f"Resource for {key_name} provided by {self.resource_origin}" | ||
|
|
||
|
|
||
| class MissingResourceError(ValueError): | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,72 +5,74 @@ | |
| ResourceID, | ||
| ) | ||
| from monitoring.uss_qualifier.resources.dev.test_modifier import ( | ||
| TestModifierModifierSpecification, | ||
| TestModifierSpecification, | ||
| NumberGeneratorModifierSpecification, | ||
| NumberGeneratorSpecification, | ||
| ) | ||
| from monitoring.uss_qualifier.resources.resource import create_resources | ||
|
|
||
|
|
||
| class TestResourceModifier(unittest.TestCase): | ||
| def _build_test_modifier_declaration( | ||
| class TestModifierResource(unittest.TestCase): | ||
| def _build_number_generator_declaration( | ||
| self, base_id | ||
| ) -> dict[ResourceID, ResourceDeclaration]: | ||
| return { | ||
| "test": ResourceDeclaration( | ||
| resource_type="resources.dev.TestModifierResource", | ||
| specification=TestModifierSpecification(base_id=base_id), | ||
| "number_generator": ResourceDeclaration( | ||
| resource_type="resources.dev.NumberGeneratorResource", | ||
| specification=NumberGeneratorSpecification(base_id=base_id), | ||
| ) | ||
| } | ||
|
|
||
| def _build_test_modifier_modifier_declaration( | ||
| def _build_modifier_declaration( | ||
| self, base_id, shift_interval | ||
| ) -> dict[ResourceID, ResourceDeclaration]: | ||
| return { | ||
| "test": self._build_test_modifier_declaration(base_id)["test"], | ||
| "test_modifier": ResourceDeclaration( | ||
| resource_type="resources.dev.TestModifierModifierResource", | ||
| specification=TestModifierModifierSpecification( | ||
| "number_generator": self._build_number_generator_declaration(base_id)[ | ||
| "number_generator" | ||
| ], | ||
| "modifier": ResourceDeclaration( | ||
| resource_type="resources.dev.NumberGeneratorModifierResource", | ||
| specification=NumberGeneratorModifierSpecification( | ||
| shift_interval=shift_interval | ||
| ), | ||
| dependencies={ | ||
| "base_resource": "test", | ||
| "base_resource": "number_generator", | ||
| }, | ||
| ), | ||
| } | ||
|
|
||
| def test_base_resource(self): | ||
| """Test basic usage of the resource""" | ||
| declaration = self._build_test_modifier_declaration(42) | ||
| declaration = self._build_number_generator_declaration(42) | ||
|
|
||
| resources = create_resources(declaration, "test", True) | ||
| assert "test" in resources | ||
| resources = create_resources(declaration, "unittest", True) | ||
| assert "number_generator" in resources | ||
|
|
||
| resource = resources["test"] | ||
| resource = resources["number_generator"] | ||
|
|
||
| assert resource.build_ids() == [42, 43, 44, 45, 46, 47, 48, 49, 50, 51] | ||
|
|
||
| def test_base_resource_base_id(self): | ||
| """Test that base id works as expected""" | ||
|
|
||
| declaration = self._build_test_modifier_declaration(52) | ||
| declaration = self._build_number_generator_declaration(52) | ||
|
|
||
| resources = create_resources(declaration, "test", True) | ||
| assert "test" in resources | ||
| resources = create_resources(declaration, "unittest", True) | ||
| assert "number_generator" in resources | ||
|
|
||
| resource = resources["test"] | ||
| resource = resources["number_generator"] | ||
|
|
||
| assert resource.build_ids() == [52, 53, 54, 55, 56, 57, 58, 59, 60, 61] | ||
|
|
||
| def test_modifier_resource(self): | ||
| """Test basic usage of the resource modifier""" | ||
| declaration = self._build_test_modifier_modifier_declaration(42, 10) | ||
| """Test basic usage of the resource modifier resource""" | ||
| declaration = self._build_modifier_declaration(42, 10) | ||
|
|
||
| resources = create_resources(declaration, "test", True) | ||
| assert "test_modifier" in resources | ||
| resources = create_resources(declaration, "unittest", True) | ||
| assert "modifier" in resources | ||
|
|
||
| resource = resources["test_modifier"] | ||
| resource = resources["modifier"] | ||
|
|
||
| assert resource.adjust(0).build_ids() == [ | ||
| assert resource.provide_resource_for(index=0).build_ids() == [ | ||
| 42, | ||
| 43, | ||
| 44, | ||
|
|
@@ -82,7 +84,7 @@ def test_modifier_resource(self): | |
| 50, | ||
| 51, | ||
| ] | ||
| assert resource.adjust(1).build_ids() == [ | ||
| assert resource.provide_resource_for(index=1).build_ids() == [ | ||
| 52, | ||
| 53, | ||
| 54, | ||
|
|
@@ -97,14 +99,14 @@ def test_modifier_resource(self): | |
|
|
||
| def test_modifier_resource_shift(self): | ||
| """Test shift usage of the resource modifier""" | ||
| declaration = self._build_test_modifier_modifier_declaration(42, 20) | ||
| declaration = self._build_modifier_declaration(42, 20) | ||
|
|
||
| resources = create_resources(declaration, "test", True) | ||
| assert "test_modifier" in resources | ||
| resources = create_resources(declaration, "unittest", True) | ||
| assert "modifier" in resources | ||
|
|
||
| resource = resources["test_modifier"] | ||
| resource = resources["modifier"] | ||
|
|
||
| assert resource.adjust(0).build_ids() == [ | ||
| assert resource.provide_resource_for(index=0).build_ids() == [ | ||
| 42, | ||
| 43, | ||
| 44, | ||
|
|
@@ -116,7 +118,7 @@ def test_modifier_resource_shift(self): | |
| 50, | ||
| 51, | ||
| ] | ||
| assert resource.adjust(1).build_ids() == [ | ||
| assert resource.provide_resource_for(index=1).build_ids() == [ | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: Shouldn't we also test the case with unknown key generating an error? Making me thinking: shouldn't we have a predetermined error like
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added (both) |
||
| 62, | ||
| 63, | ||
| 64, | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't it be the opposite ? A different
keymust produce different variants; the same may produce equivalents result?I would assume that the spirit is to ensure different resources when needed because that more important that having the same values no?
(We can however have a 'must' for the same key -> same result)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure I understand the question; the change says "different key values -> different variants" + "same key -> equivalent results" -- the same thing as the original, just using "key" instead of "index".
One substantive difference is the removal of "(unique)" because that will probably often be the case, but there's no reason it necessarily needs to be in any case where we want a resource that spawns resources based on a template resource.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That indeed about the "(unique)" removed and the addition of 'generally': since the final goal is to be able to have resources that can be used in parallel without conflicts, I would expect that those resources generator, by 'contract' return different variants in every case, not in most of the cases.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The use case prompting the creation of this resource needs unique variants in all cases and should always produce equivalent results with equivalent keys. But the way this resource is defined allows it to be used in more general ways. Just because one use of a tool has certain requirements doesn't mean those requirements need to be imposed at the tool level rather than the tool-usage level. For instance, suppose we wanted to iterate over all ordered {uss1, uss2} pairs of participants, but a test needed a token that corresponded with the unordered pair of {uss1, uss2} (so {uss1, uss2} has a different token resource than {uss1, uss3}, but the same token resource as {uss2, uss1}). The suite may still be iterating over ordered pairs, but the resource producing modified versions of the underlying token resource may produce the same modified resource for multiple keys.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, but should we then define an sub-type of the base type than enforce this? I would expect instances used in 'parallel generator' to follow stricter rules (This probably answer the type question bellow as well)
(True, but in that case since it's unordered, it would be an equal key ({uss1,uss2}=={uss2,uss1}) even if it's has different values. But that don't mean other cases wouldn't exists.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wouldn't expect we'd need to. A good test design will produce test scenarios that don't interfere with each other. Not interfering sometimes means "unique" modified resources (a necessary but not sufficient condition to deconflict geospatial resources), but not always (like in the case of iterating over ordered pairs but only needing unique unordered pairs for the token resource). But if we did need "unique" modified resources, I'd expect that to be introduced along with the thing that actually needs that constraint. The content in this PR (and #1465) doesn't need uniqueness -- the concept of stamping out modified copies of a template resource based on a key is a good standalone concept that doesn't need the additional constraint to work.
No, the key would be ordered as the test scenarios (or whatever is being primarily iterated) are distinguishable based on order in the example, but the particular resource being generated by modifying a base resource doesn't depend on order. To elaborate on the example: suppose we have a test scenario that has two roles and uses a token resource that needs to be "unique" according to participants in the scenario, but not according to what roles they're playing. The action generator would iterate over every (role1, role2) assignment of the {uss1, uss2, uss3, ...} participants when instantiating test scenarios. And, each test scenario would need a token resource based on some template token resource. But, the token resource needed would only depend on the participants rather than role assignments, so the modified token resources spawned would not be unique. At the same time, geospatial deconfliction is needed, so every set of flight intents would need to be "unique".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok - that move the "responsibility" up to test definitions to only combine things that works together without an explicit 'contract', but that valid.