Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs: add example Juju version markers #1311

Merged
merged 11 commits into from
Aug 20, 2024
Merged
126 changes: 102 additions & 24 deletions docs/custom_conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@
import pathlib
import sys

from docutils import nodes

from sphinx import addnodes
from sphinx.util.docutils import SphinxDirective

import furo
import furo.navigation

Expand All @@ -16,8 +21,8 @@

def _compute_navigation_tree(context):
tree_html = _old_compute_navigation_tree(context)
if not tree_html and context.get("toc"):
tree_html = furo.navigation.get_navigation_tree(context["toc"])
if not tree_html and context.get('toc'):
tonyandrewmeyer marked this conversation as resolved.
Show resolved Hide resolved
tree_html = furo.navigation.get_navigation_tree(context['toc'])
return tree_html


Expand All @@ -27,14 +32,17 @@ def _compute_navigation_tree(context):
# "invalid signature for autoattribute ('ops.pebble::ServiceDict.backoff-delay')"
import re # noqa: E402
import sphinx.ext.autodoc # noqa: E402

sphinx.ext.autodoc.py_ext_sig_re = re.compile(
r'''^ ([\w.]+::)? # explicit module name
r"""^ ([\w.]+::)? # explicit module name
([\w.]+\.)? # module and/or class name(s)
([^.()]+) \s* # thing name
(?: \((.*)\) # optional: arguments
(?:\s* -> \s* (.*))? # return annotation
)? $ # and nothing more
''', re.VERBOSE)
""",
re.VERBOSE,
)

# Custom configuration for the Sphinx documentation builder.
# All configuration specific to your project should be done in this file.
Expand Down Expand Up @@ -98,53 +106,43 @@ def _compute_navigation_tree(context):
# (Some settings must be part of the html_context dictionary, while others
# are on root level. Don't move the settings.)
html_context = {

# Change to the link to the website of your product (without "https://")
# For example: "ubuntu.com/lxd" or "microcloud.is"
# If there is no product website, edit the header template to remove the
# link (see the readme for instructions).
'product_page': 'juju.is/docs/sdk',

# Add your product tag (the orange part of your logo, will be used in the
# header) to ".sphinx/_static" and change the path here (start with "_static")
# (default is the circle of friends)
'product_tag': '_static/tag.png',

# Change to the discourse instance you want to be able to link to
# using the :discourse: metadata at the top of a file
# (use an empty value if you don't want to link)
'discourse': 'https://discourse.charmhub.io/',

# Change to the Mattermost channel you want to link to
# (use an empty value if you don't want to link)
'mattermost': '',

# Change to the Matrix channel you want to link to
# (use an empty value if you don't want to link)
'matrix': 'https://matrix.to/#/#charmhub-charmdev:ubuntu.com',

# Change to the GitHub URL for your project
'github_url': 'https://github.com/canonical/operator',

# Change to the branch for this version of the documentation
'github_version': 'main',

# Change to the folder that contains the documentation
# (usually "/" or "/docs/")
'github_folder': '/docs/',

# Change to an empty value if your GitHub repo doesn't have issues enabled.
# This will disable the feedback button and the issue link in the footer.
'github_issues': 'enabled',

# Controls the existence of Previous / Next buttons at the bottom of pages
# Valid options: none, prev, next, both
'sequential_nav': "none"
'sequential_nav': 'none',
}

# If your project is on documentation.ubuntu.com, specify the project
# slug (for example, "lxd") here.
slug = ""
slug = ''

############################################################
### Redirects
Expand All @@ -163,9 +161,7 @@ def _compute_navigation_tree(context):
############################################################

# Links to ignore when checking links
linkcheck_ignore = [
'http://127.0.0.1:8000'
]
linkcheck_ignore = ['http://127.0.0.1:8000']

# Pages on which to ignore anchors
# (This list will be appended to linkcheck_anchors_ignore_for_url)
Expand Down Expand Up @@ -197,7 +193,7 @@ def _compute_navigation_tree(context):
'sphinx.ext.napoleon',
'sphinx.ext.todo',
'sphinx.ext.viewcode',
]
]

# Add custom required Python modules that must be added to the
# .sphinx/requirements.txt file.
Expand All @@ -211,7 +207,7 @@ def _compute_navigation_tree(context):
# Add files or directories that should be excluded from processing.
custom_excludes = [
'doc-cheat-sheet*',
]
]

# Add CSS files (located in .sphinx/_static/)
custom_html_css_files = []
Expand Down Expand Up @@ -241,10 +237,10 @@ def _compute_navigation_tree(context):
## Add any configuration that is not covered by the common conf.py file.

# Define a :center: role that can be used to center the content of table cells.
rst_prolog = '''
rst_prolog = """
.. role:: center
:class: align-center
'''
"""


# -- Options for sphinx.ext.todo ---------------------------------------------
Expand Down Expand Up @@ -280,7 +276,7 @@ def _compute_navigation_tree(context):
autodoc_member_order = 'alphabetical'

autodoc_default_options = {
'members': None, # None here means "yes"
'members': None, # None here means "yes"
'undoc-members': None,
'show-inheritance': None,
}
Expand Down Expand Up @@ -334,3 +330,85 @@ def _compute_navigation_tree(context):
('py:class', 'ops.testing.CharmType'),
('py:obj', 'ops.testing.CharmType'),
]


# This is very strongly based on
Copy link
Contributor

Choose a reason for hiding this comment

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

# https://github.com/sphinx-doc/sphinx/blob/03b9134ee00e98df4f8b5f6d22f345cdafe31870/sphinx/domains/changeset.py#L46
# Unfortunately, the built-in class is not easily subclassable without also
# requiring extra CSS.
tonyandrewmeyer marked this conversation as resolved.
Show resolved Hide resolved
class JujuVersion(SphinxDirective):
"""Directive to describe in which version of Juju a feature was added or removed."""

change = 'changed'

has_content = True
required_arguments = 1
optional_arguments = 1
final_argument_whitespace = True
option_spec = {}

def run(self):
node = addnodes.versionmodified()
node.document = self.state.document
self.set_source_info(node)
# Make the <div> have a class matching the built-in directives so that
# we don't need custom CSS.
node['type'] = f'version{self.change}'
node['version'] = self.arguments[0]
text = f'{self.text} in Juju version {self.arguments[0]}'
if len(self.arguments) == 2:
inodes, messages = self.state.inline_text(self.arguments[1], self.lineno + 1)
para = nodes.paragraph(self.arguments[1], '', *inodes, translatable=False)
self.set_source_info(para)
node.append(para)
else:
messages = []
if self.content:
node += self.parse_content_to_nodes()
classes = ['versionmodified', self.change, 'jujuversion']
if len(node) > 0 and isinstance(node[0], nodes.paragraph):
# The contents start with a paragraph.
if node[0].rawsource:
# Make the first paragraph translatable.
content = nodes.inline(node[0].rawsource, translatable=True)
content.source = node[0].source
content.line = node[0].line
content += node[0].children
node[0].replace_self(nodes.paragraph('', '', content, translatable=False))

para = node[0]
para.insert(0, nodes.inline('', '%s: ' % text, classes=classes))
elif len(node) > 0:
# The contents do not start with a paragraph.
para = nodes.paragraph(
'', '', nodes.inline('', '%s: ' % text, classes=classes), translatable=False
)
node.insert(0, para)
else:
# The contents are empty.
para = nodes.paragraph(
'', '', nodes.inline('', '%s.' % text, classes=classes), translatable=False
)
node.append(para)

domain = self.env.get_domain('changeset')
domain.note_changeset(node)

ret: list[Node] = [node]
ret += messages
return ret


class JujuAdded(JujuVersion):
change = 'added'
text = 'Added'


class JujuRemoved(JujuVersion):
change = 'removed'
text = 'Scheduled for removal'


def setup(app):
app.add_directive('jujuversion', JujuAdded)
app.add_directive('jujuremoved', JujuRemoved)
50 changes: 45 additions & 5 deletions ops/charm.py
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,8 @@ class PreSeriesUpgradeEvent(HookEvent):
callback method associated with this event, the administrator will initiate
steps to actually upgrade the series. After the upgrade has been completed,
the :class:`PostSeriesUpgradeEvent` will fire.

.. jujuremoved:: 4.0
tonyandrewmeyer marked this conversation as resolved.
Show resolved Hide resolved
"""


Expand All @@ -368,6 +370,8 @@ class PostSeriesUpgradeEvent(HookEvent):
upgraded version of a database. Note however charms are expected to check if
the series has actually changed or whether it was rolled back to the
original series.

.. jujuremoved:: 4.0
"""


Expand Down Expand Up @@ -403,6 +407,8 @@ class CollectMetricsEvent(HookEvent):

Note that associated callback methods are currently sandboxed in
how they can interact with Juju.

.. jujuremoved:: 4.0
"""

def add_metrics(
Expand Down Expand Up @@ -828,7 +834,10 @@ def restore(self, snapshot: Dict[str, Any]):


class PebbleCustomNoticeEvent(PebbleNoticeEvent):
"""Event triggered when a Pebble notice of type "custom" is created or repeats."""
"""Event triggered when a Pebble notice of type "custom" is created or repeats.

.. jujuversion:: 3.4
"""


class PebbleCheckEvent(WorkloadEvent):
Expand Down Expand Up @@ -873,6 +882,8 @@ class PebbleCheckFailedEvent(PebbleCheckEvent):
emitted next). If the handler is executing code that should only be done
if the check is currently failing, check the current status with
``event.info.status == ops.pebble.CheckStatus.DOWN``.

.. jujuversion:: 3.6
"""


Expand All @@ -882,6 +893,8 @@ class PebbleCheckRecoveredEvent(PebbleCheckEvent):
This event is only triggered when the check has previously reached a failure
state (not simply failed, but failed at least as many times as the
configured threshold).

.. jujuversion:: 3.6
"""


Expand Down Expand Up @@ -929,6 +942,9 @@ class SecretChangedEvent(SecretEvent):
Typically, the charm will fetch the new content by calling
:meth:`event.secret.get_content() <ops.Secret.get_content>` with ``refresh=True``
to tell Juju to start tracking the new revision.

.. jujuversion:: 3.0
Charm secrets added in Juju 3.0, user secrets added in Juju 3.3
"""


Expand All @@ -938,6 +954,8 @@ class SecretRotateEvent(SecretEvent):
This event is fired on the secret owner to inform it that the secret must
be rotated. The event will keep firing until the owner creates a new
revision by calling :meth:`event.secret.set_content() <ops.Secret.set_content>`.

.. jujuversion:: 3.0
"""

def defer(self) -> NoReturn:
Expand All @@ -963,6 +981,8 @@ class SecretRemoveEvent(SecretEvent):
:meth:`event.secret.remove_revision() <ops.Secret.remove_revision>` to
remove the now-unused revision. If the charm does not, then the event will
be emitted again, when further revisions are ready for removal.

.. jujuversion:: 3.0
"""

def __init__(self, handle: 'Handle', id: str, label: Optional[str], revision: int):
Expand Down Expand Up @@ -998,6 +1018,8 @@ class SecretExpiredEvent(SecretEvent):
This event is fired on the secret owner to inform it that the secret revision
must be removed. The event will keep firing until the owner removes the
revision by calling :meth:`event.secret.remove_revision() <ops.Secret.remove_revision>`.

.. jujuversion:: 3.0
"""

def __init__(self, handle: 'Handle', id: str, label: Optional[str], revision: int):
Expand Down Expand Up @@ -1159,10 +1181,16 @@ class CharmEvents(ObjectEvents):
"""Triggered by request to upgrade the charm (see :class:`UpgradeCharmEvent`)."""

pre_series_upgrade = EventSource(PreSeriesUpgradeEvent)
"""Triggered to prepare a unit for series upgrade (see :class:`PreSeriesUpgradeEvent`)."""
"""Triggered to prepare a unit for series upgrade (see :class:`PreSeriesUpgradeEvent`).

.. jujuremoved:: 4.0
"""

post_series_upgrade = EventSource(PostSeriesUpgradeEvent)
"""Triggered after a series upgrade (see :class:`PostSeriesUpgradeEvent`)."""
"""Triggered after a series upgrade (see :class:`PostSeriesUpgradeEvent`).

.. jujuremoved:: 4.0
"""

leader_elected = EventSource(LeaderElectedEvent)
"""Triggered when a new leader has been elected (see :class:`LeaderElectedEvent`)."""
Expand All @@ -1171,30 +1199,42 @@ class CharmEvents(ObjectEvents):
"""Triggered when leader changes any settings (see
:class:`LeaderSettingsChangedEvent`).

.. deprecated: 2.4.0
.. deprecated:: 2.4.0
"""

collect_metrics = EventSource(CollectMetricsEvent)
"""Triggered by Juju to collect metrics (see :class:`CollectMetricsEvent`)."""
"""Triggered by Juju to collect metrics (see :class:`CollectMetricsEvent`).

.. jujuremoved:: 4.0
"""

secret_changed = EventSource(SecretChangedEvent)
"""Triggered by Juju on the observer when the secret owner changes its contents (see
:class:`SecretChangedEvent`).

.. jujuversion:: 3.0
Charm secrets added in Juju 3.0, user secrets added in Juju 3.3
"""

secret_expired = EventSource(SecretExpiredEvent)
"""Triggered by Juju on the owner when a secret's expiration time elapses (see
:class:`SecretExpiredEvent`).

.. jujuversion:: 3.0
"""

secret_rotate = EventSource(SecretRotateEvent)
"""Triggered by Juju on the owner when the secret's rotation policy elapses (see
:class:`SecretRotateEvent`).

.. jujuversion:: 3.0
"""

secret_remove = EventSource(SecretRemoveEvent)
"""Triggered by Juju on the owner when a secret revision can be removed (see
:class:`SecretRemoveEvent`).

.. jujuversion:: 3.0
"""

collect_app_status = EventSource(CollectStatusEvent)
Expand Down
Loading
Loading