Merge lp:~niemeyer/pyjuju/no-regex-option into lp:pyjuju

Proposed by Gustavo Niemeyer
Status: Merged
Approved by: Kapil Thangavelu
Approved revision: 337
Merged at revision: 337
Proposed branch: lp:~niemeyer/pyjuju/no-regex-option
Merge into: lp:pyjuju
Diff against target: 267 lines (+85/-62)
6 files modified
ensemble/formula/config.py (+31/-27)
ensemble/formula/tests/repository/dummy/config.yaml (+3/-3)
ensemble/formula/tests/repository/wordpress/config.yaml (+1/-1)
ensemble/formula/tests/test_config.py (+48/-29)
examples/php/config.yaml (+1/-1)
examples/wordpress/config.yaml (+1/-1)
To merge this branch: bzr merge lp:~niemeyer/pyjuju/no-regex-option
Reviewer Review Type Date Requested Status
Kapil Thangavelu (community) Approve
William Reade (community) Approve
Review via email: mp+73448@code.launchpad.net

Description of the change

This handles the two changes suggested in the mailing list:

- Drop regex type
- Rename 'str' to 'string'

For the second change, it also introduces backwards compatibility logic
so that we can continue to work with 'str' for the moment while we warn
authors to move out of it.

To post a comment you must log in.
lp:~niemeyer/pyjuju/no-regex-option updated
337. By Gustavo Niemeyer

Fixed examples.

Revision history for this message
William Reade (fwereade) wrote :

[0]

+ _warned_str_is_obsolete = False

Unused?

Otherwise +1.

review: Approve
Revision history for this message
Kapil Thangavelu (hazmat) wrote :

This is modifying a global variable when a deprecated usage is found, but multiple formulas may be utilized in a single command (deploy with auto-resolve against the ppa, or --with syntax for example), and in that case deprecated usage by additional formulas would go un-logged. perhaps keying to config path in a class dict, or just yagni for now

review: Needs Information
Revision history for this message
Gustavo Niemeyer (niemeyer) wrote :

Note that the message doesn't really specify which formula is using the obsoleted format. My idea with that is that people would review all of their formulas once they get the warning, while at the same time we can avoid pestering too much poor unrelated users that can't do anything about it.

Revision history for this message
Kapil Thangavelu (hazmat) wrote :

hmmm.. i almost think its worth changing the constructor to include the formula name for this case. Giving warnings on potentially third-party formulas isn't particularly useful without identifying the formula in question imo. The primary role i'm concerned about isn't the formula-author but the ensemble user consuming formulas. All that said, at this point those roles are a perfect intersection.

review: Approve
Revision history for this message
Gustavo Niemeyer (niemeyer) wrote :

