ops 3.5.2__tar.gz → 3.7.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (137) hide show
  1. {ops-3.5.2 → ops-3.7.0}/CHANGES.md +70 -0
  2. {ops-3.5.2 → ops-3.7.0}/HACKING.md +3 -0
  3. {ops-3.5.2 → ops-3.7.0}/PKG-INFO +4 -4
  4. {ops-3.5.2 → ops-3.7.0}/README.md +1 -1
  5. {ops-3.5.2 → ops-3.7.0}/STYLE.md +8 -0
  6. {ops-3.5.2/test/charms/test_main/lib → ops-3.7.0}/ops/_main.py +1 -1
  7. {ops-3.5.2 → ops-3.7.0}/ops/_private/harness.py +15 -11
  8. {ops-3.5.2 → ops-3.7.0}/ops/charm.py +2 -2
  9. {ops-3.5.2/test/charms/test_main/lib → ops-3.7.0}/ops/framework.py +2 -2
  10. {ops-3.5.2/test/charms/test_main/lib → ops-3.7.0}/ops/hookcmds/_types.py +1 -1
  11. {ops-3.5.2 → ops-3.7.0}/ops/log.py +2 -2
  12. {ops-3.5.2/test/charms/test_main/lib → ops-3.7.0}/ops/model.py +23 -15
  13. {ops-3.5.2/test/charms/test_main/lib → ops-3.7.0}/ops/pebble.py +27 -27
  14. {ops-3.5.2/test/charms/test_main/lib → ops-3.7.0}/ops/storage.py +2 -2
  15. {ops-3.5.2/test/charms/test_main/lib → ops-3.7.0}/ops/testing.py +2 -2
  16. {ops-3.5.2/test/charms/test_main/lib → ops-3.7.0}/ops/version.py +1 -1
  17. {ops-3.5.2 → ops-3.7.0}/ops.egg-info/PKG-INFO +4 -4
  18. {ops-3.5.2 → ops-3.7.0}/ops.egg-info/requires.txt +2 -2
  19. {ops-3.5.2 → ops-3.7.0}/pyproject.toml +42 -5
  20. {ops-3.5.2 → ops-3.7.0/test/charms/test_main/lib}/ops/_main.py +1 -1
  21. {ops-3.5.2 → ops-3.7.0}/test/charms/test_main/lib/ops/_private/harness.py +15 -11
  22. {ops-3.5.2 → ops-3.7.0}/test/charms/test_main/lib/ops/charm.py +2 -2
  23. {ops-3.5.2 → ops-3.7.0/test/charms/test_main/lib}/ops/framework.py +2 -2
  24. {ops-3.5.2 → ops-3.7.0/test/charms/test_main/lib}/ops/hookcmds/_types.py +1 -1
  25. {ops-3.5.2 → ops-3.7.0}/test/charms/test_main/lib/ops/log.py +2 -2
  26. {ops-3.5.2 → ops-3.7.0/test/charms/test_main/lib}/ops/model.py +23 -15
  27. {ops-3.5.2 → ops-3.7.0/test/charms/test_main/lib}/ops/pebble.py +27 -27
  28. {ops-3.5.2 → ops-3.7.0/test/charms/test_main/lib}/ops/storage.py +2 -2
  29. {ops-3.5.2 → ops-3.7.0/test/charms/test_main/lib}/ops/testing.py +2 -2
  30. {ops-3.5.2 → ops-3.7.0/test/charms/test_main/lib}/ops/version.py +1 -1
  31. {ops-3.5.2 → ops-3.7.0}/test/test_model.py +11 -0
  32. {ops-3.5.2 → ops-3.7.0}/test/test_model_relation_data_class.py +53 -12
  33. {ops-3.5.2 → ops-3.7.0}/test/test_pebble.py +6 -8
  34. {ops-3.5.2 → ops-3.7.0}/tox.ini +3 -1
  35. {ops-3.5.2 → ops-3.7.0}/AGENTS.md +0 -0
  36. {ops-3.5.2 → ops-3.7.0}/CODE_OF_CONDUCT.md +0 -0
  37. {ops-3.5.2 → ops-3.7.0}/CONTRIBUTING.md +0 -0
  38. {ops-3.5.2 → ops-3.7.0}/LICENSE.txt +0 -0
  39. {ops-3.5.2 → ops-3.7.0}/MANIFEST.in +0 -0
  40. {ops-3.5.2 → ops-3.7.0}/SECURITY.md +0 -0
  41. {ops-3.5.2 → ops-3.7.0}/ops/__init__.py +0 -0
  42. {ops-3.5.2 → ops-3.7.0}/ops/_private/__init__.py +0 -0
  43. {ops-3.5.2 → ops-3.7.0}/ops/_private/timeconv.py +0 -0
  44. {ops-3.5.2 → ops-3.7.0}/ops/_private/yaml.py +0 -0
  45. {ops-3.5.2 → ops-3.7.0}/ops/hookcmds/__init__.py +0 -0
  46. {ops-3.5.2 → ops-3.7.0}/ops/hookcmds/_action.py +0 -0
  47. {ops-3.5.2 → ops-3.7.0}/ops/hookcmds/_other.py +0 -0
  48. {ops-3.5.2 → ops-3.7.0}/ops/hookcmds/_port.py +0 -0
  49. {ops-3.5.2 → ops-3.7.0}/ops/hookcmds/_relation.py +0 -0
  50. {ops-3.5.2 → ops-3.7.0}/ops/hookcmds/_secret.py +0 -0
  51. {ops-3.5.2 → ops-3.7.0}/ops/hookcmds/_state.py +0 -0
  52. {ops-3.5.2 → ops-3.7.0}/ops/hookcmds/_status.py +0 -0
  53. {ops-3.5.2 → ops-3.7.0}/ops/hookcmds/_storage.py +0 -0
  54. {ops-3.5.2 → ops-3.7.0}/ops/hookcmds/_utils.py +0 -0
  55. {ops-3.5.2 → ops-3.7.0}/ops/jujucontext.py +0 -0
  56. {ops-3.5.2 → ops-3.7.0}/ops/jujuversion.py +0 -0
  57. {ops-3.5.2 → ops-3.7.0}/ops/lib/__init__.py +0 -0
  58. {ops-3.5.2 → ops-3.7.0}/ops/main.py +0 -0
  59. {ops-3.5.2 → ops-3.7.0}/ops/py.typed +0 -0
  60. {ops-3.5.2 → ops-3.7.0}/ops.egg-info/SOURCES.txt +0 -0
  61. {ops-3.5.2 → ops-3.7.0}/ops.egg-info/dependency_links.txt +0 -0
  62. {ops-3.5.2 → ops-3.7.0}/ops.egg-info/top_level.txt +0 -0
  63. {ops-3.5.2 → ops-3.7.0}/setup.cfg +0 -0
  64. {ops-3.5.2 → ops-3.7.0}/test/__init__.py +0 -0
  65. {ops-3.5.2 → ops-3.7.0}/test/benchmark/__init__.py +0 -0
  66. {ops-3.5.2 → ops-3.7.0}/test/bin/relation-ids +0 -0
  67. {ops-3.5.2 → ops-3.7.0}/test/bin/relation-list +0 -0
  68. {ops-3.5.2 → ops-3.7.0}/test/charms/__init__.py +0 -0
  69. {ops-3.5.2 → ops-3.7.0}/test/charms/test_main/actions.yaml +0 -0
  70. {ops-3.5.2 → ops-3.7.0}/test/charms/test_main/config.yaml +0 -0
  71. {ops-3.5.2 → ops-3.7.0}/test/charms/test_main/lib/__init__.py +0 -0
  72. {ops-3.5.2 → ops-3.7.0}/test/charms/test_main/lib/ops/__init__.py +0 -0
  73. {ops-3.5.2 → ops-3.7.0}/test/charms/test_main/lib/ops/_private/__init__.py +0 -0
  74. {ops-3.5.2 → ops-3.7.0}/test/charms/test_main/lib/ops/_private/timeconv.py +0 -0
  75. {ops-3.5.2 → ops-3.7.0}/test/charms/test_main/lib/ops/_private/yaml.py +0 -0
  76. {ops-3.5.2 → ops-3.7.0}/test/charms/test_main/lib/ops/hookcmds/__init__.py +0 -0
  77. {ops-3.5.2 → ops-3.7.0}/test/charms/test_main/lib/ops/hookcmds/_action.py +0 -0
  78. {ops-3.5.2 → ops-3.7.0}/test/charms/test_main/lib/ops/hookcmds/_other.py +0 -0
  79. {ops-3.5.2 → ops-3.7.0}/test/charms/test_main/lib/ops/hookcmds/_port.py +0 -0
  80. {ops-3.5.2 → ops-3.7.0}/test/charms/test_main/lib/ops/hookcmds/_relation.py +0 -0
  81. {ops-3.5.2 → ops-3.7.0}/test/charms/test_main/lib/ops/hookcmds/_secret.py +0 -0
  82. {ops-3.5.2 → ops-3.7.0}/test/charms/test_main/lib/ops/hookcmds/_state.py +0 -0
  83. {ops-3.5.2 → ops-3.7.0}/test/charms/test_main/lib/ops/hookcmds/_status.py +0 -0
  84. {ops-3.5.2 → ops-3.7.0}/test/charms/test_main/lib/ops/hookcmds/_storage.py +0 -0
  85. {ops-3.5.2 → ops-3.7.0}/test/charms/test_main/lib/ops/hookcmds/_utils.py +0 -0
  86. {ops-3.5.2 → ops-3.7.0}/test/charms/test_main/lib/ops/jujucontext.py +0 -0
  87. {ops-3.5.2 → ops-3.7.0}/test/charms/test_main/lib/ops/jujuversion.py +0 -0
  88. {ops-3.5.2 → ops-3.7.0}/test/charms/test_main/lib/ops/lib/__init__.py +0 -0
  89. {ops-3.5.2 → ops-3.7.0}/test/charms/test_main/lib/ops/main.py +0 -0
  90. {ops-3.5.2 → ops-3.7.0}/test/charms/test_main/lib/ops/py.typed +0 -0
  91. {ops-3.5.2 → ops-3.7.0}/test/charms/test_main/metadata.yaml +0 -0
  92. {ops-3.5.2 → ops-3.7.0}/test/charms/test_main/src/charm.py +0 -0
  93. {ops-3.5.2 → ops-3.7.0}/test/charms/test_relation/.gitignore +0 -0
  94. {ops-3.5.2 → ops-3.7.0}/test/charms/test_relation/charmcraft.yaml +0 -0
  95. {ops-3.5.2 → ops-3.7.0}/test/charms/test_relation/pyproject.toml +0 -0
  96. {ops-3.5.2 → ops-3.7.0}/test/charms/test_relation/src/charm.py +0 -0
  97. {ops-3.5.2 → ops-3.7.0}/test/charms/test_secrets/.gitignore +0 -0
  98. {ops-3.5.2 → ops-3.7.0}/test/charms/test_secrets/__init__.py +0 -0
  99. {ops-3.5.2 → ops-3.7.0}/test/charms/test_secrets/charmcraft.yaml +0 -0
  100. {ops-3.5.2 → ops-3.7.0}/test/charms/test_secrets/pyproject.toml +0 -0
  101. {ops-3.5.2 → ops-3.7.0}/test/charms/test_secrets/src/__init__.py +0 -0
  102. {ops-3.5.2 → ops-3.7.0}/test/charms/test_secrets/src/charm.py +0 -0
  103. {ops-3.5.2 → ops-3.7.0}/test/charms/test_smoke/.gitignore +0 -0
  104. {ops-3.5.2 → ops-3.7.0}/test/charms/test_smoke/README.md +0 -0
  105. {ops-3.5.2 → ops-3.7.0}/test/charms/test_smoke/metadata.yaml +0 -0
  106. {ops-3.5.2 → ops-3.7.0}/test/charms/test_smoke/reference-pyproject.toml +0 -0
  107. {ops-3.5.2 → ops-3.7.0}/test/charms/test_smoke/src/charm.py +0 -0
  108. {ops-3.5.2 → ops-3.7.0}/test/charms/test_tracing/.gitignore +0 -0
  109. {ops-3.5.2 → ops-3.7.0}/test/charms/test_tracing/charmcraft.yaml +0 -0
  110. {ops-3.5.2 → ops-3.7.0}/test/charms/test_tracing/pyproject.toml +0 -0
  111. {ops-3.5.2 → ops-3.7.0}/test/charms/test_tracing/src/charm.py +0 -0
  112. {ops-3.5.2 → ops-3.7.0}/test/conftest.py +0 -0
  113. {ops-3.5.2 → ops-3.7.0}/test/fake_pebble.py +0 -0
  114. {ops-3.5.2 → ops-3.7.0}/test/integration/__init__.py +0 -0
  115. {ops-3.5.2 → ops-3.7.0}/test/integration/conftest.py +0 -0
  116. {ops-3.5.2 → ops-3.7.0}/test/integration/test_relation.py +0 -0
  117. {ops-3.5.2 → ops-3.7.0}/test/integration/test_secrets.py +0 -0
  118. {ops-3.5.2 → ops-3.7.0}/test/integration/test_tracing.py +0 -0
  119. {ops-3.5.2 → ops-3.7.0}/test/pebble_cli.py +0 -0
  120. {ops-3.5.2 → ops-3.7.0}/test/smoke/test_smoke.py +0 -0
  121. {ops-3.5.2 → ops-3.7.0}/test/test_charm.py +0 -0
  122. {ops-3.5.2 → ops-3.7.0}/test/test_framework.py +0 -0
  123. {ops-3.5.2 → ops-3.7.0}/test/test_helpers.py +0 -0
  124. {ops-3.5.2 → ops-3.7.0}/test/test_hookcmds.py +0 -0
  125. {ops-3.5.2 → ops-3.7.0}/test/test_infra.py +0 -0
  126. {ops-3.5.2 → ops-3.7.0}/test/test_jujucontext.py +0 -0
  127. {ops-3.5.2 → ops-3.7.0}/test/test_jujuversion.py +0 -0
  128. {ops-3.5.2 → ops-3.7.0}/test/test_lib.py +0 -0
  129. {ops-3.5.2 → ops-3.7.0}/test/test_log.py +0 -0
  130. {ops-3.5.2 → ops-3.7.0}/test/test_main.py +0 -0
  131. {ops-3.5.2 → ops-3.7.0}/test/test_main_invocation.py +0 -0
  132. {ops-3.5.2 → ops-3.7.0}/test/test_main_type_hint.py +0 -0
  133. {ops-3.5.2 → ops-3.7.0}/test/test_real_pebble.py +0 -0
  134. {ops-3.5.2 → ops-3.7.0}/test/test_storage.py +0 -0
  135. {ops-3.5.2 → ops-3.7.0}/test/test_testing.py +0 -0
  136. {ops-3.5.2 → ops-3.7.0}/test/test_timeconv.py +0 -0
  137. {ops-3.5.2 → ops-3.7.0}/test/test_yaml.py +0 -0
