generated from PietroPasotti/charm-lib-template
-
Notifications
You must be signed in to change notification settings - Fork 1
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
PoC library simplification #6
Closed
samuelallan72
wants to merge
6
commits into
PietroPasotti:master
from
samuelallan72:example-simplification
Closed
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,4 +7,5 @@ __pycache__/ | |
*.py[cod] | ||
.idea | ||
.mypy_cache | ||
lib/ | ||
lib/ | ||
*.egg-info |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,34 +5,39 @@ The first step to get there is to allow charmers to define the status as a pool | |
|
||
# What this is | ||
|
||
This charm lib exposes utilities to create 'status pools'. | ||
This charm lib exposes utilities to create 'status pools'. | ||
```python | ||
from compound_status import * | ||
|
||
class MyPool(StatusPool): | ||
"""This defines my charm's status pool.""" | ||
workload = Status() # this tracks the workload status | ||
relation_1 = Status() # this tracks my integration #1 | ||
relation_2 = Status() # this tracks my integration #2 | ||
from compound_status import StatusPool, Status | ||
|
||
|
||
class MyCharm(CharmBase): | ||
def __init__(self, framework, key=None): | ||
super().__init__(framework, key) | ||
status_pool = MyPool(self) | ||
status_pool = StatusPool(self) | ||
status_pool.add(Status("workload")) # this tracks workload status | ||
status_pool.add(Status("relation_1")) # this tracks my integration #1 | ||
status_pool.add(Status("relation_2")) # this tracks my integration #2 | ||
|
||
status_pool.set_status("relation_1", ActiveStatus('✅')) | ||
status_pool.relation_2 = ActiveStatus('✅') | ||
|
||
workload_status = status_pool.workload | ||
workload_status.status = ActiveStatus('✅') | ||
Comment on lines
+20
to
+24
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Seems like trying to accommodate everything and the kitchen sink here is a bit much. I'd prefer we settle on one canonical way to set/retrieve status. I'd almost be inclined to just do |
||
... | ||
|
||
status_pool.relation_1 = ActiveStatus('✅') | ||
status_pool.commit() # sync with juju | ||
status_pool.relation_1.unset() # send status_1 back to unknown, until you set it again. | ||
|
||
status_pool.relation_1.unset() # send status_1 back to unknown, until you set it again. | ||
|
||
status_pool.relation_2 = WaitingStatus('𝌗: foo') | ||
|
||
# write some logs with automatic prefixes based on the status name | ||
status_pool.workload.warning('found something weird') | ||
status_pool.workload.info('attempting to work around...') | ||
status_pool.workload.error('whoopsiedaisies!') | ||
|
||
status_pool.workload = BlockedStatus('💔') | ||
status_pool.commit() | ||
``` | ||
status_pool.commit() | ||
``` | ||
|
||
Juju status will display: | ||
|
||
|
@@ -50,78 +55,70 @@ Sorted best-to-worst, all possible statuses are: | |
- `waiting` | ||
- `blocked` | ||
|
||
Their intended usage mirrors that of [statuses in charms](https://discourse.charmhub.io/t/status-values/1168). | ||
Their intended usage mirrors that of [statuses in charms](https://discourse.charmhub.io/t/status-values/1168). | ||
|
||
In `ops` you can't set a Unit status to `unknown`. Unknown is reserved for units that are not initialized yet (i.e. the charm hasn't had a chance to run). | ||
In `ops` you can't set a Unit status to `unknown`. Unknown is reserved for units that are not initialized yet (i.e. the charm hasn't had a chance to run). | ||
|
||
Here `unknown` is also special. | ||
|
||
- When you first create the pool, all statuses start off as `unknown`. | ||
- `unknown` means: not relevant/not interesting, such as when a non-necessary relation for a charm is not present. As such, you typically don't want to surface unknown statuses to the user. Therefore, you can also choose to set the class attribute `StatusPool.SKIP_UNKNOWN=True` to automatically hide `unknown` statuses from the clobbered pool message. | ||
- As soon as you set a status, the status can be brought back to `unknown` only by calling `Status.unset()`, unlike other statuses, which can be set by assignment like so: `self.status_pool.foo = WaitingStatus('bar')` | ||
- `unknown` means: not relevant/not interesting, such as when a non-necessary relation for a charm is not present. As such, you typically don't want to surface unknown statuses to the user. Therefore, you can also choose to init the pool with attribute `skip_unknown=True` to automatically hide `unknown` statuses from the summarized pool message. | ||
- As soon as you set a status, the status can be brought back to `unknown` by calling `Status.unset()` (a handy shortcut). You can also set the status to `UnknownStatus()` directly. | ||
|
||
## Priority | ||
|
||
To unambiguously be able to point out the 'worst' status in a pool, the concept of `priority` comes into play. | ||
By default, the order of definition of the Statuses in the pool determines their priority: | ||
from top to bottom = from most important to least important. | ||
By default, the order of addition of the Statuses in the pool determines their priority: | ||
from first to list == from most important to least important. | ||
Example: | ||
|
||
```python | ||
from compound_status import * | ||
class MyPool(StatusPool): | ||
relation_1 = Status() # priority 1 | ||
relation_2 = Status() # priority 2 | ||
relation_3 = Status() # priority 3 | ||
workload = Status() # priority 4 | ||
relation_4 = Status() # priority 5 | ||
status_pool = StatusPool(self) | ||
status_pool.add(Status("workload")) # priority 1 | ||
status_pool.add(Status("relation_1")) # priority 2 | ||
status_pool.add(Status("relation_2")) # priority 3 | ||
``` | ||
|
||
In this case, if all are active except `relation_3` and `workload`, which are both `blocked`, only the status for `relation_3` will be shown, because it has been defined first and has therefore implicitly priority 3. | ||
In this case, if all are active except `workload` and `relation_2`, which are both `blocked`, only the status for `workload` will be shown, because it has been added first and has therefore implicitly priority 3. | ||
|
||
To allow more flexibility (subclassing, whatnot), you can also manually pass priorities to the Statuses, like so: | ||
To allow more flexibility, you can also manually pass priorities to the Statuses, like so: | ||
|
||
```python | ||
from compound_status import * | ||
class MyPool(StatusPool): | ||
SKIP_UNKNOWN = True | ||
|
||
relation_1 = Status(priority=12) | ||
relation_2 = Status(priority=10) | ||
relation_3 = Status(priority=62) | ||
workload = Status(priority=40) | ||
relation_4 = Status(priority=1) | ||
status_pool = StatusPool(self) | ||
status_pool.add(Status("workload", priority=99)) # priority 3 | ||
status_pool.add(Status("relation_1", priority=1)) # priority 1 | ||
status_pool.add(Status("relation_2", priority=3)) # priority 2 | ||
``` | ||
|
||
In this case, if `relation_3` has lower priority than `workload`, so if both are blocked `workload` will take precedence. | ||
In this case, if `workload` has lower priority than `relation_1`, so if both are blocked `relation_1` will take precedence. | ||
|
||
Notes: | ||
- priority defaults to `0` if not explicitly set, thus ties are broken by insertion order (assuming stable sorting is used in summarizing functions). | ||
|
||
Caveats: | ||
- you can't mix 'manual' and 'auto' priority modes: either you pass `priority:int` to each and every status, or to none at all. | ||
- You have to ensure yourself that no two Statuses have the same priority. In that case, the precedence will be (presumably) random. | ||
TODO: document how to use and create summarizer functions. | ||
|
||
TODO: document what auto_commit does and what commits are in this context. | ||
|
||
## Dynamically defining Statuses | ||
## Example | ||
|
||
Having statically defined Statuses is nice because you get code completion, type hints, and so on, but sometimes it's not enough. Sometimes you want to use statuses to track intrinsically dynamic things, such as many relations attached to an endpoint. Every time a unit joins, you want to track the status of the relation with that remote in a separate status. For that purpose, we offer | ||
-`StatusPool.add_status` to add and start tracking a new status | ||
-`StatusPool.get_status` to grab an existing status by name (alias to `getattr`) | ||
-`StatusPool.set_status` to set a status by name (alias to `setattr`) | ||
-`StatusPool.remove_status` to remove (forget) an existing status | ||
Here's an example that uses more advanced features: | ||
|
||
Example usage (pseudocody): | ||
TODO: update this and other examples in the readme | ||
|
||
```python | ||
from compound_status import * | ||
from compound_status import StatusPool, Status | ||
from ops.charm import RelationDepartedEvent, RelationJoinedEvent | ||
|
||
class MyPool(StatusPool): | ||
workload = Status() | ||
|
||
|
||
class MyCharm(CharmBase): | ||
def __init__(self, framework, key=None): | ||
super().__init__(framework, key) | ||
self.status_pool = MyPool(self) | ||
self.status_pool = StatusPool(self) | ||
self.status_pool.add(Status('workload')) | ||
self.framework.observe(self.on.workload_pebble_ready, | ||
self._workload_ready) | ||
self.framework.observe(self.on.foo_relation_joined, | ||
|
@@ -137,38 +134,35 @@ class MyCharm(CharmBase): | |
|
||
def _foo_relation_joined(self, event:RelationJoinedEvent): | ||
remote_unit_name = event.unit # the unit that just joined | ||
identifier = remote_unit_name.replace('/', '_') | ||
status = Status(tag=remote_unit_name) | ||
self.status_pool.add_status(status, identifier) | ||
|
||
# from now on you can: | ||
stat = getattr(self.status_pool, identifier) | ||
status = Status(remote_unit_name) | ||
self.status_pool.add(status) | ||
|
||
# now it's in the pool | ||
stat = self.status_pool.get(remote_unit_name) | ||
assert stat is status | ||
|
||
def _foo_relation_changed(self, event): | ||
for remote_unit in event.relation.units: | ||
identifier = remote_unit.name.replace('/', '_') | ||
|
||
# you can access the 'previous' status: | ||
# same as: getattr(self.status_pool, identifier) | ||
previous_status = self.status_pool.get_status(identifier) | ||
previous_status = self.status_pool.get(remote_unit.name) | ||
print(previous_status) | ||
|
||
# for example | ||
new_status = WaitingStatus('this relation is waiting') | ||
previous_status.warning('waiting because...') | ||
|
||
# and then you can | ||
self.status_pool.set_status(identifier, new_status) | ||
# same as: setattr(self.status_pool, identifier, new_status) | ||
|
||
self.status_pool.set_status(remote_unit.name, new_status) | ||
# same as this, because remember that `previous_status` is a `Status` object in the pool: | ||
# previous_status.status = new_status | ||
|
||
def _foo_relation_departed(self, event: RelationDepartedEvent): | ||
remote_unit_name = event.departing_unit.name | ||
identifier = remote_unit_name.replace('/', '_') | ||
current_status = self.status_pool.get_status(identifier) | ||
if current_status.status == 'blocked': | ||
current_status = self.status_pool.get(remote_unit_name) | ||
if current_status.get_name() == 'blocked': | ||
current_status.error( | ||
'This unit departed while the relation status was blocked;' | ||
'This unit departed while the relation status was blocked;' | ||
'this means very bad things.') | ||
# forget about this status: | ||
self.status_pool.remove_status(current_status) | ||
|
@@ -177,16 +171,20 @@ class MyCharm(CharmBase): | |
|
||
# How to get it | ||
|
||
`charmcraft fetch-lib charms.compound_status.v0.compound_status` | ||
``` | ||
charmcraft fetch-lib charms.compound_status.v0.compound_status | ||
``` | ||
|
||
TODO: docs for installing from git and possibly pypi in the future | ||
|
||
# How to contribute | ||
if you want to publish a new revision, you can run `scripts/update.sh`. | ||
This will | ||
This will | ||
- Bump the revision | ||
- Inline the lib | ||
- Publish the lib | ||
|
||
When you bump to a new (major) version, you'll have to manually change the | ||
When you bump to a new (major) version, you'll have to manually change the | ||
value of `$LIB_V` in `scripts/publish.sh`. | ||
|
||
# Demo charm | ||
|
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about something like: