Skip to content

Commit

Permalink
Subclass list
Browse files Browse the repository at this point in the history
  • Loading branch information
goanpeca committed Apr 27, 2020
1 parent f5cf9a2 commit aeebeba
Show file tree
Hide file tree
Showing 7 changed files with 58 additions and 70 deletions.
2 changes: 1 addition & 1 deletion colosseum/constants.py
Original file line number Diff line number Diff line change
@@ -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:
Expand Down
57 changes: 11 additions & 46 deletions colosseum/declaration.py
Original file line number Diff line number Diff line change
@@ -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()

Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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)
Expand Down
1 change: 0 additions & 1 deletion colosseum/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -408,7 +408,6 @@ def uri(value):
raise ValueError('Invalid url %s' % value)



##############################################################################
# Cursor
##############################################################################
Expand Down
10 changes: 7 additions & 3 deletions colosseum/validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = '[<string> <string>]+'


URI_RE = re.compile(r"""(
(?:url\(\s?'[A-Za-z0-9\./\:\?]*'\s?\)) # Single quotes and optional spaces
|
Expand All @@ -152,9 +159,6 @@ def is_quote(value):
)""", re.VERBOSE)


is_quote.description = '[<string> <string>]+'


def is_uri(value):
"""Validate value is <uri>."""
try:
Expand Down
53 changes: 36 additions & 17 deletions colosseum/wrappers.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
from collections import OrderedDict
from collections.abc import Sequence


class BorderSpacing:
Expand Down Expand Up @@ -154,7 +153,6 @@ class Border(Shorthand):
VALID_KEYS = ['border_width', 'border_style', 'border_color']



class Uri:
"""Wrapper for a url."""

Expand All @@ -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):
Expand Down
3 changes: 1 addition & 2 deletions tests/test_declaration.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 2 additions & 0 deletions tests/test_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down

0 comments on commit aeebeba

Please sign in to comment.