def test_frozen(): """ FrozenDict is not modifyable """ f = FrozenDict(a=1) with pytest.raises(ValueError): f['b'] = 5
def __init__( self, tag_name, attributes=None, style=None, children=None, key=None, event_handlers=None, schema=None, ): if isinstance(tag_name, dict) or isinstance(tag_name, list): # Backwards compatible interface warnings.warn('Passing dict to VDOM constructor is deprecated') value = tag_name vdom_obj = VDOM.from_dict(value) tag_name = vdom_obj.tag_name style = vdom_obj.style event_handlers = vdom_obj.event_handlers attributes = vdom_obj.attributes children = vdom_obj.children key = vdom_obj.key self.tag_name = tag_name # Sort attributes so our outputs are predictable self.attributes = FrozenDict(sorted( attributes.items())) if attributes else FrozenDict() self.children = tuple(children) if children else tuple() self.key = key # Sort attributes so our outputs are predictable self.style = FrozenDict(sorted( style.items())) if style else FrozenDict() self.event_handlers = (FrozenDict(sorted(event_handlers.items())) if event_handlers else FrozenDict()) # Validate that all children are VDOMs or strings if not all( isinstance(c, (VDOM, string_types[:])) for c in self.children): raise ValueError( 'Children must be a list of VDOM objects or strings') # All style keys & values must be strings if not all( isinstance(k, string_types) and isinstance(v, string_types) for k, v in self.style.items()): raise ValueError('Style must be a dict with string keys & values') # mark completion of object creation. Object is immutable from now. self._frozen = True if schema is not None: self.validate(schema)
def __init__(self, tag_name, attributes=None, children=None, key=None, schema=None): if isinstance(tag_name, dict) or isinstance(tag_name, list): # Backwards compatible interface warnings.warn('Passing dict to VDOM constructor is deprecated') value = tag_name vdom_obj = VDOM.from_dict(value) tag_name = vdom_obj.tag_name attributes = vdom_obj.attributes children = vdom_obj.children key = vdom_obj.key self.tag_name = tag_name self.attributes = FrozenDict(attributes) if attributes else FrozenDict() self.children = tuple(children) if children else tuple() self.key = key # Validate that all children are VDOMs or strings if not all([isinstance(c, (VDOM, string_types[:])) for c in self.children]): raise ValueError('Children must be a list of VDOM objects or strings') # mark completion of object creation. Object is immutable from now. self._frozen = True if schema is not None: self.validate(schema)
class VDOM(object): """A basic virtual DOM class which allows you to write literal VDOM spec >>> VDOM(tag_name='h1', children='Hey', attributes: {}}) >>> from vdom.helpers import h1 >>> h1('Hey') """ # This class should only have these 5 attributes __slots__ = [ 'tag_name', 'attributes', 'style', 'children', 'key', 'event_handlers', '_frozen' ] def __init__( self, tag_name, attributes=None, style=None, children=None, key=None, event_handlers=None, schema=None, ): if isinstance(tag_name, dict) or isinstance(tag_name, list): # Backwards compatible interface warnings.warn('Passing dict to VDOM constructor is deprecated') value = tag_name vdom_obj = VDOM.from_dict(value) tag_name = vdom_obj.tag_name style = vdom_obj.style event_handlers = vdom_obj.event_handlers attributes = vdom_obj.attributes children = vdom_obj.children key = vdom_obj.key self.tag_name = tag_name # Sort attributes so our outputs are predictable self.attributes = FrozenDict(sorted( attributes.items())) if attributes else FrozenDict() self.children = tuple(children) if children else tuple() self.key = key # Sort attributes so our outputs are predictable self.style = FrozenDict(sorted( style.items())) if style else FrozenDict() self.event_handlers = (FrozenDict(sorted(event_handlers.items())) if event_handlers else FrozenDict()) # Validate that all children are VDOMs or strings if not all( isinstance(c, (VDOM, string_types[:])) for c in self.children): raise ValueError( 'Children must be a list of VDOM objects or strings') # All style keys & values must be strings if not all( isinstance(k, string_types) and isinstance(v, string_types) for k, v in self.style.items()): raise ValueError('Style must be a dict with string keys & values') # mark completion of object creation. Object is immutable from now. self._frozen = True if schema is not None: self.validate(schema) def __setattr__(self, attr, value): """ Make instances immutable after creation """ if hasattr(self, '_frozen') and self._frozen: raise AttributeError("Cannot change attribute of immutable object") super(VDOM, self).__setattr__(attr, value) def validate(self, schema): """ Validate VDOM against given JSON Schema Raises ValidationError if schema does not match """ try: validate(instance=self.to_dict(), schema=schema, cls=Draft4Validator) except ValidationError as e: raise ValidationError(_validate_err_template.format( VDOM_SCHEMA, e)) def to_dict(self): """Converts VDOM object to a dictionary that passes our schema """ attributes = dict(self.attributes.items()) if self.style: attributes.update({"style": dict(self.style.items())}) vdom_dict = {'tagName': self.tag_name, 'attributes': attributes} if self.event_handlers: event_handlers = dict(self.event_handlers.items()) for key, value in event_handlers.items(): value = create_event_handler(key, value) event_handlers[key] = value vdom_dict['eventHandlers'] = event_handlers if self.key: vdom_dict['key'] = self.key vdom_dict['children'] = [ c.to_dict() if isinstance(c, VDOM) else c for c in self.children ] return vdom_dict def to_json(self): return json.dumps(self.to_dict()) def to_html(self): return self._repr_html_() def json_contents(self): warnings.warn( 'VDOM.json_contents method is deprecated, use to_json instead') return self.to_json() def _to_inline_css(self, style): """ Return inline CSS from CSS key / values """ return "; ".join([ '{}: {}'.format(convert_style_key(k), v) for k, v in style.items() ]) def _repr_html_(self): """ Return HTML representation of VDOM object. HTML escaping is performed wherever necessary. """ # Use StringIO to avoid a large number of memory allocations with string concat with io.StringIO() as out: out.write('<{tag}'.format(tag=escape(self.tag_name))) if self.style: # Important values are in double quotes - cgi.escape only escapes double quotes, not single quotes! out.write(' style="{css}"'.format( css=escape(self._to_inline_css(self.style)))) for k, v in self.attributes.items(): # Important values are in double quotes - cgi.escape only escapes double quotes, not single quotes! if isinstance(v, string_types): out.write(' {key}="{value}"'.format(key=escape(k), value=escape(v))) if isinstance(v, bool) and v: out.write(' {key}'.format(key=escape(k))) out.write('>') for c in self.children: if isinstance(c, string_types): out.write(escape(safe_unicode(c))) else: out.write(c._repr_html_()) out.write('</{tag}>'.format(tag=escape(self.tag_name))) return out.getvalue() def _repr_mimebundle_(self, include=None, exclude=None, **kwargs): return { 'application/vdom.v1+json': self.to_dict(), 'text/plain': self.to_html() } @classmethod def from_dict(cls, value): try: validate(instance=value, schema=VDOM_SCHEMA, cls=Draft4Validator) except ValidationError as e: raise ValidationError(_validate_err_template.format( VDOM_SCHEMA, e)) attributes = value.get('attributes', {}) style = None event_handlers = None if 'style' in attributes: # Make a copy of attributes, since we're gonna remove styles from it attributes = attributes.copy() style = attributes.pop('style') for key, val in attributes.items(): if callable(val): attributes = attributes.copy() if event_handlers == None: event_handlers = {key: attributes.pop(key)} else: event_handlers[key] = attributes.pop(key) return cls( tag_name=value['tagName'], attributes=attributes, style=style, event_handlers=event_handlers, children=[ VDOM.from_dict(c) if isinstance(c, dict) else c for c in value.get('children', []) ], key=value.get('key'), )
def test_ordering(): """ FrozenDict should preserve ordering """ f = FrozenDict([('a', 1), ('b', 2)]) assert list(f.items()) == [('a', 1), ('b', 2)]
class VDOM(object): """A basic virtual DOM class which allows you to write literal VDOM spec >>> VDOM(tag_name='h1', children='Hey', attributes: {}}) >>> from vdom.helpers import h1 >>> h1('Hey') """ # This class should only have these 4 attributes __slots__ = ['tag_name', 'attributes', 'children', 'key', '_frozen'] def __init__(self, tag_name, attributes=None, children=None, key=None, schema=None): if isinstance(tag_name, dict) or isinstance(tag_name, list): # Backwards compatible interface warnings.warn('Passing dict to VDOM constructor is deprecated') value = tag_name vdom_obj = VDOM.from_dict(value) tag_name = vdom_obj.tag_name attributes = vdom_obj.attributes children = vdom_obj.children key = vdom_obj.key self.tag_name = tag_name self.attributes = FrozenDict(attributes) if attributes else FrozenDict() self.children = tuple(children) if children else tuple() self.key = key # Validate that all children are VDOMs or strings if not all([isinstance(c, (VDOM, string_types[:])) for c in self.children]): raise ValueError('Children must be a list of VDOM objects or strings') # mark completion of object creation. Object is immutable from now. self._frozen = True if schema is not None: self.validate(schema) def __setattr__(self, attr, value): """ Make instances immutable after creation """ if hasattr(self, '_frozen') and self._frozen: raise AttributeError("Cannot change attribute of immutable object") super(VDOM, self).__setattr__(attr, value) def validate(self, schema): """ Validate VDOM against given JSON Schema Raises ValidationError if schema does not match """ try: validate(instance=self.to_dict(), schema=schema, cls=Draft4Validator) except ValidationError as e: raise ValidationError(_validate_err_template.format(VDOM_SCHEMA, e)) def to_dict(self): vdom_dict = { 'tagName': self.tag_name, 'attributes': self.attributes } if self.key: vdom_dict['key'] = self.key vdom_dict['children'] = [c.to_dict() if isinstance(c, VDOM) else c for c in self.children] return vdom_dict def to_json(self): return json.dumps(self.to_dict()) def to_html(self): return self._repr_html_() def json_contents(self): warnings.warn('VDOM.json_contents method is deprecated, use to_json instead') return self.to_json() def _repr_html_(self): """ Return HTML representation of VDOM object. HTML escaping is performed wherever necessary. """ # Use StringIO to avoid a large number of memory allocations with string concat with io.StringIO() as out: out.write('<{tag}'.format(tag=escape(self.tag_name))) for k, v in self.attributes.items(): # Important values are in double quotes - cgi.escape only escapes double quotes, not single quotes! out.write(' {key}="{value}"'.format(key=escape(k), value=escape(v))) out.write('>') for c in self.children: if isinstance(c, string_types): out.write(escape(safe_unicode(c))) else: out.write(c._repr_html_()) out.write('</{tag}>'.format(tag=escape(self.tag_name))) return out.getvalue() def _repr_mimebundle_(self, include, exclude, **kwargs): return { 'application/vdom.v1+json': self.to_dict(), 'text/plain': self.to_html() } @classmethod def from_dict(cls, value): try: validate(instance=value, schema=VDOM_SCHEMA, cls=Draft4Validator) except ValidationError as e: raise ValidationError(_validate_err_template.format(VDOM_SCHEMA, e)) return cls( tag_name=value['tagName'], attributes=value.get('attributes', {}), children=[VDOM.from_dict(c) if isinstance(c, dict) else c for c in value.get('children', [])], key=value.get('key') )