Chromium Code Reviews| Index: appengine/components/components/config/validation.py |
| diff --git a/appengine/components/components/config/validation.py b/appengine/components/components/config/validation.py |
| index f51d551434a9dbded11c0329ab9047f8d2a1ead0..ee55758354344d4bda4838fde144c6a6c4955ae9 100644 |
| --- a/appengine/components/components/config/validation.py |
| +++ b/appengine/components/components/config/validation.py |
| @@ -26,6 +26,7 @@ Example with custom validation: |
| # Will raise ValueError |
| """ |
| +import collections |
| import fnmatch |
| import functools |
| import re |
| @@ -87,6 +88,14 @@ def is_valid_service_id(service_id): |
| return bool(common.SERVICE_ID_RGX.match(service_id)) |
| +ConfigPattern = collections.namedtuple( |
| + 'ConfigPattern', |
| + [ |
| + 'config_set', # config_set pattern, see compile_pattern(). |
| + 'path', # path pattern, see compile_pattern(). |
| + ]) |
| + |
| + |
| def rule(config_set, path, dest_type=None, rule_set=None): |
| """Creates a validation rule, that can act as a decorator. |
| @@ -109,8 +118,8 @@ def rule(config_set, path, dest_type=None, rule_set=None): |
| ctx.error('bar cannot be negative: %d', cfg.bar) |
| |config_set| and |path| are patterns that determine if a rule is applicable |
| - to a config. A pattern is an exact string or a function. Both |
| - config-set and path patterns must match. |
| + to a config. Both |config_set| and |path| patterns must match. See |
| + compile_pattern's docstring for the definition of "pattern". |
| Args: |
| config_set (str or function): pattern for config set. |
| @@ -133,12 +142,16 @@ def rule(config_set, path, dest_type=None, rule_set=None): |
| def project_config_rule(*args, **kwargs): |
| """Shortcut for rule() for project configs.""" |
| - return rule(common.PROJECT_CONFIG_SET_RGX.match, *args, **kwargs) |
| + return rule( |
| + 'regex:%s' % common.PROJECT_CONFIG_SET_RGX.pattern, |
| + *args, **kwargs) |
| def ref_config_rule(*args, **kwargs): |
| """Shortcut for rule() for ref configs.""" |
| - return rule(common.REF_CONFIG_SET_RGX.match, *args, **kwargs) |
| + return rule( |
| + 'regex:%s' % common.REF_CONFIG_SET_RGX.pattern, |
| + *args, **kwargs) |
| def self_rule(*args, **kwargs): |
| @@ -177,8 +190,10 @@ class Rule(object): |
| rule_set = None |
| def __init__(self, config_set, path, dest_type=None): |
| - self.config_set_fn = _match_fn(config_set) |
| - self.path_fn = _match_fn(path) |
| + self.config_set = config_set |
| + self.path = path |
| + self.config_set_fn = compile_pattern(config_set) |
| + self.path_fn = compile_pattern(path) |
| common._validate_dest_type(dest_type) |
| self.dest_type = dest_type |
| self.validator_funcs = [] |
| @@ -236,12 +251,57 @@ class RuleSet(object): |
| def is_defined_for(self, config_set, path): |
| return any(r.match(config_set, path) for r in self.rules) |
| + def patterns(self): |
| + """Returns a set of all config patterns that this rule_set can validate. |
| + |
| + Returns: |
| + A set of ConfigPattern objects. |
| + """ |
| + return set( |
| + ConfigPattern(config_set=r.config_set, path=r.path) |
| + for r in self.rules |
| + ) |
| + |
| + |
| +def compile_pattern(pattern): |
| + """Compiles a pattern to a predicate function. |
| + |
| + A pattern is a "<kind>:<value>" pair, where kind can be "text" (default) or |
| + "regex" and value interpretation depends on the kind: |
| + regex: value must be a regular expression. If it does not start/end with |
| + "^"/"$", they are added automatically. |
|
Sergiy Byelozyorov
2015/07/07 09:10:47
please replace "^"/"$" with just ^/$, otherwise it
nodir
2015/07/07 15:54:17
Done.
|
| + text: exact string. |
| + If colon is not present in the pattern, it is treated as "text:<pattern>". |
| + |
| + Returns: |
| + func (s: string): bool |
| + |
| + Raises: |
| + ValueError if |pattern| is malformed. |
| + """ |
| + if not isinstance(pattern, basestring): |
| + raise ValueError('Pattern must be a string') |
| + if ':' in pattern: |
| + kind, value = pattern.split(':', 2) |
| + else: |
| + kind = 'text' |
| + value = pattern |
| + |
| + if kind == 'text': |
| + return lambda s: s == value |
| + |
| + if kind == 'regex': |
| + if not value.startswith('^'): |
| + value = '^' + value |
| + if not value.endswith('$'): |
| + value = value + '$' |
| + try: |
| + regex = re.compile(value) |
| + except re.error as ex: |
| + raise ValueError(ex.message) |
| + return regex.match |
| -def _match_fn(pattern): |
| - if isinstance(pattern, basestring): |
| - return lambda s: s == pattern |
| - assert hasattr(pattern, '__call__'), '%s is not a function' % pattern |
| - return pattern |
| + raise ValueError('Invalid pattern kind: %s' % kind) |
| DEFAULT_RULE_SET = RuleSet() |