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

feat: efficient implementation of RelationDataContent.update #1586

Conversation

james-garner-canonical
Copy link
Contributor

@james-garner-canonical james-garner-canonical commented Feb 24, 2025

This PR makes it efficient to add/update/delete multiple keys and values in your relation data, by overriding the default implementation of RelationDataContent.update (provided by MutableMapping) with an efficient implementation that calls the relation-set hook tool only once. The changes to internals that back the new update implementation are exercised by existing tests interacting with RelationDataContent.update and RelationDataContent.__setitem__.

Both Harness and Scenario's model backend implementations require updates to accommodate this. The Harness backend is exercised in the existing Ops tests. The Scenario updates are exercised in new tests added to testing/tests/test_e2e/test_relations.py.


Original description, now outdated, but retained to explain the original motivation for this work:

This PR resolves #817 by adding RelationData.__setitem__(self, key, value). This allows users to write my_relation[app_or_unit] = {'key-value': 'pairs'}, instead of having to fetch the RelationDataContent object with my_relation[app_or_unit] and then update its keys and values directly.

The implementation of RelationData.__setitem__ gets the current RelationDataContent for the key, calls RelationDataContent.clear() (provided by RelationDataContent's inheritance from MutableMapping -- the inherited implementation will call RelationDataContent's custom __delitem__), and then calls the existing RelationDataContent.update(value) method. This ensures that validation and updating the backend occurs exactly as it would have if the user interacted with RelationDataContent directly. RelationData.__setitem__ will raise a KeyError if the key is not actually a known Application or Unit.

@james-garner-canonical james-garner-canonical force-pushed the 2025-02/feat/relationdata-assignment-v1 branch 2 times, most recently from f50f18e to 0d0df7d Compare February 25, 2025 04:59
@james-garner-canonical
Copy link
Contributor Author

I've broken the tests. I'm going to iterate on the efficient approach to unify the single/multiple update code paths as much as possible, and then I'll update the test side of things (may need some little modifications to harness/scenario).

Copy link
Contributor

@tonyandrewmeyer tonyandrewmeyer left a comment

Choose a reason for hiding this comment

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

I think this is definitely the right approach. It's looking really great!

On testing:

  • You should definitely add a testing/tests test, probably in test_relations. Basically the same kind of thing as you have for harness/test_testing, but for Scenario. I'd also include one that checks that removing a key works (which I think will fail currently!).
  • I don't think there's a test for the "I gave setitem an empty dict" case raising an error. It would be good to have one if it's not too complicated.
  • I don't think there's any relation-set checking in test_main at the moment, but it feels like this is maybe a good opportunity to add some, which could include this. Basically just verifying that relation-get and relation-set are called properly, including in this new flow. At the moment there's quite an assumption that it works ok since the tests (like much of test_model!) rely on Harness.

@james-garner-canonical james-garner-canonical force-pushed the 2025-02/feat/relationdata-assignment-v1 branch 2 times, most recently from 676f25a to ed71134 Compare March 4, 2025 00:55
This is an efficient implementation of update that replaces the
original, inefficient implementation inherited from MutableMapping.
@james-garner-canonical james-garner-canonical force-pushed the 2025-02/feat/relationdata-assignment-v1 branch from ed71134 to 8854be8 Compare March 4, 2025 01:13
@james-garner-canonical james-garner-canonical marked this pull request as ready for review March 4, 2025 01:33
@tonyandrewmeyer
Copy link
Contributor

The docs fail because it looks like your code is a "block quote". You can fix it like:

diff --git a/ops/model.py b/ops/model.py
index 61ed63d..2e564af 100644
--- a/ops/model.py
+++ b/ops/model.py
@@ -1947,11 +1947,12 @@ class RelationDataContent(LazyMapping, MutableMapping[str, str]):
     ) -> None:
         """Efficiently write multiple keys and values to the databag.
 
-        Has the same ultimate result as this, but uses a single relation-set call:
-        for k, v in dict(data).items():
-            self[k] = v
-        for k, v in kwargs.items():
-            self[k] = v
+        Has the same ultimate result as this, but uses a single relation-set call::
+
+            for k, v in dict(data).items():
+                self[k] = v
+            for k, v in kwargs.items():
+                self[k] = v
         """
         data = dict(data)
         data.update(kwargs)

Although I think maybe it's only the extra : that's strictly needed.

The PR title needs updating, as you did with the description.

@tonyandrewmeyer
Copy link
Contributor

We probably need a comment on #817 that explains why __setitem__ isn't being done after all. I think this will still auto-close that issue, and it probably should instead be closed as not-completed.

@james-garner-canonical james-garner-canonical changed the title feat: add item assignment support to RelationData object. feat: efficient implementation of RelationDataContent.update Mar 4, 2025
@@ -513,7 +531,6 @@ def test_relation_data_del_missing_key(self, harness: ops.testing.Harness[ops.Ch
('relation_ids', 'db1'),
('relation_list', relation_id),
('relation_get', relation_id, 'myapp/0', False),
('update_relation_data', relation_id, harness.model.unit, 'port', ''),
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Deleting an already missing key no longer hits the backend

Copy link
Contributor

@tonyandrewmeyer tonyandrewmeyer left a comment

Choose a reason for hiding this comment

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

Looks really great - thanks for making those changes!

It's a shame that we can't really do the originally requested feature, but I think this is a nice performance improvement, and it generally makes the code nicer since it follows Juju more closely.

@james-garner-canonical james-garner-canonical merged commit 8add8a0 into canonical:main Mar 19, 2025
32 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add item assignment support for 'RelationData' object
4 participants