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
1=== modified file 'lib/lp/services/features/__init__.py'
2--- lib/lp/services/features/__init__.py 2010-09-12 05:19:38 +0000
3+++ lib/lp/services/features/__init__.py 2010-09-21 06:46:51 +0000
4@@ -1,10 +1,136 @@
5 # Copyright 2010 Canonical Ltd. This software is licensed under the
6 # GNU Affero General Public License version 3 (see the file LICENSE).
7
8-"""lp.services.features provide dynamically configurable feature flags.
9-
10-These can be turned on and off by admins, and can affect particular
11-defined scopes such as "beta users" or "production servers."
12+"""Dynamic feature configuration.
13+
14+Introduction
15+============
16+
17+The point of feature flags is to let us turn some features of Launchpad on
18+and off without changing the code or restarting the application, and to
19+expose different features to different subsets of users.
20+
21+See U{https://dev.launchpad.net/LEP/FeatureFlags} for more discussion and
22+rationale.
23+
24+The typical use for feature flags is within web page requests but they can
25+also be used in asynchronous jobs or apis or other parts of Launchpad.
26+
27+Internal model for feature flags
28+================================
29+
30+A feature flag maps from a I{name} to a I{value}. The specific value used
31+for a particular request is determined by a set of zero or more I{scopes}
32+that apply to that request, by finding the I{rule} with the highest
33+I{priority}.
34+
35+Flags are defined by a I{name} that typically looks like a Python
36+identifier, for example C{notification.global.text}. A definition is
37+given for a particular I{scope}, which also looks like a dotted identifier,
38+for example C{user.beta} or C{server.edge}. This is just a naming
39+convention, and they do not need to correspond to Python modules.
40+
41+The value is stored in the database as just a Unicode string, and it might
42+be interpreted as a boolean, number, human-readable string or whatever.
43+
44+The default for flags is to be None if they're not set in the database, so
45+that should be a sensible baseline default state.
46+
47+Performance model
48+=================
49+
50+Flags are supposed to be cheap enough that you can introduce them without
51+causing a performance concern.
52+
53+If the page does not check any flags, no extra work will be done. The
54+first time a page checks a flag, all the rules will be read from the
55+database and held in memory for the duration of the request.
56+
57+Scopes may be expensive in some cases, such as checking group membership.
58+Whether a scope is active or not is looked up the first time it's needed
59+within a particular request.
60+
61+The standard page footer identifies the flags and scopes that were
62+actually used by the page.
63+
64+Naming conventions
65+==================
66+
67+We have naming conventions for feature flags and scopes, so that people can
68+understand the likely impact of a particular flag and so they can find all
69+the flags likely to affect a feature.
70+
71+So for any flag we want to say:
72+
73+ - What application area does this affect? (malone, survey, questions,
74+ code, etc)
75+
76+ - What specific feature does it change?
77+
78+ - What affect does it have on this feature? The most common is "enabled"
79+ but for some other we want to specify a specific value as well such as
80+ "date" or "size".
81+
82+These are concatenated with dots so the overall feature name looks a bit
83+like a Python module name.
84+
85+A similar approach is used for scopes.
86+
87+Checking flags in page templates
88+================================
89+
90+You can conditionally show some text like this::
91+
92+ <tal:survey condition="features/user_survey.enabled">
93+ &nbsp;&bull;&nbsp;
94+ <a href="http://survey.example.com/">Take our survey!</a>
95+ </tal:survey>
96+
97+You can use the built-in TAL feature of prepending C{not:} to the
98+condition, and for flags that have a value you could use them in
99+C{tal:replace} or C{tal:attributes}.
100+
101+If you just want to simply insert some text taken from a feature, say
102+something like::
103+
104+ Message of the day: ${motd.text}
105+
106+Templates can also check whether the request is in a particular scope, but
107+before using this consider whether the code will always be bound to that
108+scope or whether it would be more correct to define a new feature::
109+
110+ <p tal:condition="feature_scopes/server.staging">
111+ Staging server: all data will be discarded daily!</p>
112+
113+Checking flags in code
114+======================
115+
116+The Zope traversal code establishes a `FeatureController` for the duration
117+of a request. The object can be obtained through either
118+`request.features` or `lp.services.features.per_thread.features`. This
119+provides various useful methods including `getFlag` to look up one feature
120+(memoized), and `isInScope` to check one scope (also memoized).
121+
122+As a convenience, `lp.services.features.getFeatureFlag` looks up a single
123+flag in the thread default controller.
124+
125+To simply check a boolean::
126+
127+ if features.getFeatureFlag('soyuz.derived-series-ui.enabled'):
128+ ...
129+
130+and if you want to use the value ::
131+
132+ value = features.getFeatureFlag('soyuz.derived-series-ui.enabled')
133+ if value:
134+ print value
135+
136+Debugging feature usage
137+=======================
138+
139+The flags active during a page request, and the scopes that were looked
140+up are visible in the comment at the bottom of every standard Launchpad
141+page.
142 """
143
144 import threading
145
146=== modified file 'lib/lp/services/features/doc/features.txt'
147--- lib/lp/services/features/doc/features.txt 2010-07-27 09:30:45 +0000
148+++ lib/lp/services/features/doc/features.txt 2010-09-21 06:46:51 +0000
149@@ -1,127 +0,0 @@
150-****************************
151-Feature Flag Developer Guide
152-****************************
153-
154-Introduction
155-************
156-
157-The point of feature flags is to let us turn some features of Launchpad on
158-and off without changing the code or restarting the application, and to
159-expose different features to different subsets of users.
160-
161-See <https://dev.launchpad.net/LEP/FeatureFlags> for more discussion and
162-rationale.
163-
164-The typical use for feature flags is within web page requests but they can
165-also be used in asynchronous jobs or apis or other parts of Launchpad.
166-
167-Internal model for feature flags
168-********************************
169-
170-A feature flag maps from a *name* to a *value*. The specific value used
171-for a particular request is determined by a set of zero or more *scopes*
172-that apply to that request, by finding the *rule* with the highest
173-*priority*.
174-
175-Flags are defined by a *name* that typically looks like a Python
176-identifier, for example ``notification.global.text``. A definition is
177-given for a particular *scope*, which also looks like a dotted identifier,
178-for example ``user.beta`` or ``server.edge``. This is just a naming
179-convention, and they do not need to correspond to Python modules.
180-
181-The value is stored in the database as just a Unicode string, and it might
182-be interpreted as a boolean, number, human-readable string or whatever.
183-
184-The default for flags is to be None if they're not set in the database, so
185-that should be a sensible baseline default state.
186-
187-Performance model
188-*****************
189-
190-Flags are supposed to be cheap enough that you can introduce them without
191-causing a performance concern.
192-
193-If the page does not check any flags, no extra work will be done. The
194-first time a page checks a flag, all the rules will be read from the
195-database and held in memory for the duration of the request.
196-
197-Scopes may be expensive in some cases, such as checking group membership.
198-Whether a scope is active or not is looked up the first time it's needed
199-within a particular request.
200-
201-The standard page footer identifies the flags and scopes that were
202-actually used by the page.
203-
204-Naming conventions
205-******************
206-
207-We have naming conventions for feature flags and scopes, so that people can
208-understand the likely impact of a particular flag and so they can find all
209-the flags likely to affect a feature.
210-
211-So for any flag we want to say:
212-
213-* What application area does this affect? (malone, survey, questions,
214- code, etc)
215-
216-* What specific feature does it change?
217-
218-* What affect does it have on this feature? The most common is "enabled"
219- but for some other we want to specify a specific value as well such as
220- "date" or "size".
221-
222-These are concatenated with dots so the overall feature name looks a bit
223-like a Python module name.
224-
225-A similar approach is used for scopes.
226-
227-Checking flags in page templates
228-********************************
229-
230-You can conditionally show some text like this::
231-
232- <tal:survey condition="features/user_survey.enabled">
233- &nbsp;&bull;&nbsp;
234- <a href="http://survey.example.com/">Take our survey!</a>
235- </tal:survey>
236-
237-You can use the built-in TAL feature of prepending ``not:`` to the
238-condition, and for flags that have a value you could use them in
239-``tal:replace`` or ``tal:attributes``.
240-
241-If you just want to simply insert some text taken from a feature, say
242-something like::
243-
244- Message of the day: ${motd.text}
245-
246-Templates can also check whether the request is in a particular scope, but
247-before using this consider whether the code will always be bound to that
248-scope or whether it would be more correct to define a new feature::
249-
250- <p tal:condition="feature_scopes/server.staging">
251- Staging server: all data will be discarded daily!</p>
252-
253-Checking flags in code
254-**********************
255-
256-The Zope traversal code establishes a `FeatureController` for the duration
257-of a request. The object can be obtained through either
258-`request.features` or `lp.services.features.per_thread.features`. This
259-provides various useful methods including `getFlag` to look up one feature
260-(memoized), and `isInScope` to check one scope (also memoized).
261-
262-As a convenience, `lp.services.features.getFeatureFlag` looks up a single
263-flag in the thread default controller.
264-
265-Debugging feature usage
266-***********************
267-
268-The flags active during a page request, and the scopes that were looked
269-up are visible in the comment at the bottom of every standard Launchpad
270-page.
271-
272-Defining scopes
273-***************
274-
275-
276-.. vim: ft=rst
277
278=== modified file 'lib/lp/services/features/flags.py'
279--- lib/lp/services/features/flags.py 2010-08-10 04:35:41 +0000
280+++ lib/lp/services/features/flags.py 2010-09-21 06:46:51 +0000
281@@ -46,8 +46,10 @@
282 """A FeatureController tells application code what features are active.
283
284 It does this by meshing together two sources of data:
285- - feature flags, typically set by an administrator into the database
286- - feature scopes, which would typically be looked up based on attributes
287+
288+ - feature flags, typically set by an administrator into the database
289+
290+ - feature scopes, which would typically be looked up based on attributes
291 of the current web request, or the user for whom a job is being run, or
292 something similar.
293
294@@ -72,14 +74,14 @@
295 The controller is then supposed to be held in a thread-local and reused
296 for the duration of the request.
297
298- See <https://dev.launchpad.net/LEP/FeatureFlags>
299+ @see: U{https://dev.launchpad.net/LEP/FeatureFlags}
300 """
301
302 def __init__(self, scope_check_callback):
303 """Construct a new view of the features for a set of scopes.
304
305 :param scope_check_callback: Given a scope name, says whether
306- it's active or not.
307+ it's active or not.
308 """
309 self._known_scopes = Memoize(scope_check_callback)
310 self._known_flags = Memoize(self._checkFlag)
311@@ -91,8 +93,9 @@
312 """Get the value of a specific flag.
313
314 :param flag: A name to lookup. e.g. 'recipes.enabled'
315+
316 :return: The value of the flag determined by the highest priority rule
317- that matched.
318+ that matched.
319 """
320 return self._known_flags.lookup(flag)
321