Merge lp:~mbp/launchpad/flags-webapp into lp:launchpad

Proposed by Martin Pool
Status: Merged
Approved by: Martin Pool
Approved revision: no longer in the source branch.
Merged at revision: 11616
Proposed branch: lp:~mbp/launchpad/flags-webapp
Merge into: lp:launchpad
Diff against target: 320 lines (+138/-136)
3 files modified
lib/lp/services/features/__init__.py (+130/-4)
lib/lp/services/features/doc/features.txt (+0/-127)
lib/lp/services/features/flags.py (+8/-5)
To merge this branch: bzr merge lp:~mbp/launchpad/flags-webapp
Reviewer Review Type Date Requested Status
Abel Deuring (community) code Approve
Review via email: mp+32967@code.launchpad.net

Commit message

move feature flag documentation into docstrings

Description of the change

This makes the feature flag developer documentation more accessible in the Pydoctor web view.

To post a comment you must log in.
Revision history for this message
Martin Pool (mbp) wrote :

(This is not super valuable to merge on its own, but I wanted to get the bulk change separated out from substantial changes.)

Revision history for this message
Abel Deuring (adeuring) :
review: Approve (code)
Revision history for this message
Martin Pool (mbp) wrote :

@Abel would you please land this for me?

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'lib/lp/services/features/__init__.py'
--- lib/lp/services/features/__init__.py 2010-09-12 05:19:38 +0000
+++ lib/lp/services/features/__init__.py 2010-09-21 06:46:51 +0000
@@ -1,10 +1,136 @@
1# Copyright 2010 Canonical Ltd. This software is licensed under the1# Copyright 2010 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4"""lp.services.features provide dynamically configurable feature flags.4"""Dynamic feature configuration.
55
6These can be turned on and off by admins, and can affect particular6Introduction
7defined scopes such as "beta users" or "production servers."7============
8
9The point of feature flags is to let us turn some features of Launchpad on
10and off without changing the code or restarting the application, and to
11expose different features to different subsets of users.
12
13See U{https://dev.launchpad.net/LEP/FeatureFlags} for more discussion and
14rationale.
15
16The typical use for feature flags is within web page requests but they can
17also be used in asynchronous jobs or apis or other parts of Launchpad.
18
19Internal model for feature flags
20================================
21
22A feature flag maps from a I{name} to a I{value}. The specific value used
23for a particular request is determined by a set of zero or more I{scopes}
24that apply to that request, by finding the I{rule} with the highest
25I{priority}.
26
27Flags are defined by a I{name} that typically looks like a Python
28identifier, for example C{notification.global.text}. A definition is
29given for a particular I{scope}, which also looks like a dotted identifier,
30for example C{user.beta} or C{server.edge}. This is just a naming
31convention, and they do not need to correspond to Python modules.
32
33The value is stored in the database as just a Unicode string, and it might
34be interpreted as a boolean, number, human-readable string or whatever.
35
36The default for flags is to be None if they're not set in the database, so
37that should be a sensible baseline default state.
38
39Performance model
40=================
41
42Flags are supposed to be cheap enough that you can introduce them without
43causing a performance concern.
44
45If the page does not check any flags, no extra work will be done. The
46first time a page checks a flag, all the rules will be read from the
47database and held in memory for the duration of the request.
48
49Scopes may be expensive in some cases, such as checking group membership.
50Whether a scope is active or not is looked up the first time it's needed
51within a particular request.
52
53The standard page footer identifies the flags and scopes that were
54actually used by the page.
55
56Naming conventions
57==================
58
59We have naming conventions for feature flags and scopes, so that people can
60understand the likely impact of a particular flag and so they can find all
61the flags likely to affect a feature.
62
63So for any flag we want to say:
64
65 - What application area does this affect? (malone, survey, questions,
66 code, etc)
67
68 - What specific feature does it change?
69
70 - What affect does it have on this feature? The most common is "enabled"
71 but for some other we want to specify a specific value as well such as
72 "date" or "size".
73
74These are concatenated with dots so the overall feature name looks a bit
75like a Python module name.
76
77A similar approach is used for scopes.
78
79Checking flags in page templates
80================================
81
82You can conditionally show some text like this::
83
84 <tal:survey condition="features/user_survey.enabled">
85 &nbsp;&bull;&nbsp;
86 <a href="http://survey.example.com/">Take our survey!</a>
87 </tal:survey>
88
89You can use the built-in TAL feature of prepending C{not:} to the
90condition, and for flags that have a value you could use them in
91C{tal:replace} or C{tal:attributes}.
92
93If you just want to simply insert some text taken from a feature, say
94something like::
95
96 Message of the day: ${motd.text}
97
98Templates can also check whether the request is in a particular scope, but
99before using this consider whether the code will always be bound to that
100scope or whether it would be more correct to define a new feature::
101
102 <p tal:condition="feature_scopes/server.staging">
103 Staging server: all data will be discarded daily!</p>
104
105Checking flags in code
106======================
107
108The Zope traversal code establishes a `FeatureController` for the duration
109of a request. The object can be obtained through either
110`request.features` or `lp.services.features.per_thread.features`. This
111provides various useful methods including `getFlag` to look up one feature
112(memoized), and `isInScope` to check one scope (also memoized).
113
114As a convenience, `lp.services.features.getFeatureFlag` looks up a single
115flag in the thread default controller.
116
117To simply check a boolean::
118
119 if features.getFeatureFlag('soyuz.derived-series-ui.enabled'):
120 ...
121
122and if you want to use the value ::
123
124 value = features.getFeatureFlag('soyuz.derived-series-ui.enabled')
125 if value:
126 print value
127
128Debugging feature usage
129=======================
130
131The flags active during a page request, and the scopes that were looked
132up are visible in the comment at the bottom of every standard Launchpad
133page.
8"""134"""
9135
10import threading136import threading
11137
=== modified file 'lib/lp/services/features/doc/features.txt'
--- lib/lp/services/features/doc/features.txt 2010-07-27 09:30:45 +0000
+++ lib/lp/services/features/doc/features.txt 2010-09-21 06:46:51 +0000
@@ -1,127 +0,0 @@
1****************************
2Feature Flag Developer Guide
3****************************
4
5Introduction
6************
7
8The point of feature flags is to let us turn some features of Launchpad on
9and off without changing the code or restarting the application, and to
10expose different features to different subsets of users.
11
12See <https://dev.launchpad.net/LEP/FeatureFlags> for more discussion and
13rationale.
14
15The typical use for feature flags is within web page requests but they can
16also be used in asynchronous jobs or apis or other parts of Launchpad.
17
18Internal model for feature flags
19********************************
20
21A feature flag maps from a *name* to a *value*. The specific value used
22for a particular request is determined by a set of zero or more *scopes*
23that apply to that request, by finding the *rule* with the highest
24*priority*.
25
26Flags are defined by a *name* that typically looks like a Python
27identifier, for example ``notification.global.text``. A definition is
28given for a particular *scope*, which also looks like a dotted identifier,
29for example ``user.beta`` or ``server.edge``. This is just a naming
30convention, and they do not need to correspond to Python modules.
31
32The value is stored in the database as just a Unicode string, and it might
33be interpreted as a boolean, number, human-readable string or whatever.
34
35The default for flags is to be None if they're not set in the database, so
36that should be a sensible baseline default state.
37
38Performance model
39*****************
40
41Flags are supposed to be cheap enough that you can introduce them without
42causing a performance concern.
43
44If the page does not check any flags, no extra work will be done. The
45first time a page checks a flag, all the rules will be read from the
46database and held in memory for the duration of the request.
47
48Scopes may be expensive in some cases, such as checking group membership.
49Whether a scope is active or not is looked up the first time it's needed
50within a particular request.
51
52The standard page footer identifies the flags and scopes that were
53actually used by the page.
54
55Naming conventions
56******************
57
58We have naming conventions for feature flags and scopes, so that people can
59understand the likely impact of a particular flag and so they can find all
60the flags likely to affect a feature.
61
62So for any flag we want to say:
63
64* What application area does this affect? (malone, survey, questions,
65 code, etc)
66
67* What specific feature does it change?
68
69* What affect does it have on this feature? The most common is "enabled"
70 but for some other we want to specify a specific value as well such as
71 "date" or "size".
72
73These are concatenated with dots so the overall feature name looks a bit
74like a Python module name.
75
76A similar approach is used for scopes.
77
78Checking flags in page templates
79********************************
80
81You can conditionally show some text like this::
82
83 <tal:survey condition="features/user_survey.enabled">
84 &nbsp;&bull;&nbsp;
85 <a href="http://survey.example.com/">Take our survey!</a>
86 </tal:survey>
87
88You can use the built-in TAL feature of prepending ``not:`` to the
89condition, and for flags that have a value you could use them in
90``tal:replace`` or ``tal:attributes``.
91
92If you just want to simply insert some text taken from a feature, say
93something like::
94
95 Message of the day: ${motd.text}
96
97Templates can also check whether the request is in a particular scope, but
98before using this consider whether the code will always be bound to that
99scope or whether it would be more correct to define a new feature::
100
101 <p tal:condition="feature_scopes/server.staging">
102 Staging server: all data will be discarded daily!</p>
103
104Checking flags in code
105**********************
106
107The Zope traversal code establishes a `FeatureController` for the duration
108of a request. The object can be obtained through either
109`request.features` or `lp.services.features.per_thread.features`. This
110provides various useful methods including `getFlag` to look up one feature
111(memoized), and `isInScope` to check one scope (also memoized).
112
113As a convenience, `lp.services.features.getFeatureFlag` looks up a single
114flag in the thread default controller.
115
116Debugging feature usage
117***********************
118
119The flags active during a page request, and the scopes that were looked
120up are visible in the comment at the bottom of every standard Launchpad
121page.
122
123Defining scopes
124***************
125
126
127.. vim: ft=rst
1280
=== modified file 'lib/lp/services/features/flags.py'
--- lib/lp/services/features/flags.py 2010-08-10 04:35:41 +0000
+++ lib/lp/services/features/flags.py 2010-09-21 06:46:51 +0000
@@ -46,8 +46,10 @@
46 """A FeatureController tells application code what features are active.46 """A FeatureController tells application code what features are active.
4747
48 It does this by meshing together two sources of data:48 It does this by meshing together two sources of data:
49 - feature flags, typically set by an administrator into the database49
50 - feature scopes, which would typically be looked up based on attributes50 - feature flags, typically set by an administrator into the database
51
52 - feature scopes, which would typically be looked up based on attributes
51 of the current web request, or the user for whom a job is being run, or53 of the current web request, or the user for whom a job is being run, or
52 something similar.54 something similar.
5355
@@ -72,14 +74,14 @@
72 The controller is then supposed to be held in a thread-local and reused74 The controller is then supposed to be held in a thread-local and reused
73 for the duration of the request.75 for the duration of the request.
7476
75 See <https://dev.launchpad.net/LEP/FeatureFlags>77 @see: U{https://dev.launchpad.net/LEP/FeatureFlags}
76 """78 """
7779
78 def __init__(self, scope_check_callback):80 def __init__(self, scope_check_callback):
79 """Construct a new view of the features for a set of scopes.81 """Construct a new view of the features for a set of scopes.
8082
81 :param scope_check_callback: Given a scope name, says whether83 :param scope_check_callback: Given a scope name, says whether
82 it's active or not.84 it's active or not.
83 """85 """
84 self._known_scopes = Memoize(scope_check_callback)86 self._known_scopes = Memoize(scope_check_callback)
85 self._known_flags = Memoize(self._checkFlag)87 self._known_flags = Memoize(self._checkFlag)
@@ -91,8 +93,9 @@
91 """Get the value of a specific flag.93 """Get the value of a specific flag.
92 94
93 :param flag: A name to lookup. e.g. 'recipes.enabled'95 :param flag: A name to lookup. e.g. 'recipes.enabled'
96
94 :return: The value of the flag determined by the highest priority rule97 :return: The value of the flag determined by the highest priority rule
95 that matched.98 that matched.
96 """99 """
97 return self._known_flags.lookup(flag)100 return self._known_flags.lookup(flag)
98101