Skip to content

Commit 97cd339

Browse files
authored
Merge pull request #44 from regisb/regisb/toggles-config-and-tools
[BD-21] Add featuretoggle configuration and sphinx extension
2 parents a6c0310 + cb555d6 commit 97cd339

File tree

10 files changed

+217
-14
lines changed

10 files changed

+217
-14
lines changed

.github/PULL_REQUEST_TEMPLATE.md

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@
22

33
**JIRA:** Link to JIRA ticket
44

5-
**Dependencies:** dependencies on other outstanding PRs, issues, etc.
5+
**Dependencies:** dependencies on other outstanding PRs, issues, etc.
66

77
**Merge deadline:** List merge deadline (if any)
88

9-
**Installation instructions:** List any non-trivial installation
9+
**Installation instructions:** List any non-trivial installation
1010
instructions.
1111

1212
**Testing instructions:**
@@ -16,8 +16,8 @@ instructions.
1616
3. If C happened instead - check failed.
1717

1818
**Reviewers:**
19-
- [ ] tag reviewer
20-
- [ ] tag reviewer
19+
- [ ] tag reviewer
20+
- [ ] tag reviewer
2121

2222
**Merge checklist:**
2323
- [ ] All reviewers approved
@@ -26,14 +26,13 @@ instructions.
2626
- [ ] Changelog record added
2727
- [ ] Documentation updated (not only docstrings)
2828
- [ ] Commits are squashed
29-
- [ ] PR author is listed in AUTHORS
3029

3130
**Post merge:**
3231
- [ ] Create a tag
33-
- [ ] Check new version is pushed to PyPi after tag-triggered build is
32+
- [ ] Check new version is pushed to PyPi after tag-triggered build is
3433
finished.
3534
- [ ] Delete working branch (if not needed anymore)
3635

37-
**Author concerns:** List any concerns about this PR - inelegant
38-
solutions, hacks, quick-and-dirty implementations, concerns about
36+
**Author concerns:** List any concerns about this PR - inelegant
37+
solutions, hacks, quick-and-dirty implementations, concerns about
3938
migrations, etc.

CHANGELOG.rst

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,12 @@ Change Log
1111

1212
.. There should always be an "Unreleased" section for changes pending release.
1313
14-
Unreleased
15-
~~~~~~~~~~
14+
[0.5.0] - 2020-08-06
15+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1616

17-
* Add ADR 0001-config-and-tools.rst for adding a placing in this repository for shared annotation configs and supporting tools.
17+
* Add ``featuretoggles`` Sphinx extension
18+
* Include ``config_and_tools`` folder in pip-installable package
19+
* Add ADR 0001-config-and-tools.rst for adding a place in this repository for shared annotation configs and supporting tools.
1820

1921
[0.4.0] - 2020-07-22
2022
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

MANIFEST.in

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@ include CONTRIBUTING.rst
44
include LICENSE.txt
55
include README.rst
66
include requirements/base.in
7-
recursive-include code_annotations *.html *.png *.gif *js *.css *jpg *jpeg *svg *py
7+
recursive-include code_annotations *.html *.png *.gif *js *.css *jpg *jpeg *svg *py *.yaml *.yml