I agree with all of your points, and also that it feels a bit early in the game to be worrying too much about this. I hope all formulas get fixed ASAP so that we can obsolete the option completely.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'ensemble/formula/config.py'
--- ensemble/formula/config.py 2011-08-24 00:18:27 +0000
+++ ensemble/formula/config.py 2011-08-30 21:40:24 +0000
@@ -1,5 +1,5 @@
1import sys
1import os2import os
2import re
33
4import yaml4import yaml
55
@@ -7,29 +7,24 @@
7 Constant, OneOf, Int, Float)7 Constant, OneOf, Int, Float)
8from ensemble.formula.errors import ServiceConfigError8from ensemble.formula.errors import ServiceConfigError
99
10# regex type requires validator10OPTION_SCHEMA = KeyDict({
11REGEX_TYPE_SCHEMA = KeyDict({"type": Constant("regex"),11 "type": OneOf(Constant("string"),
12 "validator": String(),12 Constant("str"), # Obsolete
13 "default": OneOf(String(), Int(), Float()),13 Constant("int"),
14 "description": String(),14 Constant("float")),
15 },15 "default": OneOf(String(), Int(), Float()),
16 optional=["default", "description"])16 "description": String(),
1717 },
18SIMPLE_TYPE_SCHEMA = KeyDict({"type": OneOf(Constant("str"),18 optional=["default", "description"],
19 Constant("int"),19 )
20 Constant("float")),
21 "default": OneOf(String(), Int(), Float()),
22 "description": String(),
23 },
24 optional=["default", "description"])
2520
26# Schema used to validate ConfigOptions specifications21# Schema used to validate ConfigOptions specifications
27CONFIG_SCHEMA = KeyDict({22CONFIG_SCHEMA = KeyDict({
28 "options": Dict(23 "options": Dict(String(), OPTION_SCHEMA),
29 String(),
30 OneOf(REGEX_TYPE_SCHEMA, SIMPLE_TYPE_SCHEMA))
31 })24 })
3225
26WARNED_STR_IS_OBSOLETE = False
27
3328
34class ConfigOptions(object):29class ConfigOptions(object):
35 """Represents the configuration options exposed by a formula.30 """Represents the configuration options exposed by a formula.
@@ -90,6 +85,8 @@
90 data = self.parse_serialization_data(raw_data, pathname)85 data = self.parse_serialization_data(raw_data, pathname)
91 self._data = data86 self._data = data
9287
88 _warned_str_is_obsolete = False
89
93 def parse_serialization_data(self, data, pathname=None):90 def parse_serialization_data(self, data, pathname=None):
94 """Verify we have sensible option metadata.91 """Verify we have sensible option metadata.
9592
@@ -108,11 +105,23 @@
108 raise ServiceConfigError("Invalid options specification: %s%s" % (105 raise ServiceConfigError("Invalid options specification: %s%s" % (
109 error, pathname))106 error, pathname))
110107
108 # XXX Drop this after everyone has migrated their config to 'string'.
109 global WARNED_STR_IS_OBSOLETE
110 if not WARNED_STR_IS_OBSOLETE:
111 for name, info in data["options"].iteritems():
112 for field, value in info.iteritems():
113 if field == "type" and value == "str":
114 sys.stderr.write(
115 "WARNING: Formula is using obsolete 'str' type "
116 "in config.yaml. Rename it to 'string'.\n")
117 WARNED_STR_IS_OBSOLETE = True
118 break
119
111 return data["options"]120 return data["options"]
112121
113 def _validate_one(self, name, value):122 def _validate_one(self, name, value):
114 # see if there is a type associated with the option123 # see if there is a type associated with the option
115 kind = self._data[name].get("type", "str")124 kind = self._data[name].get("type", "string")
116 if kind not in validation_kinds:125 if kind not in validation_kinds:
117 raise ServiceConfigError(126 raise ServiceConfigError(
118 "Unknown service option type: %s" % kind)127 "Unknown service option type: %s" % kind)
@@ -179,15 +188,10 @@
179 except ValueError:188 except ValueError:
180 return value, False189 return value, False
181190
182
183def validate_regex(value, options):
184 pattern = options["validator"]
185 return value, re.match(pattern, value)
186
187# maps service option types to callables191# maps service option types to callables
188validation_kinds = {192validation_kinds = {
189 "str": validate_str,193 "string": validate_str,
194 "str": validate_str, # Obsolete
190 "int": validate_int,195 "int": validate_int,
191 "float": validate_float,196 "float": validate_float,
192 "regex": validate_regex
193 }197 }
194198
=== modified file 'ensemble/formula/tests/repository/dummy/config.yaml'
--- ensemble/formula/tests/repository/dummy/config.yaml 2011-08-24 00:18:27 +0000
+++ ensemble/formula/tests/repository/dummy/config.yaml 2011-08-30 21:40:24 +0000
@@ -1,5 +1,5 @@
1options:1options:
2 title: {default: My Title, description: A descriptive title used for the service., type: str}2 title: {default: My Title, description: A descriptive title used for the service., type: string}
3 outlook: {description: No default outlook., type: str}3 outlook: {description: No default outlook., type: string}
4 username: {default: admin001, description: The name of the initial account (given admin permissions)., type: regex, validator: '\w{8}'}4 username: {default: admin001, description: The name of the initial account (given admin permissions)., type: string}
5 skill-level: {description: A number indicating skill., type: int}5 skill-level: {description: A number indicating skill., type: int}
66
=== modified file 'ensemble/formula/tests/repository/wordpress/config.yaml'
--- ensemble/formula/tests/repository/wordpress/config.yaml 2011-05-31 23:07:35 +0000
+++ ensemble/formula/tests/repository/wordpress/config.yaml 2011-08-30 21:40:24 +0000
@@ -1,3 +1,3 @@
1options:1options:
2 blog-title: {default: My Title, description: A descriptive title used for the blog., type: str}2 blog-title: {default: My Title, description: A descriptive title used for the blog., type: string}
33
44
=== modified file 'ensemble/formula/tests/test_config.py'
--- ensemble/formula/tests/test_config.py 2011-08-26 12:24:00 +0000
+++ ensemble/formula/tests/test_config.py 2011-08-30 21:40:24 +0000
@@ -1,16 +1,30 @@
1# -*- encoding: utf-8 -*-1# -*- encoding: utf-8 -*-
2from StringIO import StringIO
3import sys
4
2import yaml5import yaml
36
4from ensemble.lib.testing import TestCase7from ensemble.lib.testing import TestCase
5from ensemble.formula.config import ConfigOptions8from ensemble.formula.config import ConfigOptions
6from ensemble.formula.errors import ServiceConfigError9from ensemble.formula.errors import ServiceConfigError
10from ensemble.formula import config
711
8sample_configuration = """12sample_configuration = """
9options:13options:
10 title: {default: My Title, description: A descriptive title used for the service., type: str}14 title:
11 outlook: {description: No default outlook., type: str}15 default: My Title
12 username: {default: admin001, description: The name of the initial account (given admin permissions)., type: regex, validator: '\w{8}'}16 description: A descriptive title used for the service.
13 skill-level: {description: A number indicating skill., type: int}17 type: string
18 outlook:
19 description: No default outlook.
20 type: string
21 username:
22 default: admin001
23 description: The name of the initial account (given admin permissions).
24 type: string
25 skill-level:
26 description: A number indicating skill.
27 type: int
14"""28"""
1529
16sample_yaml_data = yaml.load(sample_configuration)30sample_yaml_data = yaml.load(sample_configuration)
@@ -79,8 +93,7 @@
79 self.config.parse(sample_configuration)93 self.config.parse(sample_configuration)
80 data = self.config.validate(sample_input)94 data = self.config.validate(sample_input)
8195
82 # This should include an overridden value, a default and a new96 # This should include an overridden value, a default and a new value.
83 # value.
84 self.assertEqual(data,97 self.assertEqual(data,
85 {"username": "admin001",98 {"username": "admin001",
86 "outlook": "Peachy",99 "outlook": "Peachy",
@@ -88,7 +101,8 @@
88101
89 # now try to set a value outside the expected102 # now try to set a value outside the expected
90 sample_input["bad"] = "value"103 sample_input["bad"] = "value"
91 error = self.assertRaises(ServiceConfigError, self.config.validate, sample_input)104 error = self.assertRaises(ServiceConfigError,
105 self.config.validate, sample_input)
92 self.assertEqual(error.message,106 self.assertEqual(error.message,
93 "bad is not a valid configuration option.")107 "bad is not a valid configuration option.")
94108
@@ -97,28 +111,6 @@
97 config = ConfigOptions()111 config = ConfigOptions()
98 self.assertRaises(ServiceConfigError, config.validate, sample_input)112 self.assertRaises(ServiceConfigError, config.validate, sample_input)
99113
100 def test_validate_regex(self):
101 self.config.parse(sample_configuration)
102
103 error = self.assertRaises(ServiceConfigError,
104 self.config.validate, dict(username="1234"))
105 self.assertEquals(str(error), "Invalid value for username: 1234")
106
107 data = self.config.validate(dict(username="12345678"))
108 self.assertEqual(data, {"username": "12345678", "title": "My Title"})
109
110 def test_validate_regex_wo_validator(self):
111 """Assure that validator is required for type=regex."""
112 local_configuration = sample_configuration + \
113 """ fails: {description: missing required., type: regex}"""
114 error = self.assertRaises(ServiceConfigError,
115 self.config.parse,
116 local_configuration)
117 self.assertEquals(
118 str(error),
119 "Invalid options specification: options.fails.validator: "
120 "required value not found")
121
122 def test_validate_types(self):114 def test_validate_types(self):
123 self.config.parse(sample_configuration)115 self.config.parse(sample_configuration)
124116
@@ -131,3 +123,30 @@
131 self.assertEqual(data, {"skill-level": 9001,123 self.assertEqual(data, {"skill-level": 9001,
132 "title": "My Title",124 "title": "My Title",
133 "username": "admin001"})125 "username": "admin001"})
126
127 def test_validate_with_obsolete_str(self):
128 """
129 Test the handling for the obsolete 'str' option type (it's
130 'string' now). Remove support for it after a while, and take
131 this test with it.
132 """
133 config = yaml.load(sample_configuration)
134 config["options"]["title"]["type"] = "str"
135 obsolete_config = yaml.dump(config)
136
137 sio = StringIO()
138 self.patch(sys, "stderr", sio)
139
140 self.config.parse(obsolete_config)
141 data = self.config.validate({"title": "Helpful Title"})
142 self.assertEqual(data["title"], "Helpful Title")
143 self.assertIn("obsolete 'str'", sio.getvalue())
144
145 # Trying it again, it should not warn since we don't want
146 # to pester the formula author.
147 sio.truncate(0)
148 self.config.parse(obsolete_config)
149 data = self.config.validate({"title": "Helpful Title"})
150 self.assertEqual(data["title"], "Helpful Title")
151 self.assertEqual(sio.getvalue(), "")
152
134153
=== modified file 'examples/php/config.yaml'
--- examples/php/config.yaml 2011-07-01 11:40:20 +0000
+++ examples/php/config.yaml 2011-08-30 21:40:24 +0000
@@ -1,5 +1,5 @@
1options:1options:
2 application_file:2 application_file:
3 description: An application file to push.3 description: An application file to push.
4 type: str4 type: string
55
66
=== modified file 'examples/wordpress/config.yaml'
--- examples/wordpress/config.yaml 2011-06-13 20:33:35 +0000
+++ examples/wordpress/config.yaml 2011-08-30 21:40:24 +0000
@@ -3,5 +3,5 @@
3 description: |3 description: |
4 The URL of a wordpress plugin which will be installed and made available in the Admin UI.4 The URL of a wordpress plugin which will be installed and made available in the Admin UI.
5 http://downloads.wordpress.org/plugin/buddypress.1.2.8.zip is a valid example.5 http://downloads.wordpress.org/plugin/buddypress.1.2.8.zip is a valid example.
6 type: str6 type: string
77

Subscribers

People subscribed via source and target branches

to status/vote changes: