Repository: canonical/operator

Issue:
Optional parameter not saved in RelationMeta class
Charm authors can mark some of the relations as optional using 'optional' attribute in relations section (provides/requires/peers) in metadata.yaml [1], however the parameter is not saved in RelationMeta class.

The operator charms can perform some actions like updating configuration files, db sync once all the relations are established. However there could be cases where some relations are not mandatory and can be marked as Optional in metadata.yaml.
Since the information is not saved in RelationMeta class, currently it is mandatory to have all the relations established.   

[1] https://juju.is/docs/sdk/metadata-reference

Discussion:

Patch:
diff --git a/ops/charm.py b/ops/charm.py
index e2b39fa..77a1f0b 100755
--- a/ops/charm.py
+++ b/ops/charm.py
@@ -52,6 +52,7 @@ if TYPE_CHECKING:
         '_RelationMetaDict', {
             'interface': Required[str],
             'limit': int,
+            'optional': bool,
             'scope': _Scopes},
         total=False)
 
@@ -1300,6 +1301,14 @@ class RelationMeta:
     Will be either ``"global"`` or ``"container"``.
     """
 
+    optional: bool
+    """If True, the relation is considered optional.
+
+    This value is informational only and is not used by Juju itself (all
+    relations are optional from Juju's perspective), but it may be set in
+    ``metadata.yaml`` and used by the charm code if appropriate.
+    """
+
     VALID_SCOPES = ['global', 'container']
 
     def __init__(self, role: RelationRole, relation_name: str, raw: '_RelationMetaDict'):
@@ -1319,6 +1328,8 @@ class RelationMeta:
             raise TypeError("scope should be one of {}; not '{}'".format(
                 ', '.join(f"'{s}'" for s in self.VALID_SCOPES), self.scope))
 
+        self.optional = raw.get('optional', False)
+
 
 class StorageMeta:
     """Object containing metadata about a storage definition."""


Test patch:
diff --git a/test/test_charm.py b/test/test_charm.py
index e0b748c..e2fc510 100755
--- a/test/test_charm.py
+++ b/test/test_charm.py
@@ -362,7 +362,6 @@ containers:
         self.assertEqual(charm.count, 2)
 
     def test_relations_meta(self):
-
         # language=YAML
         self.meta = ops.CharmMeta.from_yaml('''
 name: my-charm
@@ -373,15 +372,18 @@ requires:
     scope: container
   metrics:
     interface: prometheus-scraping
+    optional: true
 ''')
 
         self.assertEqual(self.meta.requires['database'].interface_name, 'mongodb')
         self.assertEqual(self.meta.requires['database'].limit, 1)
         self.assertEqual(self.meta.requires['database'].scope, 'container')
+        self.assertFalse(self.meta.requires['database'].optional)
 
         self.assertEqual(self.meta.requires['metrics'].interface_name, 'prometheus-scraping')
         self.assertIsNone(self.meta.requires['metrics'].limit)
         self.assertEqual(self.meta.requires['metrics'].scope, 'global')  # Default value
+        self.assertTrue(self.meta.requires['metrics'].optional)
 
     def test_relations_meta_limit_type_validation(self):
         with self.assertRaisesRegex(TypeError, "limit should be an int, not <class 'str'>"):