code_annotations/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@
22
Extensible tools for parsing annotations in codebases.
33
"""
44

5-
__version__ = '0.4.0'
5+
__version__ = '0.5.0'
File renamed without changes.
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# This code-annotations configuration file supports OEP-17, Feature Toggles.
2+
3+
source_path: ./
4+
report_path: reports
5+
safelist_path: .annotation_safe_list.yml
6+
coverage_target: 50.0
7+
annotations:
8+
documented_elsewhere:
9+
- ".. documented_elsewhere:":
10+
- ".. documented_elsewhere_name:":
11+
feature_toggle:
12+
- ".. toggle_name:":
13+
- ".. toggle_implementation:":
14+
choices: [ExperimentWaffleFlag, WaffleFlag, WaffleSample, WaffleSwitch, CourseWaffleFlag, ConfigurationModel, DjangoSetting]
15+
- ".. toggle_default:":
16+
- ".. toggle_description:":
17+
- ".. toggle_category:":
18+
- ".. toggle_use_cases:":
19+
choices: [incremental_release, launch_date, monitored_rollout, graceful_degradation, beta_testing, vip, opt_out, opt_in, open_edx]
20+
- ".. toggle_creation_date:":
21+
- ".. toggle_expiration_date:":
22+
- ".. toggle_warnings:":
23+
- ".. toggle_tickets:":
24+
- ".. toggle_status:":
25+
extensions:
26+
python:
27+
- py
28+
rst_template: doc.rst.j2
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
"""
2+
Sphinx extension for viewing feature toggle annotations.
3+
"""
4+
import os
5+
6+
import pkg_resources
7+
8+
from code_annotations.base import AnnotationConfig
9+
from code_annotations.find_static import StaticSearch
10+
from docutils import nodes
11+
from sphinx.util.docutils import SphinxDirective
12+
13+
14+
def find_feature_toggles(source_path):
15+
"""
16+
Find the feature toggles as defined in the configuration file.
17+
18+
Return:
19+
toggles (dict): feature toggles indexed by name.
20+
"""
21+
config_path = pkg_resources.resource_filename(
22+
"code_annotations",
23+
os.path.join("config_and_tools", "config", "feature_toggle_annotations.yaml"),
24+
)
25+
config = AnnotationConfig(
26+
config_path, verbosity=0, source_path_override=source_path
27+
)
28+
results = StaticSearch(config).search()
29+
30+
toggles = {}
31+
current_entry = {}
32+
for filename, entries in results.items():
33+
for entry in entries:
34+
key = entry["annotation_token"]
35+
value = entry["annotation_data"]
36+
if key == ".. toggle_name:":
37+
toggle_name = value
38+
toggles[toggle_name] = {
39+
"filename": filename,
40+
"line_number": entry["line_number"],
41+
}
42+
current_entry = toggles[toggle_name]
43+
else:
44+
current_entry[key] = value
45+
46+
return toggles
47+
48+
49+
class FeatureToggles(SphinxDirective):
50+
"""
51+
Sphinx directive to list the feature toggles in a single documentation page.
52+
53+
Use this directive as follows::
54+
55+
.. featuretoggles::
56+
57+
This directive supports the following configuration parameters:
58+
59+
- ``featuretoggles_source_path``: absolute path to the repository file tree. E.g:
60+
61+
featuretoggles_source_path = os.path.join(os.path.dirname(__file__), "..", "..")
62+
63+
- ``featuretoggles_repo_url``: Github repository where the code is hosted. E.g:
64+
65+
featuretoggles_repo_url = "https://github.com/edx/myrepo"
66+
67+
- ``featuretoggles_repo_version``: current version of the git repository. E.g:
68+
69+
import git
70+
try:
71+
repo = git.Repo(search_parent_directories=True)
72+
featuretoggles_repo_version = repo.head.object.hexsha
73+
except git.InvalidGitRepositoryError:
74+
featuretoggles_repo_version = "master"
75+
"""
76+
77+
required_arguments = 0
78+
optional_arguments = 0
79+
option_spec = {}
80+
81+
def run(self):
82+
"""
83+
Public interface of the Directive class.
84+
85+
Return:
86+
nodes (list): nodes to be appended to the resulting document.
87+
"""
88+
toggle_nodes = list(self.iter_nodes())
89+
return [nodes.section("", *toggle_nodes, ids=["featuretoggles"])]
90+
91+
def iter_nodes(self):
92+
"""
93+
Iterate on the docutils nodes generated by this directive.
94+
"""
95+
toggles = find_feature_toggles(self.env.config.featuretoggles_source_path)
96+
for toggle_name in sorted(toggles):
97+
toggle = toggles[toggle_name]
98+
yield nodes.title(text=toggle_name)
99+
toggle_default_value = toggle.get(".. toggle_default:", "Not defined")
100+
toggle_default_node = nodes.literal(text=quote_value(toggle_default_value))
101+
yield nodes.paragraph("", "Default: ", toggle_default_node)
102+
yield nodes.paragraph(
103+
"",
104+
"Source: ",
105+
nodes.reference(
106+
text="{} (line {})".format(
107+
toggle["filename"], toggle["line_number"]
108+
),
109+
refuri="{}/blob/{}/{}#L{}".format(
110+
self.env.config.featuretoggles_repo_url,
111+
self.env.config.featuretoggles_repo_version,
112+
toggle["filename"],
113+
toggle["line_number"],
114+
),
115+
),
116+
)
117+
yield nodes.paragraph(text=toggle.get(".. toggle_description:", ""))
118+
119+
120+
def quote_value(value):
121+
"""
122+
Quote a Python object if it is string-like.
123+
"""
124+
if value in ("True", "False", "None"):
125+
return str(value)
126+
try:
127+
float(value)
128+
return str(value)
129+
except ValueError:
130+
pass
131+
if isinstance(value, str):
132+
return '"{}"'.format(value)
133+
return str(value)
134+
135+
136+
def setup(app):
137+
"""
138+
Declare the Sphinx extension.
139+
"""
140+
app.add_config_value(
141+
"featuretoggles_source_path", os.path.abspath(".."), "env",
142+
)
143+
app.add_config_value("featuretoggles_repo_url", "", "env")
144+
app.add_config_value("featuretoggles_repo_version", "master", "env")
145+
app.add_directive("featuretoggles", FeatureToggles)
146+
147+
return {
148+
"version": "0.1",
149+
"parallel_read_safe": True,
150+
"parallel_write_safe": True,
151+
}

docs/extensions.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
Extensions
22
----------
33

4-
Code Annotations uses `Stevedore`_ to allow new lanuages to be statically searched in an easily extensible fashion. All
4+
Code Annotations uses `Stevedore`_ to allow new languages to be statically searched in an easily extensible fashion. All
55
language searches, even the ones that come by default, are implemented as extensions. A language extension is
66
responsible for finding all comments in files of the given type. Note that extensions are only used in the Static Search
77
and not in Django Search, as Django models are obviously all written in Python.

docs/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ Contents:
1818
static_search
1919
django_search
2020
configuration
21+
sphinx_extensions
2122
extensions
2223
testing
2324
modules

docs/sphinx_extensions.rst

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
Sphinx extensions
2+
-----------------
3+
4+
``featuretoggles``
5+
==================
6+
7+
This package can be used to document the feature toggles in your code base. To do so,
8+
add the following to your ``conf.py``::
9+
10+
extensions = ["code_annotations.config_and_tools.sphinx.extensions.featuretoggles"]
11+
featuretoggles_source_path = os.path.abspath(
12+
os.path.join(os.path.dirname(__file__), "..")
13+
)
14+
featuretoggles_repo_url = "https://github.com/edx/yourrepo"
15+
try:
16+
featuretoggles_repo_version = git.Repo(search_parent_directories=True).head.object.hexsha
17+
except git.InvalidGitRepositoryError:
18+
pass
19+
20+
Then, in an ``.rst`` file::
21+
22+
.. featuretoggles::

0 commit comments

Comments
 (0)