Improve is_valid_attribute_name utility function

- don't allow python keywords;
- use ascii symbols only, no national letters or digits;
- code cleanup.

Change-Id: Ibf863edb6fd06257df05ca3c490f87f1c097144f
This commit is contained in:
Ivan A. Melnikov
2013-11-22 15:12:27 +04:00
parent 63f89bc916
commit 4400723399
2 changed files with 52 additions and 29 deletions

View File

@@ -260,6 +260,37 @@ class AttrDictTest(test.TestCase):
self.assertEqual('c', obj['_b'])
class IsValidAttributeNameTestCase(test.TestCase):
def test_a_is_ok(self):
self.assertTrue(misc.is_valid_attribute_name('a'))
def test_name_can_be_longer(self):
self.assertTrue(misc.is_valid_attribute_name('foobarbaz'))
def test_name_can_have_digits(self):
self.assertTrue(misc.is_valid_attribute_name('fo12'))
def test_name_cannot_start_with_digit(self):
self.assertFalse(misc.is_valid_attribute_name('1z'))
def test_hidden_names_are_forbidden(self):
self.assertFalse(misc.is_valid_attribute_name('_z'))
def test_hidden_names_can_be_allowed(self):
self.assertTrue(
misc.is_valid_attribute_name('_z', allow_hidden=True))
def test_self_is_forbidden(self):
self.assertFalse(misc.is_valid_attribute_name('self'))
def test_self_can_be_allowed(self):
self.assertTrue(
misc.is_valid_attribute_name('self', allow_self=True))
def test_no_unicode_please(self):
self.assertFalse(misc.is_valid_attribute_name('mañana'))
class ExcInfoUtilsTest(test.TestCase):
def _make_ex_info(self):

View File

@@ -21,9 +21,11 @@ import collections
import copy
import errno
import functools
import keyword
import logging
import os
import six
import string
import sys
import traceback
@@ -32,7 +34,7 @@ from taskflow.utils import reflection
LOG = logging.getLogger(__name__)
NUMERIC_TYPES = tuple(list(six.integer_types) + [float])
NUMERIC_TYPES = six.integer_types + (float,)
def wraps(fn):
@@ -76,36 +78,26 @@ def get_duplicate_keys(iterable, key=None):
return duplicates
# NOTE(imelnikov): we should not use str.isalpha or str.isdigit
# as they are locale-dependant
_ASCII_WORD_SYMBOLS = frozenset(string.ascii_letters + string.digits + '_')
def is_valid_attribute_name(name, allow_self=False, allow_hidden=False):
"""Validates that a string name is a valid/invalid python attribute name"""
if not isinstance(name, six.string_types) or len(name) == 0:
return False
# Make the name just be a simple string in latin-1 encoding in python3
name = six.b(name)
if not allow_self and name.lower().startswith(six.b('self')):
return False
if not allow_hidden and name.startswith(six.b("_")):
return False
# See: http://docs.python.org/release/2.5.2/ref/grammar.txt (or newer)
#
# Python identifiers should start with a letter.
if isinstance(name[0], six.integer_types):
if not chr(name[0]).isalpha():
return False
else:
if not name[0].isalpha():
return False
for i in range(1, len(name)):
symbol = name[i]
# The rest of an attribute name follows: (letter | digit | "_")*
if isinstance(symbol, six.integer_types):
symbol = chr(symbol)
if not (symbol.isalpha() or symbol.isdigit() or symbol == "_"):
return False
else:
if not (symbol.isalpha() or symbol.isdigit() or symbol == "_"):
return False
return True
return all((
isinstance(name, six.string_types),
len(name) > 0,
(allow_self or not name.lower().startswith('self')),
(allow_hidden or not name.lower().startswith('_')),
# NOTE(imelnikov): keywords should be forbidden
not keyword.iskeyword(name),
# See: http://docs.python.org/release/2.5.2/ref/grammar.txt
not (name[0] in string.digits),
all(symbol in _ASCII_WORD_SYMBOLS for symbol in name)
))
class AttrDict(dict):