From aeebebab912999b45926d3661a6b49dfcbfbce10 Mon Sep 17 00:00:00 2001 From: goanpeca Date: Sun, 26 Apr 2020 20:51:36 -0500 Subject: [PATCH] Subclass list --- colosseum/constants.py | 2 +- colosseum/declaration.py | 57 ++++++++------------------------------- colosseum/parser.py | 1 - colosseum/validators.py | 10 ++++--- colosseum/wrappers.py | 53 ++++++++++++++++++++++++------------ tests/test_declaration.py | 3 +-- tests/test_parser.py | 2 ++ 7 files changed, 58 insertions(+), 70 deletions(-) diff --git a/colosseum/constants.py b/colosseum/constants.py index 9e33b7c26..e5a45e5fc 100644 --- a/colosseum/constants.py +++ b/colosseum/constants.py @@ -1,6 +1,6 @@ from .validators import (ValidationError, is_border_spacing, is_color, is_cursor, is_integer, is_length, is_number, - is_percentage, is_quote, is_rect, is_uri) + is_percentage, is_quote, is_rect) class Choices: diff --git a/colosseum/declaration.py b/colosseum/declaration.py index 6aed676c2..40320cbb8 100644 --- a/colosseum/declaration.py +++ b/colosseum/declaration.py @@ -1,31 +1,30 @@ -from . import engine as css_engine, parser +from . import engine as css_engine +from . import parser from .constants import ( # noqa ALIGN_CONTENT_CHOICES, ALIGN_ITEMS_CHOICES, ALIGN_SELF_CHOICES, AUTO, BACKGROUND_COLOR_CHOICES, BORDER_COLLAPSE_CHOICES, BORDER_COLOR_CHOICES, BORDER_SPACING_CHOICES, BORDER_STYLE_CHOICES, BORDER_WIDTH_CHOICES, BOX_OFFSET_CHOICES, CAPTION_SIDE_CHOICES, CLEAR_CHOICES, CLIP_CHOICES, - COLOR_CHOICES, CURSOR_CHOICES, DIRECTION_CHOICES, DISPLAY_CHOICES, - EMPTY_CELLS_CHOICES, FLEX_BASIS_CHOICES, FLEX_DIRECTION_CHOICES, - FLEX_GROW_CHOICES, FLEX_SHRINK_CHOICES, FLEX_START, FLEX_WRAP_CHOICES, - FLOAT_CHOICES, GRID_AUTO_CHOICES, GRID_AUTO_FLOW_CHOICES, GRID_GAP_CHOICES, + COLOR_CHOICES, DIRECTION_CHOICES, DISPLAY_CHOICES, EMPTY_CELLS_CHOICES, + FLEX_BASIS_CHOICES, FLEX_DIRECTION_CHOICES, FLEX_GROW_CHOICES, + FLEX_SHRINK_CHOICES, FLEX_START, FLEX_WRAP_CHOICES, FLOAT_CHOICES, + GRID_AUTO_CHOICES, GRID_AUTO_FLOW_CHOICES, GRID_GAP_CHOICES, GRID_PLACEMENT_CHOICES, GRID_TEMPLATE_AREA_CHOICES, GRID_TEMPLATE_CHOICES, INITIAL, INLINE, INVERT, JUSTIFY_CONTENT_CHOICES, LETTER_SPACING_CHOICES, LTR, MARGIN_CHOICES, MAX_SIZE_CHOICES, MEDIUM, MIN_SIZE_CHOICES, NORMAL, NOWRAP, ORDER_CHOICES, ORPHANS_CHOICES, OUTLINE_COLOR_CHOICES, OUTLINE_STYLE_CHOICES, OUTLINE_WIDTH_CHOICES, OVERFLOW_CHOICES, PADDING_CHOICES, PAGE_BREAK_AFTER_CHOICES, PAGE_BREAK_BEFORE_CHOICES, - PAGE_BREAK_INSIDE_CHOICES, POSITION_CHOICES, QUOTES_CHOICES, ROW, SEPARATE, - SHOW, SIZE_CHOICES, STATIC, STRETCH, TABLE_LAYOUT_CHOICES, + PAGE_BREAK_INSIDE_CHOICES, POSITION_CHOICES, QUOTES_CHOICES, ROW, + SEPARATE, SHOW, SIZE_CHOICES, STATIC, STRETCH, TABLE_LAYOUT_CHOICES, TEXT_ALIGN_CHOICES, TEXT_DECORATION_CHOICES, TEXT_INDENT_CHOICES, TEXT_TRANSFORM_CHOICES, TOP, TRANSPARENT, UNICODE_BIDI_CHOICES, VISIBILITY_CHOICES, VISIBLE, WHITE_SPACE_CHOICES, WIDOWS_CHOICES, WORD_SPACING_CHOICES, Z_INDEX_CHOICES, OtherProperty, - TextAlignInitialValue, default, + TextAlignInitialValue, default, CURSOR_CHOICES, ) from .exceptions import ValidationError -from .wrappers import ( - Border, BorderBottom, BorderLeft, BorderRight, BorderTop, Outline, -) +from .wrappers import Border, BorderBottom, BorderLeft, BorderRight, BorderTop, Outline _CSS_PROPERTIES = set() @@ -72,40 +71,6 @@ def deleter(self): # Attribute doesn't exist pass - -def validated_list_property(name, choices, initial): - """Define a property holding a list values.""" - if not isinstance(initial, list): - raise ValueError('Initial value must be a list!') - - def getter(self): - return getattr(self, '_%s' % name, initial).copy() - - def setter(self, values): - if not isinstance(values, list): - raise ValueError('Value must be a list!') - - validated_values = [] - for value in values: - try: - validated_values.append(choices.validate(value)) - except ValueError: - raise ValueError("Invalid value '%s' for CSS property '%s'; Valid values are: %s" % ( - value, name, choices - )) - - if validated_values != getattr(self, '_%s' % name, initial): - setattr(self, '_%s' % name, validated_values) - self.dirty = True - - def deleter(self): - try: - delattr(self, '_%s' % name) - self.dirty = True - except AttributeError: - # Attribute doesn't exist - pass - _CSS_PROPERTIES.add(name) return property(getter, setter, deleter) @@ -146,7 +111,7 @@ def validated_property(name, choices, initial): # Check the value attribute is a callable if not callable(value_attr): - raise ValueError('Initial value "%s" attribute is not callable!' % initial) + raise ValueError('Initial value "%s" `value` attribute is not callable!' % initial) except AttributeError: raise ValueError('Initial value "%s" does not have a value attribute!' % initial) diff --git a/colosseum/parser.py b/colosseum/parser.py index 310450fd8..85b7acb8a 100644 --- a/colosseum/parser.py +++ b/colosseum/parser.py @@ -408,7 +408,6 @@ def uri(value): raise ValueError('Invalid url %s' % value) - ############################################################################## # Cursor ############################################################################## diff --git a/colosseum/validators.py b/colosseum/validators.py index d345bf6fe..7ba8ec978 100644 --- a/colosseum/validators.py +++ b/colosseum/validators.py @@ -143,6 +143,13 @@ def is_quote(value): value = parser.quotes(value) except ValueError: raise ValidationError('Value {value} is not a valid quote'.format(value=value)) + + return value + + +is_quote.description = '[ ]+' + + URI_RE = re.compile(r"""( (?:url\(\s?'[A-Za-z0-9\./\:\?]*'\s?\)) # Single quotes and optional spaces | @@ -152,9 +159,6 @@ def is_quote(value): )""", re.VERBOSE) -is_quote.description = '[ ]+' - - def is_uri(value): """Validate value is .""" try: diff --git a/colosseum/wrappers.py b/colosseum/wrappers.py index 7bbe2815a..05809ff1c 100644 --- a/colosseum/wrappers.py +++ b/colosseum/wrappers.py @@ -1,5 +1,4 @@ from collections import OrderedDict -from collections.abc import Sequence class BorderSpacing: @@ -154,7 +153,6 @@ class Border(Shorthand): VALID_KEYS = ['border_width', 'border_style', 'border_color'] - class Uri: """Wrapper for a url.""" @@ -172,46 +170,67 @@ def url(self): return self._url -class ImmutableList(Sequence): +class ImmutableList(list): """Immutable list to store list properties.""" def __init__(self, iterable=()): - self._data = tuple(iterable) + super().__init__(iterable) def _get_error_message(self, err): - return str(err).replace('tuple', self.__class__.__name__, 1) + return str(err).replace('list', self.__class__.__name__, 1) - def __eq__(self, other): - return other.__class__ == self.__class__ and self._data == other._data + # def __eq__(self, other): + # return other.__class__ == self.__class__ and self == other def __getitem__(self, index): try: - return self._data[index] + return super().__getitem__(index) except Exception as err: error_msg = self._get_error_message(err) raise err.__class__(error_msg) - def __len__(self): - return len(self._data) + def __setitem__(self, index, value): + raise TypeError("{} values cannot be changed!".format(self.__class__.__name__)) def __hash__(self): - return hash((self.__class__.__name__, self._data)) + return hash((self.__class__.__name__, tuple(self))) def __repr__(self): class_name = self.__class__.__name__ - if len(self._data) > 1: - text = '{class_name}([{data}])'.format(data=str(self._data)[1:-1], class_name=class_name) - elif len(self._data) == 1: - text = '{class_name}([{data}])'.format(data=str(self._data)[1:-2], class_name=class_name) + if len(self) != 0: + text = '{class_name}([{data}])'.format(data=repr(list(self))[1:-1], class_name=class_name) else: text = '{class_name}()'.format(class_name=class_name) + return text def __str__(self): - return ', '.join(str(v) for v in self._data) + return ', '.join(str(v) for v in self) def copy(self): - return self.__class__(self._data) + return self.__class__(self) + + # Disable mutating methods + def append(self, object): + raise TypeError("{} values cannot be changed!".format(self.__class__.__name__)) + + def extend(self, iterable): + raise TypeError("{} values cannot be changed!".format(self.__class__.__name__)) + + def insert(self, index, object): + raise TypeError("{} values cannot be changed!".format(self.__class__.__name__)) + + def pop(self, index=None): + raise TypeError("{} values cannot be changed!".format(self.__class__.__name__)) + + def remove(self, value): + raise TypeError("{} values cannot be changed!".format(self.__class__.__name__)) + + def reverse(self): + raise TypeError("{} values cannot be changed!".format(self.__class__.__name__)) + + def sort(self, cmp=None, key=None, reverse=False): + raise TypeError("{} values cannot be changed!".format(self.__class__.__name__)) class Cursor(ImmutableList): diff --git a/tests/test_declaration.py b/tests/test_declaration.py index 13f835094..d018b3ce3 100644 --- a/tests/test_declaration.py +++ b/tests/test_declaration.py @@ -5,8 +5,7 @@ from colosseum.constants import (AUTO, BLOCK, INHERIT, INITIAL, INLINE, LEFT, REVERT, RIGHT, RTL, TABLE, UNSET, Choices, OtherProperty) -from colosseum.declaration import (CSS, validated_list_property, - validated_property) +from colosseum.declaration import CSS, validated_property from colosseum.units import percent, px from colosseum.validators import (is_color, is_integer, is_length, is_number, is_percentage, is_uri) diff --git a/tests/test_parser.py b/tests/test_parser.py index d1947d3e4..941e4e511 100644 --- a/tests/test_parser.py +++ b/tests/test_parser.py @@ -681,6 +681,8 @@ def test_parse_border_shorthand_invalid_too_many_items(self): with self.assertRaises(ValueError): func('black solid thick black thick') + + class ParseUriTests(TestCase): def test_url_valid_single_quotes_url(self):