@@ -1,3 +1,73 @@
1
+ # 3.7.0 - 30 March 2026
2
+
3
+ ## Features
4
+
5
+ * Enable interactive debugging via `breakpoint` in testing (#2363)
6
+ * ops.testing autoload support for charmcraft extensions (#2367)
7
+ * Allow testing.State.get_relation to accept relation objects (#2359)
8
+ * Support charmcraft.yaml format as meta for testing.Context (#2296)
9
+
10
+ ## Fixes
11
+
12
+ * Correct type annotation for StorageMeta.properties (#2348)
13
+ * Move the testing.Container compatibility import so that mypy style checkers understand it (#2343)
14
+ * Hold only copies of user provided meta/config/actions in testing.Context (#2349)
15
+ * Deep-copy layer objects during testing.State plan rendering (#2380)
16
+ * Return copies from testing.State secret_get and action_get (#2379)
17
+ * Use timezone-aware datetimes in expiry calculation (#2378)
18
+ * Warn before clearing non-empty container in testing (#2365)
19
+
20
+ ## Documentation
21
+
22
+ * Replace links to juju.is by canonical.com/juju (#2368)
23
+ * Refactor homepage to better put Ops in context (#2370)
24
+ * Add pytest-operator migration guide from Jubilant docs (#2381)
25
+ * Add a tip about AI help in the Jubilant migration guide (#2382)
26
+ * Mention jhack scenario snapshot (#2351)
27
+ * Update integration testing how-to guide (#2390)
28
+ * Explain K8s charms briefly at the start of the tutorial (#2392)
29
+ * Juju secrets identifier is now an opaque string (#2387)
30
+
31
+ ## Tests
32
+
33
+ * Extend the type checking of the ops-scenario tests (#2230)
34
+
35
+ ## CI
36
+
37
+ * Run ruff check --fix in tox -e format (#2369)
38
+ * Check example charms with mypy in CI (#2360)
39
+ * Update the list of published charms in the compatibility tests (#2384)
40
+ * Adjust minimum Python version in broad charm compatibility tests (#2317)
41
+
42
+ # 3.6.0 - 26 February 2026
43
+
44
+ ## Features
45
+
46
+ * Bump default Juju version in `ops.testing.Context` to 3.6.14 (#2316)
47
+
48
+ ## Fixes
49
+
50
+ * Correct the `Model.get_binding()` return type (#2329)
51
+ * Only show executable in `ExecError.__str__`, not full command line (#2336)
52
+ * Support Pydantic `MISSING` sentinel in `ops.Relation.save` (#2306)
53
+
54
+ ## Documentation
55
+
56
+ * Add how-to subcategory for managing containers (#2309)
57
+ * Remove 2.19 version in docs, tweak ops.testing title (#2332)
58
+ * Use "true" and "false" consistently in the reference documentation (#2330)
59
+ * Add CLI args as another place to not put sensitive data (#2334)
60
+ * Fix remote unit kwarg in testing example (#2342)
61
+ * Clarify that secret labels are not names (#2337)
62
+
63
+ ## Tests
64
+
65
+ * Set `SCENARIO_BARE_CHARM_ERRORS=true` in Ops tests that care (#2314)
66
+
67
+ ## CI
68
+
69
+ * Fix releasing on branches with no `versions.md` doc (#2323)
70
+
1
71
  # 3.5.2 - 11 February 2026
2
72
 
3
73
  ## Fixes
@@ -18,6 +18,9 @@ uv tool install tox --with tox-uv
18
18
  uv tool update-shell
19
19
  ```
20
20
 
21
+ Optionally, to run checks automatically before each commit, install
22
+ [pre-commit](https://pre-commit.com/#install) and run `pre-commit install`.
23
+
21
24
  You can validate that you have a working installation by running:
22
25
 
23
26
  ```sh
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ops
3
- Version: 3.5.2
3
+ Version: 3.7.0
4
4
  Summary: The Python library behind great charms
5
5
  Author: The Charm Tech team at Canonical Ltd.
6
6
  License-Expression: Apache-2.0
@@ -22,9 +22,9 @@ Requires-Dist: PyYAML==6.*
22
22
  Requires-Dist: websocket-client==1.*
23
23
  Requires-Dist: opentelemetry-api~=1.0
24
24
  Provides-Extra: testing
25
- Requires-Dist: ops-scenario==8.5.2; extra == "testing"
25
+ Requires-Dist: ops-scenario==8.7.0; extra == "testing"
26
26
  Provides-Extra: tracing
27
- Requires-Dist: ops-tracing==3.5.2; extra == "tracing"
27
+ Requires-Dist: ops-tracing==3.7.0; extra == "tracing"
28
28
  Provides-Extra: harness
29
29
  Dynamic: license-file
30
30
 
@@ -32,7 +32,7 @@ Dynamic: license-file
32
32
 
33
33
  ![CI Status](https://github.com/canonical/operator/actions/workflows/framework-tests.yaml/badge.svg)
34
34
 
35
- The `ops` library is a Python framework for developing and testing Kubernetes and machine [charms](https://charmhub.io/). While charms can be written in any language, `ops` defines the latest standard, and charmers are encouraged to use Python with `ops` for all charms. The library is an official component of the Charm SDK, itself a part of [the Juju universe](https://juju.is/).
35
+ The `ops` library is a Python framework for developing and testing Kubernetes and machine [charms](https://charmhub.io/). While charms can be written in any language, `ops` defines the latest standard, and charmers are encouraged to use Python with `ops` for all charms. The library is an official component of the Charm SDK, itself a part of [the Juju universe](https://canonical.com/juju).
36
36
 
37
37
  > - `ops` is [available on PyPI](https://pypi.org/project/ops/).
38
38
  > - The latest version of `ops` requires Python 3.10 or above.
@@ -2,7 +2,7 @@
2
2
 
3
3
  ![CI Status](https://github.com/canonical/operator/actions/workflows/framework-tests.yaml/badge.svg)
4
4
 
5
- The `ops` library is a Python framework for developing and testing Kubernetes and machine [charms](https://charmhub.io/). While charms can be written in any language, `ops` defines the latest standard, and charmers are encouraged to use Python with `ops` for all charms. The library is an official component of the Charm SDK, itself a part of [the Juju universe](https://juju.is/).
5
+ The `ops` library is a Python framework for developing and testing Kubernetes and machine [charms](https://charmhub.io/). While charms can be written in any language, `ops` defines the latest standard, and charmers are encouraged to use Python with `ops` for all charms. The library is an official component of the Charm SDK, itself a part of [the Juju universe](https://canonical.com/juju).
6
6
 
7
7
  > - `ops` is [available on PyPI](https://pypi.org/project/ops/).
8
8
  > - The latest version of `ops` requires Python 3.10 or above.
@@ -261,6 +261,14 @@ However, it's okay to use acronyms that are very well known in our domain, like
261
261
 
262
262
  Choice of words: For example, if the whole document uses "mandatory", you probably shouldn't use "required" in a newly added paragraph. For another example, if the whole document uses "list foo" when adding new content, don't use "get a list of bar".
263
263
 
264
+ ### Use "true"/"false" consistently in docstrings
265
+
266
+ When describing boolean parameters in docstrings:
267
+
268
+ - Use lowercase "true"/"false" (no backticks) for truth-y/falsy concepts: "if true, create parent directories".
269
+ - Use double-backtick-quoted `` ``True`` ``/`` ``False`` `` when referring to the Python objects themselves: "pass ``True`` to enable".
270
+ - Do not use bare "True"/"False" (capitalised without backticks).
271
+
264
272
  ### Be precise
265
273
 
266
274
  Be precise in names and verbs. Use precise verbs to describe the behaviour. For example, the appropriate description of `/v1/services` is "list services", while "get a service" is probably a better fit for `/v1/services/{name}`.
@@ -212,7 +212,7 @@ class _Dispatcher:
212
212
  self.event_name = name
213
213
 
214
214
  def is_restricted_context(self):
215
- """Return True if we are running in a restricted Juju context.
215
+ """Return ``True`` if we are running in a restricted Juju context.
216
216
 
217
217
  When in a restricted context, most commands (relation-get, config-get,
218
218
  state-get) are not available. As such, we change how we interact with
@@ -184,7 +184,7 @@ class ActionFailed(Exception): # noqa: N818 (name doesn't end with "Error")
184
184
  state: State | None
185
185
  """The Juju state after the action has been run.
186
186
 
187
- When using Harness.run_action, this will be None.
187
+ When using Harness.run_action, this will be ``None``.
188
188
  """
189
189
 
190
190
  def __init__(
@@ -450,7 +450,7 @@ class Harness(Generic[CharmType]):
450
450
  should be active when the charm starts, and then call this method. This method will
451
451
  automatically create and add peer relations that are specified in metadata.yaml.
452
452
 
453
- If the charm metadata specifies containers, this sets can_connect to True for all
453
+ If the charm metadata specifies containers, this sets can_connect to ``True`` for all
454
454
  containers (in addition to triggering pebble-ready for each).
455
455
 
456
456
  Example::
@@ -801,8 +801,8 @@ class Harness(Generic[CharmType]):
801
801
  Args:
802
802
  storage_name: The storage backend name on the Charm
803
803
  count: Number of disks being added
804
- attach: True to also attach the storage mount; if :meth:`begin`
805
- has been called a True value will also emit storage-attached
804
+ attach: If true, also attach the storage mount; if :meth:`begin`
805
+ has been called a true value will also emit storage-attached
806
806
 
807
807
  Return:
808
808
  A list of storage IDs, e.g. ["my-storage/1", "my-storage/2"].
@@ -1646,7 +1646,7 @@ class Harness(Generic[CharmType]):
1646
1646
  do/don't trigger extra calls.
1647
1647
 
1648
1648
  Args:
1649
- reset: If True, reset the calls list back to empty, if false, the call list is
1649
+ reset: If true, reset the calls list back to empty, if false, the call list is
1650
1650
  preserved.
1651
1651
 
1652
1652
  Return:
@@ -1859,7 +1859,7 @@ class Harness(Generic[CharmType]):
1859
1859
 
1860
1860
  Args:
1861
1861
  secret_id: The ID of the secret associated with the event.
1862
- label: Label value to send to the event. If None, the secret's
1862
+ label: Label value to send to the event. If ``None``, the secret's
1863
1863
  label is used.
1864
1864
  """
1865
1865
  secret = self._ensure_secret(secret_id)
@@ -1880,7 +1880,7 @@ class Harness(Generic[CharmType]):
1880
1880
  secret_id: The ID of the secret associated with the event.
1881
1881
  revision: Revision number to provide to the event. This should be
1882
1882
  an item from the list returned by :meth:`get_secret_revisions`.
1883
- label: Label value to send to the event. If None, the secret's
1883
+ label: Label value to send to the event. If ``None``, the secret's
1884
1884
  label is used.
1885
1885
  """
1886
1886
  secret = self._ensure_secret(secret_id)
@@ -1901,7 +1901,7 @@ class Harness(Generic[CharmType]):
1901
1901
  secret_id: The ID of the secret associated with the event.
1902
1902
  revision: Revision number to provide to the event. This should be
1903
1903
  an item from the list returned by :meth:`get_secret_revisions`.
1904
- label: Label value to send to the event. If None, the secret's
1904
+ label: Label value to send to the event. If ``None``, the secret's
1905
1905
  label is used.
1906
1906
  """
1907
1907
  secret = self._ensure_secret(secret_id)
@@ -2545,7 +2545,7 @@ class _TestingModelBackend:
2545
2545
 
2546
2546
  Args:
2547
2547
  name: name (i.e. from metadata.yaml).
2548
- include_detached: True to include unattached storage mounts as well.
2548
+ include_detached: If true, include unattached storage mounts as well.
2549
2549
  """
2550
2550
  return [
2551
2551
  index
@@ -2599,7 +2599,11 @@ class _TestingModelBackend:
2599
2599
  self._storage_attached[name].remove(index)
2600
2600
 
2601
2601
  def _storage_attach(self, storage_id: str):
2602
- """Mark the named storage_id as attached and return True if it was previously detached."""
2602
+ """Mark the named storage_id as attached.
2603
+
2604
+ Returns:
2605
+ ``True`` if it was previously detached.
2606
+ """
2603
2607
  # NOTE: This is an extra function for _TestingModelBackend to simulate
2604
2608
  # re-attachment of a storage unit. This is not present in
2605
2609
  # ops.model._ModelBackend.
@@ -2910,7 +2914,7 @@ class _TestingModelBackend:
2910
2914
 
2911
2915
  @classmethod
2912
2916
  def _generate_secret_id(cls) -> str:
2913
- # Not a proper Juju secrets-style xid, but that's okay
2917
+ # Not a proper Juju secrets identifier, but that's okay
2914
2918
  return f'secret:{uuid.uuid4()}'
2915
2919
 
2916
2920
  def secret_add(
@@ -2019,7 +2019,7 @@ class RelationMeta:
2019
2019
  """
2020
2020
 
2021
2021
  optional: bool
2022
- """If True, the relation is considered optional.
2022
+ """If true, the relation is considered optional.
2023
2023
 
2024
2024
  This value is informational only and is not used by Juju itself (all
2025
2025
  relations are optional from Juju's perspective), but it may be set in
@@ -2079,7 +2079,7 @@ class StorageMeta:
2079
2079
  multiple_range: tuple[int, int | None] | None
2080
2080
  """Range of numeric qualifiers when multiple storage units are used."""
2081
2081
 
2082
- properties = list[str]
2082
+ properties: list[str]
2083
2083
  """List of additional characteristics of the storage."""
2084
2084
 
2085
2085
  def __init__(self, name: str, raw: _StorageMetaDict):
@@ -78,7 +78,7 @@ class Handle:
78
78
  """Handle defines a name for an object in the form of a hierarchical path.
79
79
 
80
80
  The provided parent is the object (or that object's handle) that this handle
81
- sits under, or None if the object identified by this handle stands by itself
81
+ sits under, or ``None`` if the object identified by this handle stands by itself
82
82
  as the root of its own hierarchy.
83
83
 
84
84
  The handle kind is a string that defines a namespace so objects with the
@@ -1231,7 +1231,7 @@ class StoredState:
1231
1231
  Data is stored alongside the charm (in the charm container for Kubernetes
1232
1232
  sidecar charms, and on the machine for machine charms). The exceptions are
1233
1233
  two deprecated cases: Kubernetes podspec charms, and charms explicitly
1234
- passing `True` for `use_juju_for_storage` when running :meth:`ops.main`.
1234
+ passing ``True`` for ``use_juju_for_storage`` when running :meth:`ops.main`.
1235
1235
 
1236
1236
  For machine charms, charms are upgraded in-place on the machine, so the data
1237
1237
  is preserved. For Kubernetes sidecar charms, when the charm is upgraded, the
@@ -154,7 +154,7 @@ class CloudSpec:
154
154
  """Whether to skip TLS verification."""
155
155
 
156
156
  is_controller_cloud: bool = False
157
- """If this is the cloud used by the controller, defaults to False."""
157
+ """If this is the cloud used by the controller, defaults to ``False``."""
158
158
 
159
159
  @classmethod
160
160
  def _from_dict(cls, d: dict[str, Any]) -> CloudSpec:
@@ -61,8 +61,8 @@ def setup_root_logging(
61
61
 
62
62
  Args:
63
63
  model_backend: a ModelBackend to use for juju-log
64
- debug: if True, write logs to stderr as well as to juju-log.
65
- exc_stderr: if True, write uncaught exceptions to stderr as well as to juju-log.
64
+ debug: if true, write logs to stderr as well as to juju-log.
65
+ exc_stderr: if true, write uncaught exceptions to stderr as well as to juju-log.
66
66
  """
67
67
  logger = logging.getLogger()
68
68
  logger.setLevel(logging.DEBUG)
@@ -259,7 +259,7 @@ class Model:
259
259
  """
260
260
  return self.relations._get_unique(relation_name, relation_id)
261
261
 
262
- def get_binding(self, binding_key: str | Relation) -> Binding | None:
262
+ def get_binding(self, binding_key: str | Relation) -> Binding:
263
263
  """Get a network space binding.
264
264
 
265
265
  Args:
@@ -536,7 +536,7 @@ def _calculate_expiry(
536
536
  if isinstance(expire, datetime.datetime):
537
537
  return expire
538
538
  elif isinstance(expire, datetime.timedelta):
539
- return datetime.datetime.now() + expire
539
+ return datetime.datetime.now(tz=datetime.timezone.utc) + expire
540
540
  else:
541
541
  raise TypeError(
542
542
  'Expiration time must be a datetime or timedelta from now, '
@@ -1382,7 +1382,7 @@ class Secret:
1382
1382
  identifier for identifying one secret in a set of secrets of arbitrary
1383
1383
  size, use :attr:`unique_identifier` -- this should be rare.)
1384
1384
 
1385
- This will be None if the secret was obtained using
1385
+ This will be ``None`` if the secret was obtained using
1386
1386
  :meth:`Model.get_secret` with a label but no ID.
1387
1387
  """
1388
1388
  return self._id
@@ -1391,8 +1391,7 @@ class Secret:
1391
1391
  def unique_identifier(self) -> str | None:
1392
1392
  """Unique identifier of this secret.
1393
1393
 
1394
- This is the secret's globally-unique identifier (currently a
1395
- 20-character Xid, for example "9m4e2mr0ui3e8a215n4g").
1394
+ This is the secret's globally-unique identifier (alphanumeric).
1396
1395
 
1397
1396
  Charms should use :attr:`id` (the secret's locator ID) to send
1398
1397
  the secret's ID across relation data, and labels (:attr:`label`) to
@@ -1401,7 +1400,7 @@ class Secret:
1401
1400
  cases where the charm has a set of secrets of arbitrary size, for
1402
1401
  example, a group of 10 or 20 TLS certificates.
1403
1402
 
1404
- This will be None if the secret was obtained using
1403
+ This will be ``None`` if the secret was obtained using
1405
1404
  :meth:`Model.get_secret` with a label but no ID.
1406
1405
  """
1407
1406
  if self._id is None:
@@ -1445,7 +1444,7 @@ class Secret:
1445
1444
  Juju will ensure that the entity (the owner or observer) only has one
1446
1445
  secret with this label at once.
1447
1446
 
1448
- This will be None if the secret was obtained using
1447
+ This will be ``None`` if the secret was obtained using
1449
1448
  :meth:`Model.get_secret` with an ID but no label.
1450
1449
  """
1451
1450
  return self._label
@@ -1873,6 +1872,10 @@ class Relation:
1873
1872
  # data.destination will be stored under the Juju relation key 'to'
1874
1873
  relation.save(data, self.unit)
1875
1874
 
1875
+ If a Pydantic model's ``model_dump`` method omits any field (e.g. if its
1876
+ value is Pydantic's ``MISSING`` sentinel) the field will be erased from
1877
+ the relation data.
1878
+
1876
1879
  Args:
1877
1880
  obj: an object with attributes to save to the relation data, typically
1878
1881
  a Pydantic ``BaseModel`` subclass or dataclass.
@@ -1915,7 +1918,11 @@ class Relation:
1915
1918
  values = {field: getattr(obj, field) for field in fields}
1916
1919
 
1917
1920
  # Encode each value, and then pass it over to Juju.
1918
- data = {field: encoder(values[attr]) for attr, field in sorted(fields.items())}
1921
+ # Missing values are erased from the databag using empty string values.
1922
+ data = {
1923
+ field: encoder(values[attr]) if attr in values else ''
1924
+ for attr, field in sorted(fields.items())
1925
+ }
1919
1926
  self.data[dst].update(data)
1920
1927
 
1921
1928
 
@@ -2643,9 +2650,9 @@ class Container:
2643
2650
  combining).
2644
2651
  layer: A YAML string, configuration layer dict, or pebble.Layer
2645
2652
  object containing the Pebble layer to add.
2646
- combine: If combine is False (the default), append the new layer
2653
+ combine: If combine is false (the default), append the new layer
2647
2654
  as the top layer with the given label (must be unique). If
2648
- combine is True and the label already exists, the two layers
2655
+ combine is true and the label already exists, the two layers
2649
2656
  are combined into a single one considering the layer override
2650
2657
  rules; if the layer doesn't exist, it is added as usual.
2651
2658
  """
@@ -2798,7 +2805,7 @@ class Container:
2798
2805
  encoding: Encoding to use for encoding source str to bytes, or
2799
2806
  strings read from source if it is a TextIO type. Ignored if
2800
2807
  source is bytes or BinaryIO.
2801
- make_dirs: If True, create parent directories if they don't exist.
2808
+ make_dirs: If true, create parent directories if they don't exist.
2802
2809
  permissions: Permissions (mode) to create file with (Pebble default
2803
2810
  is 0o644).
2804
2811
  user_id: User ID (UID) for file. If neither ``group_id`` nor ``group`` is provided,
@@ -3036,6 +3043,7 @@ class Container:
3036
3043
  type=ftype,
3037
3044
  size=info.st_size,
3038
3045
  permissions=stat.S_IMODE(info.st_mode),
3046
+ # This is unused, but a required FileInfo field.
3039
3047
  last_modified=datetime.datetime.fromtimestamp(info.st_mtime),
3040
3048
  user_id=info.st_uid,
3041
3049
  user=pw_name,
@@ -3130,7 +3138,7 @@ class Container:
3130
3138
 
3131
3139
  Args:
3132
3140
  path: Path of the directory to create on the remote system.
3133
- make_parents: If True, create parent directories if they don't exist.
3141
+ make_parents: If true, create parent directories if they don't exist.
3134
3142
  permissions: Permissions (mode) to create directory with (Pebble
3135
3143
  default is 0o755).
3136
3144
  user_id: User ID (UID) for directory. If neither ``group_id`` nor ``group``
@@ -3158,13 +3166,13 @@ class Container:
3158
3166
 
3159
3167
  Args:
3160
3168
  path: Path of the file or directory to delete from the remote system.
3161
- recursive: If True, and path is a directory, recursively delete it and
3169
+ recursive: If true, and path is a directory, recursively delete it and
3162
3170
  everything under it. If path is a file, delete the file. In
3163
3171
  either case, do nothing if the file or directory does not
3164
3172
  exist. Behaviourally similar to ``rm -rf <file|dir>``.
3165
3173
 
3166
3174
  Raises:
3167
- pebble.PathError: If a relative path is provided, or if `recursive` is False
3175
+ pebble.PathError: If a relative path is provided, or if ``recursive`` is ``False``
3168
3176
  and the file or directory cannot be removed (it does not exist or is not empty).
3169
3177
  """
3170
3178
  self._pebble.remove_path(path, recursive=recursive)
@@ -4277,7 +4285,7 @@ class CloudSpec:
4277
4285
  """Whether to skip TLS verification."""
4278
4286
 
4279
4287
  is_controller_cloud: bool = False
4280
- """If this is the cloud used by the controller, defaults to False."""
4288
+ """If this is the cloud used by the controller, defaults to ``False``."""
4281
4289
 
4282
4290
  @classmethod
4283
4291
  def from_dict(cls, d: dict[str, Any]) -> CloudSpec:
@@ -540,9 +540,9 @@ class ExecError(Error, Generic[AnyStr]):
540
540
  """Standard error from the process.
541
541
 
542
542
  If :meth:`ExecProcess.wait_output` was being called and ``combine_stderr``
543
- was False, this is the captured stderr as a str (or bytes if encoding was
543
+ was ``False``, this is the captured stderr as a str (or bytes if encoding was
544
544
  None). If :meth:`ExecProcess.wait` was being called or ``combine_stderr``
545
- was True, this is None.
545
+ was ``True``, this is None.
546
546
  """
547
547
 
548
548
  def __init__(
@@ -558,7 +558,7 @@ class ExecError(Error, Generic[AnyStr]):
558
558
  self.stderr = stderr
559
559
 
560
560
  def __str__(self):
561
- message = f'non-zero exit code {self.exit_code} executing {self.command!r}'
561
+ message = f'non-zero exit code {self.exit_code} executing {self.command[0]!r}'
562
562
 
563
563
  for name, out in [('stdout', self.stdout), ('stderr', self.stderr)]:
564
564
  if out is None:
@@ -1069,7 +1069,7 @@ class ServiceInfo:
1069
1069
  self.current = current
1070
1070
 
1071
1071
  def is_running(self) -> bool:
1072
- """Return True if this service is running (in the active state)."""
1072
+ """Return ``True`` if this service is running (in the active state)."""
1073
1073
  return self.current == ServiceStatus.ACTIVE
1074
1074
 
1075
1075
  @classmethod
@@ -1425,7 +1425,7 @@ class CheckInfo:
1425
1425
  level: CheckLevel | str | None
1426
1426
  """Check level.
1427
1427
 
1428
- This can be :attr:`CheckLevel.ALIVE`, :attr:`CheckLevel.READY`, or None (level not set).
1428
+ This can be :attr:`CheckLevel.ALIVE`, :attr:`CheckLevel.READY`, or ``None`` (level not set).
1429
1429
  """
1430
1430
 
1431
1431
  startup: CheckStartup
@@ -1453,7 +1453,7 @@ class CheckInfo:
1453
1453
  :meth:`Client.start_checks`. It is reset to one when the check
1454
1454
  succeeds after the check's failure threshold was reached.
1455
1455
 
1456
- This will be None if the version of Pebble being queried doesn't return
1456
+ This will be ``None`` if the version of Pebble being queried doesn't return
1457
1457
  the ``successes`` field (introduced in Pebble v1.23.0).
1458
1458
  """
1459
1459
 
@@ -1472,7 +1472,7 @@ class CheckInfo:
1472
1472
  change_id: ChangeID | None
1473
1473
  """Change ID of ``perform-check`` or ``recover-check`` change driving this check.
1474
1474
 
1475
- This will be None on older versions of Pebble, which did not use changes
1475
+ This will be ``None`` on older versions of Pebble, which did not use changes
1476
1476
  to drive health checks.
1477
1477
  """
1478
1478
 
@@ -1708,7 +1708,7 @@ class ExecProcess(Generic[AnyStr]):
1708
1708
 
1709
1709
  If the stdin argument was not passed to :meth:`Client.exec`, this is a
1710
1710
  writable file-like object the caller can use to stream input to the
1711
- process. It is None if stdin was passed to :meth:`Client.exec`.
1711
+ process. It is ``None`` if stdin was passed to :meth:`Client.exec`.
1712
1712
  """
1713
1713
 
1714
1714
  stdout: IO[AnyStr] | None
@@ -1716,16 +1716,16 @@ class ExecProcess(Generic[AnyStr]):
1716
1716
 
1717
1717
  If the stdout argument was not passed to :meth:`Client.exec`, this is a
1718
1718
  readable file-like object the caller can use to stream output from the
1719
- process. It is None if stdout was passed to :meth:`Client.exec`.
1719
+ process. It is ``None`` if stdout was passed to :meth:`Client.exec`.
1720
1720
  """
1721
1721
 
1722
1722
  stderr: IO[AnyStr] | None
1723
1723
  """Standard error from the process.
1724
1724
 
1725
1725
  If the stderr argument was not passed to :meth:`Client.exec` and
1726
- ``combine_stderr`` was False, this is a readable file-like object the
1727
- caller can use to stream error output from the process. It is None if
1728
- stderr was passed to :meth:`Client.exec` or ``combine_stderr`` was True.
1726
+ ``combine_stderr`` was ``False``, this is a readable file-like object the
1727
+ caller can use to stream error output from the process. It is ``None`` if
1728
+ stderr was passed to :meth:`Client.exec` or ``combine_stderr`` was ``True``.
1729
1729
  """
1730
1730
 
1731
1731
  def __init__(
@@ -1820,8 +1820,8 @@ class ExecProcess(Generic[AnyStr]):
1820
1820
  """Wait for the process to finish and return tuple of (stdout, stderr).
1821
1821
 
1822
1822
  If a timeout was specified to the :meth:`Client.exec` call, this waits
1823
- at most that duration. If combine_stderr was True, stdout will include
1824
- the process's standard error, and stderr will be None.
1823
+ at most that duration. If combine_stderr was ``True``, stdout will include
1824
+ the process's standard error, and stderr will be ``None``.
1825
1825
 
1826
1826
  Raises:
1827
1827
  ChangeError: if there was an error starting or running the process.
@@ -1878,7 +1878,7 @@ class ExecProcess(Generic[AnyStr]):
1878
1878
 
1879
1879
 
1880
1880
  def _has_fileno(f: Any) -> bool:
1881
- """Return True if the file-like object has a valid fileno() method."""
1881
+ """Return ``True`` if the file-like object has a valid fileno() method."""
1882
1882
  try:
1883
1883
  f.fileno()
1884
1884
  return True
@@ -2443,7 +2443,7 @@ class Client:
2443
2443
  Args:
2444
2444
  change_id: Change ID of change to wait for.
2445
2445
  timeout: Maximum time in seconds to wait for the change to be
2446
- ready. It may be None, in which case wait_change never times out.
2446
+ ready. It may be ``None``, in which case wait_change never times out.
2447
2447
  delay: If polling, this is the delay in seconds between attempts.
2448
2448
 
2449
2449
  Returns:
@@ -2539,8 +2539,8 @@ class Client:
2539
2539
  def add_layer(self, label: str, layer: str | LayerDict | Layer, *, combine: bool = False):
2540
2540
  """Dynamically add a new layer onto the Pebble configuration layers.
2541
2541
 
2542
- If combine is False (the default), append the new layer as the top
2543
- layer with the given label. If combine is True and the label already
2542
+ If combine is false (the default), append the new layer as the top
2543
+ layer with the given label. If combine is true and the label already
2544
2544
  exists, the two layers are combined into a single one considering the
2545
2545
  layer override rules; if the layer doesn't exist, it is added as usual.
2546
2546
  """
@@ -2605,12 +2605,12 @@ class Client:
2605
2605
  Args:
2606
2606
  path: Path of the file to read from the remote system.
2607
2607
  encoding: Encoding to use for decoding the file's bytes to str,
2608
- or None to specify no decoding.
2608
+ or ``None`` to specify no decoding.
2609
2609
 
2610
2610
  Returns:
2611
2611
  A readable file-like object, whose read() method will return str
2612
2612
  objects decoded according to the specified encoding, or bytes if
2613
- encoding is None.
2613
+ encoding is ``None``.
2614
2614
 
2615
2615
  Raises:
2616
2616
  PathError: If there was an error reading the file at path, for
@@ -2689,7 +2689,7 @@ class Client:
2689
2689
  encoding: Encoding to use for encoding source str to bytes, or
2690
2690
  strings read from source if it is a TextIO type. Ignored if
2691
2691
  source is bytes or BinaryIO.
2692
- make_dirs: If True, create parent directories if they don't exist.
2692
+ make_dirs: If true, create parent directories if they don't exist.
2693
2693
  permissions: Permissions (mode) to create file with (Pebble default
2694
2694
  is 0o644).
2695
2695
  user_id: User ID (UID) for file. If neither ``group_id`` nor ``group`` is provided,
@@ -2849,7 +2849,7 @@ class Client:
2849
2849
 
2850
2850
  Args:
2851
2851
  path: Path of the directory to create on the remote system.
2852
- make_parents: If True, create parent directories if they don't exist.
2852
+ make_parents: If true, create parent directories if they don't exist.
2853
2853
  permissions: Permissions (mode) to create directory with (Pebble
2854
2854
  default is 0o755).
2855
2855
  user_id: User ID (UID) for directory. If neither ``group_id`` nor ``group``
@@ -2885,13 +2885,13 @@ class Client:
2885
2885
 
2886
2886
  Args:
2887
2887
  path: Path of the file or directory to delete from the remote system.
2888
- recursive: If True, and path is a directory, recursively delete it and
2888
+ recursive: If true, and path is a directory, recursively delete it and
2889
2889
  everything under it. If path is a file, delete the file. In
2890
2890
  either case, do nothing if the file or directory does not
2891
2891
  exist. Behaviourally similar to ``rm -rf <file|dir>``.
2892
2892
 
2893
2893
  Raises:
2894
- pebble.PathError: If a relative path is provided, or if `recursive` is False
2894
+ pebble.PathError: If a relative path is provided, or if ``recursive`` is ``False``
2895
2895
  and the file or directory cannot be removed (it does not exist or is not empty).
2896
2896
  """
2897
2897
  path = str(path)
@@ -3074,13 +3074,13 @@ class Client:
3074
3074
  :meth:`ExecProcess.wait_output` to capture error output as a
3075
3075
  string, or read from :meth:`ExecProcess.stderr` to stream
3076
3076
  error output from the process. Must be None if combine_stderr
3077
- is True.
3077
+ is ``True``.
3078
3078
  encoding: If encoding is set (the default is UTF-8), the types
3079
3079
  read or written to stdin/stdout/stderr are str, and encoding
3080
3080
  is used to encode them to bytes. If encoding is None, the
3081
3081
  types read or written are raw bytes.
3082
- combine_stderr: If True, process's stderr output is combined into
3083
- its stdout (the stderr argument must be None). If False,
3082
+ combine_stderr: If true, process's stderr output is combined into
3083
+ its stdout (the stderr argument must be None). If false,
3084
3084
  separate streams are used for stdout and stderr.
3085
3085
 
3086
3086
  Returns:
@@ -192,7 +192,7 @@ class SQLiteStorage:
192
192
 
193
193
  Args:
194
194
  event_path: If supplied, will only yield events that match event_path. If not
195
- supplied (or None/'') will return all events.
195
+ supplied (or ``None``/``''``) will return all events.
196
196
 
197
197
  Returns:
198
198
  Iterable of (event_path, observer_path, method_name) tuples
@@ -294,7 +294,7 @@ class JujuStorage:
294
294
 
295
295
  Args:
296
296
  event_path: If supplied, will only yield events that match event_path. If not
297
- supplied (or None/'') will return all events.
297
+ supplied (or ``None``/``''``) will return all events.
298
298
 
299
299
  Returns:
300
300
  Iterable of (event_path, observer_path, method_name) tuples