ops 2.23.0.dev0__tar.gz → 2.23.2__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.
- {ops-2.23.0.dev0 → ops-2.23.2}/CHANGES.md +83 -0
- {ops-2.23.0.dev0 → ops-2.23.2}/PKG-INFO +8 -8
- {ops-2.23.0.dev0 → ops-2.23.2}/README.md +3 -3
- {ops-2.23.0.dev0/test/charms/test_main/lib → ops-2.23.2}/ops/_main.py +16 -10
- {ops-2.23.0.dev0 → ops-2.23.2}/ops/_private/harness.py +1 -1
- {ops-2.23.0.dev0 → ops-2.23.2}/ops/charm.py +2 -2
- {ops-2.23.0.dev0/test/charms/test_main/lib → ops-2.23.2}/ops/model.py +37 -17
- {ops-2.23.0.dev0/test/charms/test_main/lib → ops-2.23.2}/ops/version.py +1 -1
- {ops-2.23.0.dev0 → ops-2.23.2}/ops.egg-info/PKG-INFO +8 -8
- {ops-2.23.0.dev0 → ops-2.23.2}/ops.egg-info/requires.txt +2 -2
- {ops-2.23.0.dev0 → ops-2.23.2}/pyproject.toml +4 -4
- {ops-2.23.0.dev0 → ops-2.23.2/test/charms/test_main/lib}/ops/_main.py +16 -10
- {ops-2.23.0.dev0 → ops-2.23.2}/test/charms/test_main/lib/ops/_private/harness.py +1 -1
- {ops-2.23.0.dev0 → ops-2.23.2}/test/charms/test_main/lib/ops/charm.py +2 -2
- {ops-2.23.0.dev0 → ops-2.23.2/test/charms/test_main/lib}/ops/model.py +37 -17
- {ops-2.23.0.dev0 → ops-2.23.2/test/charms/test_main/lib}/ops/version.py +1 -1
- {ops-2.23.0.dev0 → ops-2.23.2}/test/test_model.py +21 -78
- {ops-2.23.0.dev0 → ops-2.23.2}/tox.ini +5 -2
- {ops-2.23.0.dev0 → ops-2.23.2}/CODE_OF_CONDUCT.md +0 -0
- {ops-2.23.0.dev0 → ops-2.23.2}/CONTRIBUTING.md +0 -0
- {ops-2.23.0.dev0 → ops-2.23.2}/HACKING.md +0 -0
- {ops-2.23.0.dev0 → ops-2.23.2}/LICENSE.txt +0 -0
- {ops-2.23.0.dev0 → ops-2.23.2}/MANIFEST.in +0 -0
- {ops-2.23.0.dev0 → ops-2.23.2}/SECURITY.md +0 -0
- {ops-2.23.0.dev0 → ops-2.23.2}/STYLE.md +0 -0
- {ops-2.23.0.dev0 → ops-2.23.2}/ops/__init__.py +0 -0
- {ops-2.23.0.dev0 → ops-2.23.2}/ops/_private/__init__.py +0 -0
- {ops-2.23.0.dev0 → ops-2.23.2}/ops/_private/timeconv.py +0 -0
- {ops-2.23.0.dev0 → ops-2.23.2}/ops/_private/yaml.py +0 -0
- {ops-2.23.0.dev0 → ops-2.23.2}/ops/framework.py +0 -0
- {ops-2.23.0.dev0 → ops-2.23.2}/ops/jujucontext.py +0 -0
- {ops-2.23.0.dev0 → ops-2.23.2}/ops/jujuversion.py +0 -0
- {ops-2.23.0.dev0 → ops-2.23.2}/ops/lib/__init__.py +0 -0
- {ops-2.23.0.dev0 → ops-2.23.2}/ops/log.py +0 -0
- {ops-2.23.0.dev0 → ops-2.23.2}/ops/main.py +0 -0
- {ops-2.23.0.dev0 → ops-2.23.2}/ops/pebble.py +0 -0
- {ops-2.23.0.dev0 → ops-2.23.2}/ops/py.typed +0 -0
- {ops-2.23.0.dev0 → ops-2.23.2}/ops/storage.py +0 -0
- {ops-2.23.0.dev0 → ops-2.23.2}/ops/testing.py +0 -0
- {ops-2.23.0.dev0 → ops-2.23.2}/ops.egg-info/SOURCES.txt +0 -0
- {ops-2.23.0.dev0 → ops-2.23.2}/ops.egg-info/dependency_links.txt +0 -0
- {ops-2.23.0.dev0 → ops-2.23.2}/ops.egg-info/top_level.txt +0 -0
- {ops-2.23.0.dev0 → ops-2.23.2}/setup.cfg +0 -0
- {ops-2.23.0.dev0 → ops-2.23.2}/test/__init__.py +0 -0
- {ops-2.23.0.dev0 → ops-2.23.2}/test/benchmark/__init__.py +0 -0
- {ops-2.23.0.dev0 → ops-2.23.2}/test/bin/relation-ids +0 -0
- {ops-2.23.0.dev0 → ops-2.23.2}/test/bin/relation-ids.bat +0 -0
- {ops-2.23.0.dev0 → ops-2.23.2}/test/bin/relation-list +0 -0
- {ops-2.23.0.dev0 → ops-2.23.2}/test/bin/relation-list.bat +0 -0
- {ops-2.23.0.dev0 → ops-2.23.2}/test/charms/test_main/actions.yaml +0 -0
- {ops-2.23.0.dev0 → ops-2.23.2}/test/charms/test_main/config.yaml +0 -0
- {ops-2.23.0.dev0 → ops-2.23.2}/test/charms/test_main/lib/__init__.py +0 -0
- {ops-2.23.0.dev0 → ops-2.23.2}/test/charms/test_main/lib/ops/__init__.py +0 -0
- {ops-2.23.0.dev0 → ops-2.23.2}/test/charms/test_main/lib/ops/_private/__init__.py +0 -0
- {ops-2.23.0.dev0 → ops-2.23.2}/test/charms/test_main/lib/ops/_private/timeconv.py +0 -0
- {ops-2.23.0.dev0 → ops-2.23.2}/test/charms/test_main/lib/ops/_private/yaml.py +0 -0
- {ops-2.23.0.dev0 → ops-2.23.2}/test/charms/test_main/lib/ops/framework.py +0 -0
- {ops-2.23.0.dev0 → ops-2.23.2}/test/charms/test_main/lib/ops/jujucontext.py +0 -0
- {ops-2.23.0.dev0 → ops-2.23.2}/test/charms/test_main/lib/ops/jujuversion.py +0 -0
- {ops-2.23.0.dev0 → ops-2.23.2}/test/charms/test_main/lib/ops/lib/__init__.py +0 -0
- {ops-2.23.0.dev0 → ops-2.23.2}/test/charms/test_main/lib/ops/log.py +0 -0
- {ops-2.23.0.dev0 → ops-2.23.2}/test/charms/test_main/lib/ops/main.py +0 -0
- {ops-2.23.0.dev0 → ops-2.23.2}/test/charms/test_main/lib/ops/pebble.py +0 -0
- {ops-2.23.0.dev0 → ops-2.23.2}/test/charms/test_main/lib/ops/py.typed +0 -0
- {ops-2.23.0.dev0 → ops-2.23.2}/test/charms/test_main/lib/ops/storage.py +0 -0
- {ops-2.23.0.dev0 → ops-2.23.2}/test/charms/test_main/lib/ops/testing.py +0 -0
- {ops-2.23.0.dev0 → ops-2.23.2}/test/charms/test_main/metadata.yaml +0 -0
- {ops-2.23.0.dev0 → ops-2.23.2}/test/charms/test_main/src/charm.py +0 -0
- {ops-2.23.0.dev0 → ops-2.23.2}/test/charms/test_relation/.gitignore +0 -0
- {ops-2.23.0.dev0 → ops-2.23.2}/test/charms/test_relation/charmcraft.yaml +0 -0
- {ops-2.23.0.dev0 → ops-2.23.2}/test/charms/test_relation/src/charm.py +0 -0
- {ops-2.23.0.dev0 → ops-2.23.2}/test/charms/test_smoke/README.md +0 -0
- {ops-2.23.0.dev0 → ops-2.23.2}/test/charms/test_smoke/metadata.yaml +0 -0
- {ops-2.23.0.dev0 → ops-2.23.2}/test/charms/test_smoke/src/charm.py +0 -0
- {ops-2.23.0.dev0 → ops-2.23.2}/test/charms/test_tracing/.gitignore +0 -0
- {ops-2.23.0.dev0 → ops-2.23.2}/test/charms/test_tracing/charmcraft.yaml +0 -0
- {ops-2.23.0.dev0 → ops-2.23.2}/test/charms/test_tracing/src/charm.py +0 -0
- {ops-2.23.0.dev0 → ops-2.23.2}/test/conftest.py +0 -0
- {ops-2.23.0.dev0 → ops-2.23.2}/test/fake_pebble.py +0 -0
- {ops-2.23.0.dev0 → ops-2.23.2}/test/integration/conftest.py +0 -0
- {ops-2.23.0.dev0 → ops-2.23.2}/test/integration/test_relation.py +0 -0
- {ops-2.23.0.dev0 → ops-2.23.2}/test/integration/test_tracing.py +0 -0
- {ops-2.23.0.dev0 → ops-2.23.2}/test/pebble_cli.py +0 -0
- {ops-2.23.0.dev0 → ops-2.23.2}/test/smoke/test_smoke.py +0 -0
- {ops-2.23.0.dev0 → ops-2.23.2}/test/test_charm.py +0 -0
- {ops-2.23.0.dev0 → ops-2.23.2}/test/test_framework.py +0 -0
- {ops-2.23.0.dev0 → ops-2.23.2}/test/test_helpers.py +0 -0
- {ops-2.23.0.dev0 → ops-2.23.2}/test/test_infra.py +0 -0
- {ops-2.23.0.dev0 → ops-2.23.2}/test/test_jujucontext.py +0 -0
- {ops-2.23.0.dev0 → ops-2.23.2}/test/test_jujuversion.py +0 -0
- {ops-2.23.0.dev0 → ops-2.23.2}/test/test_lib.py +0 -0
- {ops-2.23.0.dev0 → ops-2.23.2}/test/test_log.py +0 -0
- {ops-2.23.0.dev0 → ops-2.23.2}/test/test_main.py +0 -0
- {ops-2.23.0.dev0 → ops-2.23.2}/test/test_main_invocation.py +0 -0
- {ops-2.23.0.dev0 → ops-2.23.2}/test/test_main_type_hint.py +0 -0
- {ops-2.23.0.dev0 → ops-2.23.2}/test/test_model_relation_data_class.py +0 -0
- {ops-2.23.0.dev0 → ops-2.23.2}/test/test_pebble.py +0 -0
- {ops-2.23.0.dev0 → ops-2.23.2}/test/test_real_pebble.py +0 -0
- {ops-2.23.0.dev0 → ops-2.23.2}/test/test_storage.py +0 -0
- {ops-2.23.0.dev0 → ops-2.23.2}/test/test_testing.py +0 -0
- {ops-2.23.0.dev0 → ops-2.23.2}/test/test_timeconv.py +0 -0
- {ops-2.23.0.dev0 → ops-2.23.2}/test/test_yaml.py +0 -0
|
@@ -1,3 +1,86 @@
|
|
|
1
|
+
# 2.23.2 - 11 February 2026
|
|
2
|
+
|
|
3
|
+
## Fixes
|
|
4
|
+
|
|
5
|
+
* Drop unused `setuptools_scm` build dependency (#2318)
|
|
6
|
+
|
|
7
|
+
## Documentation
|
|
8
|
+
|
|
9
|
+
* For 2.23, update links and config for switch to documentation.ubuntu.com/ops (#1942)
|
|
10
|
+
* For 2.x, fix site title and unstyled error pages (#1945)
|
|
11
|
+
* For 2.x, remove .html extensions (#1954)
|
|
12
|
+
* For 2.x, fix unstyled error pages (#1973)
|
|
13
|
+
|
|
14
|
+
# 2.23.1 - 30 July 2025
|
|
15
|
+
|
|
16
|
+
## Fixes
|
|
17
|
+
|
|
18
|
+
* Add the remote unit to `Relation.data` but not `Relation.units` (#1928)
|
|
19
|
+
|
|
20
|
+
## Documentation
|
|
21
|
+
|
|
22
|
+
* Be consistent with recommending `self.app` and `self.unit` (#1856)
|
|
23
|
+
* Add notice about ops 2 and ops 3 (#1867)
|
|
24
|
+
* Update title and edit links for ops 2.23 docs (#1885)
|
|
25
|
+
|
|
26
|
+
## CI
|
|
27
|
+
|
|
28
|
+
* Hotfix, publish job for ops-tracing (#1865)
|
|
29
|
+
|
|
30
|
+
# 2.23.0 - 30 June 2025
|
|
31
|
+
|
|
32
|
+
## Features
|
|
33
|
+
|
|
34
|
+
* Support for config schema as Python classes (#1741)
|
|
35
|
+
* Support for action parameter schema as Python classes (#1756)
|
|
36
|
+
* Ops[tracing] compatibility with jhack (#1806)
|
|
37
|
+
* Support for relation data schema as Python classes (#1701)
|
|
38
|
+
* Add CheckInfo.successes field and .has_run property (#1819)
|
|
39
|
+
* Provide a method to create a testing.State from a testing.Context (#1797)
|
|
40
|
+
* Expose trace data in testing (#1842)
|
|
41
|
+
* Add a helper to generate a Layer from rockcraft.yaml (#1831)
|
|
42
|
+
|
|
43
|
+
## Fixes
|
|
44
|
+
|
|
45
|
+
* Correctly load an empty Juju config options map (#1778)
|
|
46
|
+
* Fix type annotation of container check_infos in ops.testing (#1784)
|
|
47
|
+
* Restrict the version of a dependency, opentelemetry-sdk (#1794)
|
|
48
|
+
* Remote unit data is available in relation-departed (#1364)
|
|
49
|
+
* Juju allows access to the remote app databag in relation-broken, so Harness should too (#1787)
|
|
50
|
+
* Don't use private OpenTelemetry API (#1798)
|
|
51
|
+
* Do not return this unit in a mocked peer relation (#1828)
|
|
52
|
+
* Testing.PeerRelation properly defaults to no peers (#1832)
|
|
53
|
+
* In meter-status-changed JUJU_VERSION is not set (#1840)
|
|
54
|
+
* Only provide the units belonging to the app in Relation.units (#1837)
|
|
55
|
+
|
|
56
|
+
## Documentation
|
|
57
|
+
|
|
58
|
+
* Remove two best practices, and drop two to tips (#1758)
|
|
59
|
+
* Update link to Charmcraft for managing app config (#1763)
|
|
60
|
+
* Update link to Juju documentation for setting up deployment (#1781)
|
|
61
|
+
* Fix external OTLP link (#1786)
|
|
62
|
+
* Distribute the ops-scenario README content across the ops docs (#1773)
|
|
63
|
+
* Improve testing.errors.UncaughtCharmError message (#1795)
|
|
64
|
+
* In the "manage the charm version" how-to, give an example of using override-build (#1802)
|
|
65
|
+
* Small adjustments to the 'how to trace charm code' doc (#1792)
|
|
66
|
+
* Replace Harness example and fix links in README (#1820)
|
|
67
|
+
* Add httpbin charm from Charmcraft as an example charm (#1743)
|
|
68
|
+
* Fix on_collect mistake in sample code (#1829)
|
|
69
|
+
* Update code in K8s tutorial, with source in repo (part 2) (#1734)
|
|
70
|
+
* Update Loki section on charming zero-to-hero tutorial (#1847)
|
|
71
|
+
* Remove expandable boxes of text (#1844)
|
|
72
|
+
* Improve httpbin charm by removing defer() and adding collect_status (#1833)
|
|
73
|
+
* Move {posargs} to the end of pytest command lines in tox.ini (#1854)
|
|
74
|
+
|
|
75
|
+
## CI
|
|
76
|
+
|
|
77
|
+
* Install the ops[tracing] dependencies for the TIOBE action (#1761)
|
|
78
|
+
* Add ops-scenario and ops-tracing as explicit installs for TIOBE (#1764)
|
|
79
|
+
* Persist credentials for update-charm-pins workflow (#1766)
|
|
80
|
+
* Stop smoke testing Charmcraft 2 (#1782)
|
|
81
|
+
* Use Charmcraft 3.x for smoke testing 20.04 and 22.04 (#1821)
|
|
82
|
+
* Enable xdist for the 'unit' tox environments (#1830)
|
|
83
|
+
|
|
1
84
|
# 2.22.0 - 29 May 2025
|
|
2
85
|
|
|
3
86
|
## Features
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ops
|
|
3
|
-
Version: 2.23.
|
|
3
|
+
Version: 2.23.2
|
|
4
4
|
Summary: The Python library behind great charms
|
|
5
5
|
Author: The Charm Tech team at Canonical Ltd.
|
|
6
|
-
Project-URL: Homepage, https://
|
|
6
|
+
Project-URL: Homepage, https://documentation.ubuntu.com/ops/2.x/
|
|
7
7
|
Project-URL: Repository, https://github.com/canonical/operator
|
|
8
8
|
Project-URL: Issues, https://github.com/canonical/operator/issues
|
|
9
|
-
Project-URL: Documentation, https://ops.
|
|
9
|
+
Project-URL: Documentation, https://documentation.ubuntu.com/ops/2.x/
|
|
10
10
|
Project-URL: Changelog, https://github.com/canonical/operator/blob/main/CHANGES.md
|
|
11
11
|
Classifier: Programming Language :: Python :: 3
|
|
12
12
|
Classifier: License :: OSI Approved :: Apache Software License
|
|
@@ -23,9 +23,9 @@ Requires-Dist: websocket-client==1.*
|
|
|
23
23
|
Requires-Dist: opentelemetry-api~=1.0
|
|
24
24
|
Requires-Dist: importlib-metadata
|
|
25
25
|
Provides-Extra: testing
|
|
26
|
-
Requires-Dist: ops-scenario==7.23.
|
|
26
|
+
Requires-Dist: ops-scenario==7.23.2; extra == "testing"
|
|
27
27
|
Provides-Extra: tracing
|
|
28
|
-
Requires-Dist: ops-tracing==2.23.
|
|
28
|
+
Requires-Dist: ops-tracing==2.23.2; extra == "tracing"
|
|
29
29
|
Provides-Extra: harness
|
|
30
30
|
Dynamic: license-file
|
|
31
31
|
|
|
@@ -36,8 +36,8 @@ Dynamic: license-file
|
|
|
36
36
|
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/).
|
|
37
37
|
|
|
38
38
|
> - `ops` is [available on PyPI](https://pypi.org/project/ops/).
|
|
39
|
-
> -
|
|
40
|
-
> - Read our [docs](https://
|
|
39
|
+
> - Version 2.x of `ops` requires Python 3.8 or above.
|
|
40
|
+
> - Read our [docs](https://documentation.ubuntu.com/ops/2.x/) for tutorials, how-to guides, the library reference, and more.
|
|
41
41
|
|
|
42
42
|
## Give it a try
|
|
43
43
|
|
|
@@ -181,6 +181,6 @@ Congratulations, you’ve just built your first Kubernetes charm using `ops`!
|
|
|
181
181
|
|
|
182
182
|
## Next steps
|
|
183
183
|
|
|
184
|
-
- Read the [docs](https://
|
|
184
|
+
- Read the [docs](https://documentation.ubuntu.com/ops/2.x/).
|
|
185
185
|
- Read our [Code of conduct](https://ubuntu.com/community/code-of-conduct) and join our [chat](https://matrix.to/#/#charmhub-ops:ubuntu.com) and [forum](https://discourse.charmhub.io/) or [open an issue](https://github.com/canonical/operator/issues).
|
|
186
186
|
- Read our [CONTRIBUTING guide](https://github.com/canonical/operator/blob/main/HACKING.md) and contribute!
|
|
@@ -5,8 +5,8 @@
|
|
|
5
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/).
|
|
6
6
|
|
|
7
7
|
> - `ops` is [available on PyPI](https://pypi.org/project/ops/).
|
|
8
|
-
> -
|
|
9
|
-
> - Read our [docs](https://
|
|
8
|
+
> - Version 2.x of `ops` requires Python 3.8 or above.
|
|
9
|
+
> - Read our [docs](https://documentation.ubuntu.com/ops/2.x/) for tutorials, how-to guides, the library reference, and more.
|
|
10
10
|
|
|
11
11
|
## Give it a try
|
|
12
12
|
|
|
@@ -150,6 +150,6 @@ Congratulations, you’ve just built your first Kubernetes charm using `ops`!
|
|
|
150
150
|
|
|
151
151
|
## Next steps
|
|
152
152
|
|
|
153
|
-
- Read the [docs](https://
|
|
153
|
+
- Read the [docs](https://documentation.ubuntu.com/ops/2.x/).
|
|
154
154
|
- Read our [Code of conduct](https://ubuntu.com/community/code-of-conduct) and join our [chat](https://matrix.to/#/#charmhub-ops:ubuntu.com) and [forum](https://discourse.charmhub.io/) or [open an issue](https://github.com/canonical/operator/issues).
|
|
155
155
|
- Read our [CONTRIBUTING guide](https://github.com/canonical/operator/blob/main/HACKING.md) and contribute!
|
|
@@ -368,24 +368,30 @@ class _Manager:
|
|
|
368
368
|
def _make_framework(self, dispatcher: _Dispatcher):
|
|
369
369
|
# If we are in a RelationBroken event, we want to know which relation is
|
|
370
370
|
# broken within the model, not only in the event's `.relation` attribute.
|
|
371
|
-
if
|
|
371
|
+
if dispatcher.event_name.endswith('_relation_broken'):
|
|
372
372
|
broken_relation_id = self._juju_context.relation_id
|
|
373
373
|
else:
|
|
374
374
|
broken_relation_id = None
|
|
375
375
|
|
|
376
|
+
# In a RelationDeparted event, the unit is not included in the Juju
|
|
377
|
+
# `relation-list` output, but the charm still has access to the remote
|
|
378
|
+
# relation data. To provide the charm with a mechanism for getting
|
|
379
|
+
# access to that data, we include the remote unit in Relation.units.
|
|
380
|
+
# We also expect it to be available in RelationBroken events, so ensure
|
|
381
|
+
# that it's available then as well. For other relation events, the unit
|
|
382
|
+
# will either already be in the set via `relation-list` (such as in a
|
|
383
|
+
# RelationChanged event) or correctly not in the list yet because the
|
|
384
|
+
# relation has not been fully set up (such as in a RelationJoined event).
|
|
385
|
+
if dispatcher.event_name.endswith(('_relation_departed', '_relation_broken')):
|
|
386
|
+
remote_unit_name = self._juju_context.remote_unit_name
|
|
387
|
+
else:
|
|
388
|
+
remote_unit_name = None
|
|
389
|
+
|
|
376
390
|
model = _model.Model(
|
|
377
391
|
self._charm_meta,
|
|
378
392
|
self._model_backend,
|
|
379
393
|
broken_relation_id=broken_relation_id,
|
|
380
|
-
|
|
381
|
-
# `relation-list` output, but the charm still has access to the remote
|
|
382
|
-
# relation data. To provide the charm with a mechanism for getting
|
|
383
|
-
# access to that data, we include the remote unit in Relation.units.
|
|
384
|
-
# In other relation events (such as RelationChanged) the unit will
|
|
385
|
-
# already be in the set via `relation-list` - adding it via this extra
|
|
386
|
-
# mechanism will not change the final set, and is simpler than only
|
|
387
|
-
# adding it in specific events.
|
|
388
|
-
remote_unit_name=self._juju_context.remote_unit_name,
|
|
394
|
+
remote_unit_name=remote_unit_name,
|
|
389
395
|
)
|
|
390
396
|
store = self._make_storage(dispatcher)
|
|
391
397
|
framework = _framework.Framework(
|
|
@@ -323,7 +323,7 @@ class Harness(Generic[CharmType]):
|
|
|
323
323
|
|
|
324
324
|
warnings.warn(
|
|
325
325
|
'Harness is deprecated. For the recommended approach, see: '
|
|
326
|
-
'https://
|
|
326
|
+
'https://documentation.ubuntu.com/ops/2.x/howto/write-unit-tests-for-a-charm.html',
|
|
327
327
|
PendingDeprecationWarning,
|
|
328
328
|
stacklevel=2,
|
|
329
329
|
)
|
|
@@ -1467,12 +1467,12 @@ class CharmBase(Object):
|
|
|
1467
1467
|
|
|
1468
1468
|
@property
|
|
1469
1469
|
def app(self) -> model.Application:
|
|
1470
|
-
"""
|
|
1470
|
+
"""The application that this unit is part of."""
|
|
1471
1471
|
return self.framework.model.app
|
|
1472
1472
|
|
|
1473
1473
|
@property
|
|
1474
1474
|
def unit(self) -> model.Unit:
|
|
1475
|
-
"""
|
|
1475
|
+
"""The current unit."""
|
|
1476
1476
|
return self.framework.model.unit
|
|
1477
1477
|
|
|
1478
1478
|
@property
|
|
@@ -159,17 +159,17 @@ class Model:
|
|
|
159
159
|
|
|
160
160
|
@property
|
|
161
161
|
def unit(self) -> Unit:
|
|
162
|
-
"""The unit
|
|
162
|
+
"""The current unit. Equivalent to :attr:`CharmBase.unit`.
|
|
163
163
|
|
|
164
|
-
|
|
164
|
+
To get a unit by name, use :meth:`get_unit`.
|
|
165
165
|
"""
|
|
166
166
|
return self._unit
|
|
167
167
|
|
|
168
168
|
@property
|
|
169
169
|
def app(self) -> Application:
|
|
170
|
-
"""The application this unit is
|
|
170
|
+
"""The application that this unit is part of. Equivalent to :attr:`CharmBase.app`.
|
|
171
171
|
|
|
172
|
-
|
|
172
|
+
To get an application by name, use :meth:`get_app`.
|
|
173
173
|
"""
|
|
174
174
|
return self._unit.app
|
|
175
175
|
|
|
@@ -238,22 +238,22 @@ class Model:
|
|
|
238
238
|
return self._backend._juju_context.version
|
|
239
239
|
|
|
240
240
|
def get_unit(self, unit_name: str) -> Unit:
|
|
241
|
-
"""Get
|
|
242
|
-
|
|
243
|
-
Use :attr:`unit` to get the current unit.
|
|
241
|
+
"""Get a unit by name.
|
|
244
242
|
|
|
245
243
|
Internally this uses a cache, so asking for the same unit two times will
|
|
246
244
|
return the same object.
|
|
245
|
+
|
|
246
|
+
To get the current unit, use :attr:`CharmBase.unit` or :attr:`unit`.
|
|
247
247
|
"""
|
|
248
248
|
return self._cache.get(Unit, unit_name)
|
|
249
249
|
|
|
250
250
|
def get_app(self, app_name: str) -> Application:
|
|
251
251
|
"""Get an application by name.
|
|
252
252
|
|
|
253
|
-
Use :attr:`app` to get this charm's application.
|
|
254
|
-
|
|
255
253
|
Internally this uses a cache, so asking for the same application two times will
|
|
256
254
|
return the same object.
|
|
255
|
+
|
|
256
|
+
To get the application that this unit is part of, use :attr:`CharmBase.app` or :attr:`app`.
|
|
257
257
|
"""
|
|
258
258
|
return self._cache.get(Application, app_name)
|
|
259
259
|
|
|
@@ -380,9 +380,10 @@ class Application:
|
|
|
380
380
|
"""Represents a named application in the model.
|
|
381
381
|
|
|
382
382
|
This might be this charm's application, or might be an application this charm is integrated
|
|
383
|
-
with.
|
|
384
|
-
|
|
385
|
-
|
|
383
|
+
with.
|
|
384
|
+
|
|
385
|
+
Don't instantiate Application objects directly. To get the application that this unit is
|
|
386
|
+
part of, use :attr:`CharmBase.app`. To get an application by name, use :meth:`Model.get_app`.
|
|
386
387
|
"""
|
|
387
388
|
|
|
388
389
|
name: str
|
|
@@ -426,7 +427,7 @@ class Application:
|
|
|
426
427
|
|
|
427
428
|
Example::
|
|
428
429
|
|
|
429
|
-
self.
|
|
430
|
+
self.app.status = ops.BlockedStatus('I need a human to come help me')
|
|
430
431
|
"""
|
|
431
432
|
if not self._is_our_app:
|
|
432
433
|
return UnknownStatus()
|
|
@@ -556,6 +557,9 @@ class Unit:
|
|
|
556
557
|
|
|
557
558
|
This might be the current unit, another unit of the charm's application, or a unit of
|
|
558
559
|
another application that the charm is integrated with.
|
|
560
|
+
|
|
561
|
+
Don't instantiate Unit objects directly. To get the current unit, use :attr:`CharmBase.unit`.
|
|
562
|
+
To get a unit by name, use :meth:`Model.get_unit`.
|
|
559
563
|
"""
|
|
560
564
|
|
|
561
565
|
name: str
|
|
@@ -609,7 +613,7 @@ class Unit:
|
|
|
609
613
|
|
|
610
614
|
Example::
|
|
611
615
|
|
|
612
|
-
self.
|
|
616
|
+
self.unit.status = ops.MaintenanceStatus('reconfiguring the frobnicators')
|
|
613
617
|
"""
|
|
614
618
|
if not self._is_our_unit:
|
|
615
619
|
return UnknownStatus()
|
|
@@ -1770,16 +1774,23 @@ class Relation:
|
|
|
1770
1774
|
|
|
1771
1775
|
# self.app will not be None and always be set because of the fallback mechanism above.
|
|
1772
1776
|
self.app = typing.cast('Application', app)
|
|
1773
|
-
self.data = RelationData(self, our_unit, backend)
|
|
1774
1777
|
|
|
1775
1778
|
# In relation-departed `relation-list` doesn't include the remote unit,
|
|
1776
1779
|
# but the data should still be available.
|
|
1777
1780
|
if (
|
|
1778
1781
|
_remote_unit is not None
|
|
1779
1782
|
and not is_peer
|
|
1783
|
+
# In practice, the "self.app will not be None" statement above is not
|
|
1784
|
+
# necessarily true. Once https://bugs.launchpad.net/juju/+bug/1960934
|
|
1785
|
+
# is resolved, we should be able to remove the next line.
|
|
1786
|
+
and self.app is not None
|
|
1780
1787
|
and _remote_unit.name.startswith(f'{self.app.name}/')
|
|
1781
1788
|
):
|
|
1782
|
-
|
|
1789
|
+
remote_unit = _remote_unit
|
|
1790
|
+
else:
|
|
1791
|
+
remote_unit = None
|
|
1792
|
+
|
|
1793
|
+
self.data = RelationData(self, our_unit, backend, remote_unit)
|
|
1783
1794
|
|
|
1784
1795
|
self._remote_model: RemoteModel | None = None
|
|
1785
1796
|
|
|
@@ -1975,7 +1986,13 @@ class RelationData(Mapping[Union[Unit, Application], 'RelationDataContent']):
|
|
|
1975
1986
|
:attr:`Relation.data`
|
|
1976
1987
|
"""
|
|
1977
1988
|
|
|
1978
|
-
def __init__(
|
|
1989
|
+
def __init__(
|
|
1990
|
+
self,
|
|
1991
|
+
relation: Relation,
|
|
1992
|
+
our_unit: Unit,
|
|
1993
|
+
backend: _ModelBackend,
|
|
1994
|
+
remote_unit: Unit | None = None,
|
|
1995
|
+
):
|
|
1979
1996
|
self.relation = weakref.proxy(relation)
|
|
1980
1997
|
self._data: dict[Unit | Application, RelationDataContent] = {
|
|
1981
1998
|
our_unit: RelationDataContent(self.relation, our_unit, backend),
|
|
@@ -1989,6 +2006,9 @@ class RelationData(Mapping[Union[Unit, Application], 'RelationDataContent']):
|
|
|
1989
2006
|
self._data.update({
|
|
1990
2007
|
self.relation.app: RelationDataContent(self.relation, self.relation.app, backend),
|
|
1991
2008
|
})
|
|
2009
|
+
# The unit might be departing or broken, so not in relation-list, but accessible.
|
|
2010
|
+
if remote_unit is not None and remote_unit not in self._data:
|
|
2011
|
+
self._data[remote_unit] = RelationDataContent(self.relation, remote_unit, backend)
|
|
1992
2012
|
|
|
1993
2013
|
def __contains__(self, key: Unit | Application):
|
|
1994
2014
|
return key in self._data
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ops
|
|
3
|
-
Version: 2.23.
|
|
3
|
+
Version: 2.23.2
|
|
4
4
|
Summary: The Python library behind great charms
|
|
5
5
|
Author: The Charm Tech team at Canonical Ltd.
|
|
6
|
-
Project-URL: Homepage, https://
|
|
6
|
+
Project-URL: Homepage, https://documentation.ubuntu.com/ops/2.x/
|
|
7
7
|
Project-URL: Repository, https://github.com/canonical/operator
|
|
8
8
|
Project-URL: Issues, https://github.com/canonical/operator/issues
|
|
9
|
-
Project-URL: Documentation, https://ops.
|
|
9
|
+
Project-URL: Documentation, https://documentation.ubuntu.com/ops/2.x/
|
|
10
10
|
Project-URL: Changelog, https://github.com/canonical/operator/blob/main/CHANGES.md
|
|
11
11
|
Classifier: Programming Language :: Python :: 3
|
|
12
12
|
Classifier: License :: OSI Approved :: Apache Software License
|
|
@@ -23,9 +23,9 @@ Requires-Dist: websocket-client==1.*
|
|
|
23
23
|
Requires-Dist: opentelemetry-api~=1.0
|
|
24
24
|
Requires-Dist: importlib-metadata
|
|
25
25
|
Provides-Extra: testing
|
|
26
|
-
Requires-Dist: ops-scenario==7.23.
|
|
26
|
+
Requires-Dist: ops-scenario==7.23.2; extra == "testing"
|
|
27
27
|
Provides-Extra: tracing
|
|
28
|
-
Requires-Dist: ops-tracing==2.23.
|
|
28
|
+
Requires-Dist: ops-tracing==2.23.2; extra == "tracing"
|
|
29
29
|
Provides-Extra: harness
|
|
30
30
|
Dynamic: license-file
|
|
31
31
|
|
|
@@ -36,8 +36,8 @@ Dynamic: license-file
|
|
|
36
36
|
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/).
|
|
37
37
|
|
|
38
38
|
> - `ops` is [available on PyPI](https://pypi.org/project/ops/).
|
|
39
|
-
> -
|
|
40
|
-
> - Read our [docs](https://
|
|
39
|
+
> - Version 2.x of `ops` requires Python 3.8 or above.
|
|
40
|
+
> - Read our [docs](https://documentation.ubuntu.com/ops/2.x/) for tutorials, how-to guides, the library reference, and more.
|
|
41
41
|
|
|
42
42
|
## Give it a try
|
|
43
43
|
|
|
@@ -181,6 +181,6 @@ Congratulations, you’ve just built your first Kubernetes charm using `ops`!
|
|
|
181
181
|
|
|
182
182
|
## Next steps
|
|
183
183
|
|
|
184
|
-
- Read the [docs](https://
|
|
184
|
+
- Read the [docs](https://documentation.ubuntu.com/ops/2.x/).
|
|
185
185
|
- Read our [Code of conduct](https://ubuntu.com/community/code-of-conduct) and join our [chat](https://matrix.to/#/#charmhub-ops:ubuntu.com) and [forum](https://discourse.charmhub.io/) or [open an issue](https://github.com/canonical/operator/issues).
|
|
186
186
|
- Read our [CONTRIBUTING guide](https://github.com/canonical/operator/blob/main/HACKING.md) and contribute!
|
|
@@ -26,10 +26,10 @@ dynamic = ["version"]
|
|
|
26
26
|
|
|
27
27
|
[project.optional-dependencies]
|
|
28
28
|
testing = [
|
|
29
|
-
"ops-scenario==7.23.
|
|
29
|
+
"ops-scenario==7.23.2",
|
|
30
30
|
]
|
|
31
31
|
tracing = [
|
|
32
|
-
"ops-tracing==2.23.
|
|
32
|
+
"ops-tracing==2.23.2",
|
|
33
33
|
]
|
|
34
34
|
# Empty for now, because Harness is bundled with the base install, but allow
|
|
35
35
|
# specifying the extra to ease transition later.
|
|
@@ -89,10 +89,10 @@ integration = [
|
|
|
89
89
|
]
|
|
90
90
|
|
|
91
91
|
[project.urls]
|
|
92
|
-
"Homepage" = "https://
|
|
92
|
+
"Homepage" = "https://documentation.ubuntu.com/ops/2.x/"
|
|
93
93
|
"Repository" = "https://github.com/canonical/operator"
|
|
94
94
|
"Issues" = "https://github.com/canonical/operator/issues"
|
|
95
|
-
"Documentation" = "https://ops.
|
|
95
|
+
"Documentation" = "https://documentation.ubuntu.com/ops/2.x/"
|
|
96
96
|
"Changelog" = "https://github.com/canonical/operator/blob/main/CHANGES.md"
|
|
97
97
|
|
|
98
98
|
[build-system]
|
|
@@ -368,24 +368,30 @@ class _Manager:
|
|
|
368
368
|
def _make_framework(self, dispatcher: _Dispatcher):
|
|
369
369
|
# If we are in a RelationBroken event, we want to know which relation is
|
|
370
370
|
# broken within the model, not only in the event's `.relation` attribute.
|
|
371
|
-
if
|
|
371
|
+
if dispatcher.event_name.endswith('_relation_broken'):
|
|
372
372
|
broken_relation_id = self._juju_context.relation_id
|
|
373
373
|
else:
|
|
374
374
|
broken_relation_id = None
|
|
375
375
|
|
|
376
|
+
# In a RelationDeparted event, the unit is not included in the Juju
|
|
377
|
+
# `relation-list` output, but the charm still has access to the remote
|
|
378
|
+
# relation data. To provide the charm with a mechanism for getting
|
|
379
|
+
# access to that data, we include the remote unit in Relation.units.
|
|
380
|
+
# We also expect it to be available in RelationBroken events, so ensure
|
|
381
|
+
# that it's available then as well. For other relation events, the unit
|
|
382
|
+
# will either already be in the set via `relation-list` (such as in a
|
|
383
|
+
# RelationChanged event) or correctly not in the list yet because the
|
|
384
|
+
# relation has not been fully set up (such as in a RelationJoined event).
|
|
385
|
+
if dispatcher.event_name.endswith(('_relation_departed', '_relation_broken')):
|
|
386
|
+
remote_unit_name = self._juju_context.remote_unit_name
|
|
387
|
+
else:
|
|
388
|
+
remote_unit_name = None
|
|
389
|
+
|
|
376
390
|
model = _model.Model(
|
|
377
391
|
self._charm_meta,
|
|
378
392
|
self._model_backend,
|
|
379
393
|
broken_relation_id=broken_relation_id,
|
|
380
|
-
|
|
381
|
-
# `relation-list` output, but the charm still has access to the remote
|
|
382
|
-
# relation data. To provide the charm with a mechanism for getting
|
|
383
|
-
# access to that data, we include the remote unit in Relation.units.
|
|
384
|
-
# In other relation events (such as RelationChanged) the unit will
|
|
385
|
-
# already be in the set via `relation-list` - adding it via this extra
|
|
386
|
-
# mechanism will not change the final set, and is simpler than only
|
|
387
|
-
# adding it in specific events.
|
|
388
|
-
remote_unit_name=self._juju_context.remote_unit_name,
|
|
394
|
+
remote_unit_name=remote_unit_name,
|
|
389
395
|
)
|
|
390
396
|
store = self._make_storage(dispatcher)
|
|
391
397
|
framework = _framework.Framework(
|
|
@@ -323,7 +323,7 @@ class Harness(Generic[CharmType]):
|
|
|
323
323
|
|
|
324
324
|
warnings.warn(
|
|
325
325
|
'Harness is deprecated. For the recommended approach, see: '
|
|
326
|
-
'https://
|
|
326
|
+
'https://documentation.ubuntu.com/ops/2.x/howto/write-unit-tests-for-a-charm.html',
|
|
327
327
|
PendingDeprecationWarning,
|
|
328
328
|
stacklevel=2,
|
|
329
329
|
)
|
|
@@ -1467,12 +1467,12 @@ class CharmBase(Object):
|
|
|
1467
1467
|
|
|
1468
1468
|
@property
|
|
1469
1469
|
def app(self) -> model.Application:
|
|
1470
|
-
"""
|
|
1470
|
+
"""The application that this unit is part of."""
|
|
1471
1471
|
return self.framework.model.app
|
|
1472
1472
|
|
|
1473
1473
|
@property
|
|
1474
1474
|
def unit(self) -> model.Unit:
|
|
1475
|
-
"""
|
|
1475
|
+
"""The current unit."""
|
|
1476
1476
|
return self.framework.model.unit
|
|
1477
1477
|
|
|
1478
1478
|
@property
|
|
@@ -159,17 +159,17 @@ class Model:
|
|
|
159
159
|
|
|
160
160
|
@property
|
|
161
161
|
def unit(self) -> Unit:
|
|
162
|
-
"""The unit
|
|
162
|
+
"""The current unit. Equivalent to :attr:`CharmBase.unit`.
|
|
163
163
|
|
|
164
|
-
|
|
164
|
+
To get a unit by name, use :meth:`get_unit`.
|
|
165
165
|
"""
|
|
166
166
|
return self._unit
|
|
167
167
|
|
|
168
168
|
@property
|
|
169
169
|
def app(self) -> Application:
|
|
170
|
-
"""The application this unit is
|
|
170
|
+
"""The application that this unit is part of. Equivalent to :attr:`CharmBase.app`.
|
|
171
171
|
|
|
172
|
-
|
|
172
|
+
To get an application by name, use :meth:`get_app`.
|
|
173
173
|
"""
|
|
174
174
|
return self._unit.app
|
|
175
175
|
|
|
@@ -238,22 +238,22 @@ class Model:
|
|
|
238
238
|
return self._backend._juju_context.version
|
|
239
239
|
|
|
240
240
|
def get_unit(self, unit_name: str) -> Unit:
|
|
241
|
-
"""Get
|
|
242
|
-
|
|
243
|
-
Use :attr:`unit` to get the current unit.
|
|
241
|
+
"""Get a unit by name.
|
|
244
242
|
|
|
245
243
|
Internally this uses a cache, so asking for the same unit two times will
|
|
246
244
|
return the same object.
|
|
245
|
+
|
|
246
|
+
To get the current unit, use :attr:`CharmBase.unit` or :attr:`unit`.
|
|
247
247
|
"""
|
|
248
248
|
return self._cache.get(Unit, unit_name)
|
|
249
249
|
|
|
250
250
|
def get_app(self, app_name: str) -> Application:
|
|
251
251
|
"""Get an application by name.
|
|
252
252
|
|
|
253
|
-
Use :attr:`app` to get this charm's application.
|
|
254
|
-
|
|
255
253
|
Internally this uses a cache, so asking for the same application two times will
|
|
256
254
|
return the same object.
|
|
255
|
+
|
|
256
|
+
To get the application that this unit is part of, use :attr:`CharmBase.app` or :attr:`app`.
|
|
257
257
|
"""
|
|
258
258
|
return self._cache.get(Application, app_name)
|
|
259
259
|
|
|
@@ -380,9 +380,10 @@ class Application:
|
|
|
380
380
|
"""Represents a named application in the model.
|
|
381
381
|
|
|
382
382
|
This might be this charm's application, or might be an application this charm is integrated
|
|
383
|
-
with.
|
|
384
|
-
|
|
385
|
-
|
|
383
|
+
with.
|
|
384
|
+
|
|
385
|
+
Don't instantiate Application objects directly. To get the application that this unit is
|
|
386
|
+
part of, use :attr:`CharmBase.app`. To get an application by name, use :meth:`Model.get_app`.
|
|
386
387
|
"""
|
|
387
388
|
|
|
388
389
|
name: str
|
|
@@ -426,7 +427,7 @@ class Application:
|
|
|
426
427
|
|
|
427
428
|
Example::
|
|
428
429
|
|
|
429
|
-
self.
|
|
430
|
+
self.app.status = ops.BlockedStatus('I need a human to come help me')
|
|
430
431
|
"""
|
|
431
432
|
if not self._is_our_app:
|
|
432
433
|
return UnknownStatus()
|
|
@@ -556,6 +557,9 @@ class Unit:
|
|
|
556
557
|
|
|
557
558
|
This might be the current unit, another unit of the charm's application, or a unit of
|
|
558
559
|
another application that the charm is integrated with.
|
|
560
|
+
|
|
561
|
+
Don't instantiate Unit objects directly. To get the current unit, use :attr:`CharmBase.unit`.
|
|
562
|
+
To get a unit by name, use :meth:`Model.get_unit`.
|
|
559
563
|
"""
|
|
560
564
|
|
|
561
565
|
name: str
|
|
@@ -609,7 +613,7 @@ class Unit:
|
|
|
609
613
|
|
|
610
614
|
Example::
|
|
611
615
|
|
|
612
|
-
self.
|
|
616
|
+
self.unit.status = ops.MaintenanceStatus('reconfiguring the frobnicators')
|
|
613
617
|
"""
|
|
614
618
|
if not self._is_our_unit:
|
|
615
619
|
return UnknownStatus()
|
|
@@ -1770,16 +1774,23 @@ class Relation:
|
|
|
1770
1774
|
|
|
1771
1775
|
# self.app will not be None and always be set because of the fallback mechanism above.
|
|
1772
1776
|
self.app = typing.cast('Application', app)
|
|
1773
|
-
self.data = RelationData(self, our_unit, backend)
|
|
1774
1777
|
|
|
1775
1778
|
# In relation-departed `relation-list` doesn't include the remote unit,
|
|
1776
1779
|
# but the data should still be available.
|
|
1777
1780
|
if (
|
|
1778
1781
|
_remote_unit is not None
|
|
1779
1782
|
and not is_peer
|
|
1783
|
+
# In practice, the "self.app will not be None" statement above is not
|
|
1784
|
+
# necessarily true. Once https://bugs.launchpad.net/juju/+bug/1960934
|
|
1785
|
+
# is resolved, we should be able to remove the next line.
|
|
1786
|
+
and self.app is not None
|
|
1780
1787
|
and _remote_unit.name.startswith(f'{self.app.name}/')
|
|
1781
1788
|
):
|
|
1782
|
-
|
|
1789
|
+
remote_unit = _remote_unit
|
|
1790
|
+
else:
|
|
1791
|
+
remote_unit = None
|
|
1792
|
+
|
|
1793
|
+
self.data = RelationData(self, our_unit, backend, remote_unit)
|
|
1783
1794
|
|
|
1784
1795
|
self._remote_model: RemoteModel | None = None
|
|
1785
1796
|
|
|
@@ -1975,7 +1986,13 @@ class RelationData(Mapping[Union[Unit, Application], 'RelationDataContent']):
|
|
|
1975
1986
|
:attr:`Relation.data`
|
|
1976
1987
|
"""
|
|
1977
1988
|
|
|
1978
|
-
def __init__(
|
|
1989
|
+
def __init__(
|
|
1990
|
+
self,
|
|
1991
|
+
relation: Relation,
|
|
1992
|
+
our_unit: Unit,
|
|
1993
|
+
backend: _ModelBackend,
|
|
1994
|
+
remote_unit: Unit | None = None,
|
|
1995
|
+
):
|
|
1979
1996
|
self.relation = weakref.proxy(relation)
|
|
1980
1997
|
self._data: dict[Unit | Application, RelationDataContent] = {
|
|
1981
1998
|
our_unit: RelationDataContent(self.relation, our_unit, backend),
|
|
@@ -1989,6 +2006,9 @@ class RelationData(Mapping[Union[Unit, Application], 'RelationDataContent']):
|
|
|
1989
2006
|
self._data.update({
|
|
1990
2007
|
self.relation.app: RelationDataContent(self.relation, self.relation.app, backend),
|
|
1991
2008
|
})
|
|
2009
|
+
# The unit might be departing or broken, so not in relation-list, but accessible.
|
|
2010
|
+
if remote_unit is not None and remote_unit not in self._data:
|
|
2011
|
+
self._data[remote_unit] = RelationDataContent(self.relation, remote_unit, backend)
|
|
1992
2012
|
|
|
1993
2013
|
def __contains__(self, key: Unit | Application):
|
|
1994
2014
|
return key in self._data
|
|
@@ -4421,84 +4421,27 @@ class TestGetCloudSpec:
|
|
|
4421
4421
|
assert str(excinfo.value) == 'ERROR cannot access cloud credentials\n'
|
|
4422
4422
|
|
|
4423
4423
|
|
|
4424
|
-
|
|
4425
|
-
|
|
4426
|
-
)
|
|
4427
|
-
|
|
4428
|
-
|
|
4429
|
-
|
|
4430
|
-
)
|
|
4431
|
-
|
|
4432
|
-
|
|
4433
|
-
|
|
4434
|
-
|
|
4435
|
-
|
|
4436
|
-
|
|
4437
|
-
|
|
4438
|
-
|
|
4439
|
-
|
|
4440
|
-
|
|
4441
|
-
|
|
4442
|
-
|
|
4443
|
-
|
|
4444
|
-
|
|
4445
|
-
def test_relation_has_correct_units():
|
|
4446
|
-
class Charm(ops.CharmBase):
|
|
4447
|
-
def __init__(self, framework: ops.Framework):
|
|
4448
|
-
super().__init__(framework)
|
|
4449
|
-
framework.observe(self.on['db'].relation_changed, self._on_peer_relation_changed)
|
|
4450
|
-
framework.observe(self.on['peer'].relation_changed, self._on_peer_relation_changed)
|
|
4451
|
-
|
|
4452
|
-
def _on_peer_relation_changed(self, event: ops.RelationChangedEvent):
|
|
4453
|
-
self.event = event
|
|
4454
|
-
|
|
4455
|
-
ctx = ops.testing.Context(
|
|
4456
|
-
Charm,
|
|
4457
|
-
meta={
|
|
4458
|
-
'name': 'mycharm',
|
|
4459
|
-
'requires': {'db': {'interface': 'db'}, 'ingress': {'interface': 'ingress'}},
|
|
4460
|
-
'peers': {'peer': {'interface': 'gossip'}},
|
|
4461
|
-
},
|
|
4462
|
-
)
|
|
4463
|
-
rel1 = ops.testing.Relation(
|
|
4464
|
-
'db', remote_units_data={1: {}, 2: {}, 3: {}}, remote_app_name='test-db'
|
|
4465
|
-
)
|
|
4466
|
-
rel2 = ops.testing.Relation(
|
|
4467
|
-
'ingress', remote_units_data={4: {}, 6: {}}, remote_app_name='test-ingress'
|
|
4468
|
-
)
|
|
4469
|
-
peer = ops.testing.PeerRelation('peer', peers_data={1: {}, 2: {}})
|
|
4470
|
-
state_in = ops.testing.State(relations={rel1, rel2, peer})
|
|
4471
|
-
|
|
4472
|
-
def unit_names(relation: ops.Relation):
|
|
4473
|
-
return {unit.name for unit in relation.units}
|
|
4474
|
-
|
|
4475
|
-
with ctx(ctx.on.relation_changed(peer, remote_unit=1), state_in) as mgr:
|
|
4476
|
-
mgr.run()
|
|
4477
|
-
assert unit_names(mgr.charm.event.relation) == {'mycharm/1', 'mycharm/2'}
|
|
4478
|
-
assert unit_names(mgr.charm.model.relations['peer'][0]) == {'mycharm/1', 'mycharm/2'}
|
|
4479
|
-
assert unit_names(mgr.charm.model.relations['db'][0]) == {
|
|
4480
|
-
'test-db/1',
|
|
4481
|
-
'test-db/2',
|
|
4482
|
-
'test-db/3',
|
|
4483
|
-
}
|
|
4484
|
-
assert unit_names(mgr.charm.model.relations['ingress'][0]) == {
|
|
4485
|
-
'test-ingress/4',
|
|
4486
|
-
'test-ingress/6',
|
|
4487
|
-
}
|
|
4488
|
-
|
|
4489
|
-
with ctx(ctx.on.relation_changed(rel1, remote_unit=1), state_in) as mgr:
|
|
4490
|
-
mgr.run()
|
|
4491
|
-
assert unit_names(mgr.charm.event.relation) == {'test-db/1', 'test-db/2', 'test-db/3'}
|
|
4492
|
-
assert unit_names(mgr.charm.model.relations['peer'][0]) == {'mycharm/1', 'mycharm/2'}
|
|
4493
|
-
assert unit_names(mgr.charm.model.relations['db'][0]) == {
|
|
4494
|
-
'test-db/1',
|
|
4495
|
-
'test-db/2',
|
|
4496
|
-
'test-db/3',
|
|
4497
|
-
}
|
|
4498
|
-
assert unit_names(mgr.charm.model.relations['ingress'][0]) == {
|
|
4499
|
-
'test-ingress/4',
|
|
4500
|
-
'test-ingress/6',
|
|
4501
|
-
}
|
|
4424
|
+
def test_departing_unit_data_available(fake_script: FakeScript):
|
|
4425
|
+
fake_script.write('relation-ids', """echo '["db0:1"]'""")
|
|
4426
|
+
fake_script.write('relation-list', """echo '["db/0"]'""")
|
|
4427
|
+
fake_script.write('relation-get', """echo '{"db": "data"}'""")
|
|
4428
|
+
|
|
4429
|
+
meta = ops.charm.CharmMeta({'name': 'mycharm', 'requires': {'db': {'interface': 'db'}}})
|
|
4430
|
+
backend = ops.model._ModelBackend('myapp/0')
|
|
4431
|
+
model = ops.model.Model(meta, backend, remote_unit_name='db/1')
|
|
4432
|
+
relation = model.get_relation('db')
|
|
4433
|
+
assert relation is not None
|
|
4434
|
+
for unit in relation.units:
|
|
4435
|
+
assert relation.data[unit] == {'db': 'data'}
|
|
4436
|
+
unit = model.get_unit('db/1')
|
|
4437
|
+
assert relation.data[unit] == {'db': 'data'}
|
|
4438
|
+
calls = fake_script.calls(clear=True)
|
|
4439
|
+
assert calls[:2] == [
|
|
4440
|
+
['relation-ids', 'db', '--format=json'],
|
|
4441
|
+
['relation-list', '-r', '1', '--format=json'],
|
|
4442
|
+
]
|
|
4443
|
+
assert ['relation-get', '-r', '1', '-', 'db/0', '--format=json'] in calls
|
|
4444
|
+
assert ['relation-get', '-r', '1', '-', 'db/1', '--format=json'] in calls
|
|
4502
4445
|
|
|
4503
4446
|
|
|
4504
4447
|
if __name__ == '__main__':
|
|
@@ -47,14 +47,17 @@ passenv =
|
|
|
47
47
|
# ReadTheDocs builder wants the output in a place of its choosing.
|
|
48
48
|
# https://docs.readthedocs.com/platform/stable/build-customization.html#where-to-put-files
|
|
49
49
|
READTHEDOCS_OUTPUT
|
|
50
|
+
# docs/conf.py uses these variables to build URLs of static assets on error pages.
|
|
51
|
+
READTHEDOCS_CANONICAL_URL
|
|
52
|
+
READTHEDOCS_VERSION
|
|
50
53
|
commands =
|
|
51
|
-
sphinx-build -W --keep-going docs/ {env:READTHEDOCS_OUTPUT:docs/_build}/html
|
|
54
|
+
sphinx-build -W --keep-going -b dirhtml docs/ {env:READTHEDOCS_OUTPUT:docs/_build}/html
|
|
52
55
|
|
|
53
56
|
[testenv:docs-live]
|
|
54
57
|
description = Live development: build the Sphinx docs with autoreloading enabled
|
|
55
58
|
dependency_groups = docs
|
|
56
59
|
commands =
|
|
57
|
-
sphinx-autobuild docs/ docs/_build/html --watch ops/ --port 8000 {posargs}
|
|
60
|
+
sphinx-autobuild -b dirhtml docs/ docs/_build/html --watch ops/ --port 8000 {posargs}
|
|
58
61
|
|
|
59
62
|
[testenv:format]
|
|
60
63
|
description = Apply coding style standards to code
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|