From 104387cc4a5504b6fdf9dcc9d6d7a01f7f316d41 Mon Sep 17 00:00:00 2001 From: Victor Varvaryuk Date: Tue, 19 Mar 2013 12:35:00 +0400 Subject: [PATCH] CaselessDict keys are now automatically converted to unicode. Test fixes. Small refactoring and polishing. --- src/icalendar/__init__.py | 24 +++++- src/icalendar/cal.py | 13 ++- src/icalendar/caselessdict.py | 39 +++++---- src/icalendar/parser.py | 96 +++++++-------------- src/icalendar/prop.py | 40 ++++----- src/icalendar/tests/test_cal.py | 23 +++-- src/icalendar/tests/test_icalendar.py | 12 +-- src/icalendar/tests/test_parser.py | 21 ++--- src/icalendar/tests/test_prop.py | 53 ++++++------ src/icalendar/tests/test_property_params.py | 24 +++--- src/icalendar/tools.py | 3 +- 11 files changed, 164 insertions(+), 184 deletions(-) diff --git a/src/icalendar/__init__.py b/src/icalendar/__init__.py index 2ac61e4..a9709d4 100644 --- a/src/icalendar/__init__.py +++ b/src/icalendar/__init__.py @@ -1,7 +1,23 @@ +from __future__ import absolute_import + SEQUENCE_TYPES = (list, tuple) DEFAULT_ENCODING = 'utf-8' -from icalendar.cal import ( + +def to_unicode(value, encoding='utf-8'): + """Converts a value to unicode, even if it is already a unicode string. + """ + if isinstance(value, unicode): + return value + elif isinstance(value, str): + try: + return value.decode(encoding) + except UnicodeDecodeError: + return value.decode('utf-8', 'replace') + raise AssertionError('A str/unicode expected.') + + +from .cal import ( Calendar, Event, Todo, @@ -14,7 +30,7 @@ from icalendar.cal import ( ComponentFactory, ) # Property Data Value Types -from icalendar.prop import ( +from .prop import ( vBinary, vBoolean, vCalAddress, @@ -36,13 +52,13 @@ from icalendar.prop import ( TypesFactory, ) # useful tzinfo subclasses -from icalendar.prop import ( +from .prop import ( FixedOffset, LocalTimezone, ) # Parameters and helper methods for splitting and joining string with escaped # chars. -from icalendar.parser import ( +from .parser import ( Parameters, q_split, q_join, diff --git a/src/icalendar/cal.py b/src/icalendar/cal.py index 1cda7c2..f11f71a 100644 --- a/src/icalendar/cal.py +++ b/src/icalendar/cal.py @@ -5,17 +5,18 @@ files according to rfc2445. These are the defined components. """ +from __future__ import absolute_import import pytz from datetime import datetime -from icalendar.caselessdict import CaselessDict -from icalendar.parser import ( +from .caselessdict import CaselessDict +from .parser import ( Contentlines, Contentline, Parameters, q_split, q_join, ) -from icalendar.prop import ( +from .prop import ( TypesFactory, vText, ) @@ -172,8 +173,7 @@ class Component(CaselessDict): """Returns a list of values (split on comma). """ - vals = [v.strip('" ').encode(vText.encoding) - for v in q_split(self[name])] + vals = [v.strip('" ') for v in q_split(self[name])] if decode: return [self._decode(name, val) for val in vals] return vals @@ -185,8 +185,7 @@ class Component(CaselessDict): """ if encode: values = [self._encode(name, value, 1) for value in values] - joined = q_join(values).encode(vText.encoding) - self[name] = types_factory['inline'](joined) + self[name] = types_factory['inline'](q_join(values)) ######################### # Handling of components diff --git a/src/icalendar/caselessdict.py b/src/icalendar/caselessdict.py index 040df22..1650d71 100644 --- a/src/icalendar/caselessdict.py +++ b/src/icalendar/caselessdict.py @@ -1,5 +1,7 @@ # -*- coding: utf-8 -*- -from icalendar import DEFAULT_ENCODING +from __future__ import absolute_import +from . import DEFAULT_ENCODING, to_unicode + def canonsort_keys(keys, canonical_order=None): """Sorts leading keys according to canonical_order. Keys not specified in @@ -27,61 +29,62 @@ class CaselessDict(dict): """ def __init__(self, *args, **kwargs): - "Set keys to upper for initial dict" + """Set keys to upper for initial dict. + """ dict.__init__(self, *args, **kwargs) - for k, v in self.items(): - k_upper = k.upper() - if k != k_upper: - dict.__delitem__(self, k) - self[k_upper] = v + for key, value in self.items(): + key_upper = to_unicode(key).upper() + if key != key_upper: + dict.__delitem__(self, key) + self[key_upper] = value def __getitem__(self, key): + key = to_unicode(key) return dict.__getitem__(self, key.upper()) def __setitem__(self, key, value): + key = to_unicode(key) dict.__setitem__(self, key.upper(), value) def __delitem__(self, key): + key = to_unicode(key) dict.__delitem__(self, key.upper()) def __contains__(self, key): + key = to_unicode(key) return dict.__contains__(self, key.upper()) def get(self, key, default=None): + key = to_unicode(key) return dict.get(self, key.upper(), default) def setdefault(self, key, value=None): + key = to_unicode(key) return dict.setdefault(self, key.upper(), value) def pop(self, key, default=None): + key = to_unicode(key) return dict.pop(self, key.upper(), default) def popitem(self): return dict.popitem(self) def has_key(self, key): + key = to_unicode(key) return dict.__contains__(self, key.upper()) def update(self, indict): """ Multiple keys where key1.upper() == key2.upper() will be lost. """ - for entry in indict: - self[entry] = indict[entry] + for key, value in indict.iteritems(): + self[key] = value def copy(self): return CaselessDict(dict.copy(self)) - def clear(self): - dict.clear(self) - def __repr__(self): - # TODO: not necessary when to_ical also outs unicode - dict_repr = dict(map( - lambda item: (item[0].encode(DEFAULT_ENCODING), item[1]), - self.iteritems() - )) - return 'CaselessDict(%s)' % dict_repr + return 'CaselessDict(%s)' % self # A list of keys that must appear first in sorted_keys and sorted_items; # must be uppercase. diff --git a/src/icalendar/parser.py b/src/icalendar/parser.py index ce88def..3495d2d 100644 --- a/src/icalendar/parser.py +++ b/src/icalendar/parser.py @@ -6,30 +6,17 @@ Eg. RFC 2426 (vCard) It is stupid in the sense that it treats the content purely as strings. No type conversion is attempted. """ +from __future__ import absolute_import import re -from icalendar import DEFAULT_ENCODING -from icalendar import SEQUENCE_TYPES -from icalendar.caselessdict import CaselessDict - - -def safe_unicode(value, encoding='utf-8'): - """Converts a value to unicode, even if it is already a unicode string. - - Taken from from Products.CMFPlone.utils. - """ - if isinstance(value, unicode): - return value - elif isinstance(value, basestring): - try: - value = unicode(value, encoding) - except (UnicodeDecodeError): - value = value.decode('utf-8', 'replace') - return value +from . import DEFAULT_ENCODING, SEQUENCE_TYPES, to_unicode +from .caselessdict import CaselessDict def escape_char(text): """Format value according to iCalendar TEXT escaping rules. """ + assert isinstance(text, basestring) + # TODO: optimize this # NOTE: ORDER MATTERS! return text.replace('\N', '\n')\ .replace('\\', '\\\\')\ @@ -40,6 +27,8 @@ def escape_char(text): def unescape_char(text): + assert isinstance(text, basestring) + # TODO: optimize this # NOTE: ORDER MATTERS! return text.replace(r'\N', r'\n')\ .replace(r'\r\n', '\n')\ @@ -114,12 +103,12 @@ def foldline(text, length=75, newline='\r\n'): ################################################################# # Property parameter stuff -def paramVal(val): +def param_value(value): """Returns a parameter value. """ - if type(val) in SEQUENCE_TYPES: - return q_join(val) - return dQuote(val) + if isinstance(value, SEQUENCE_TYPES): + return q_join(value) + return dquote(value) # Could be improved @@ -148,8 +137,8 @@ def validate_param_value(value, quoted=True): QUOTABLE = re.compile("[,;: ’']") -def dQuote(val): - """Parameter values containing [,;:] must be double quoted. +def dquote(val): + """Enclose parameter values containing [,;:] in double quotes. """ # a double-quote character is forbidden to appear in a parameter value # so replace it with a single-quote character @@ -182,7 +171,7 @@ def q_split(st, sep=','): def q_join(lst, sep=','): """Joins a list on sep, quoting strings with QUOTABLE chars. """ - return sep.join(dQuote(itm) for itm in lst) + return sep.join(dquote(itm) for itm in lst) class Parameters(CaselessDict): @@ -225,10 +214,11 @@ class Parameters(CaselessDict): items = self.items() items.sort() # To make doctests work for key, value in items: - value = paramVal(value) + value = param_value(value) if isinstance(value, unicode): value = value.encode(DEFAULT_ENCODING) - result.append('%s=%s' % (key.upper(), value)) + # CaselessDict keys are always unicode + result.append('%s=%s' % (key.upper().encode('utf-8'), value)) return ';'.join(result) @staticmethod @@ -269,12 +259,14 @@ class Parameters(CaselessDict): def escape_string(val): + # TODO: optimize this # '%{:02X}'.format(i) return val.replace(r'\,', '%2C').replace(r'\:', '%3A')\ .replace(r'\;', '%3B').replace(r'\\', '%5C') def unsescape_string(val): + # TODO: optimize this return val.replace('%2C', ',').replace('%3A', ':')\ .replace('%3B', ';').replace('%5C', '\\') @@ -288,7 +280,7 @@ class Contentline(unicode): """ def __new__(cls, value, strict=False, encoding=DEFAULT_ENCODING): - value = safe_unicode(value, encoding=encoding) + value = to_unicode(value, encoding=encoding) self = super(Contentline, cls).__new__(cls, value) self.strict = strict return self @@ -307,10 +299,10 @@ class Contentline(unicode): # values = escape_char(values) # TODO: after unicode only, remove - name = safe_unicode(name) - values = safe_unicode(values) + name = to_unicode(name) + values = to_unicode(values) if params: - params = safe_unicode(params.to_ical()) + params = to_unicode(params.to_ical()) return Contentline(u'%s;%s:%s' % (name, params, values)) return Contentline(u'%s:%s' % (name, values)) except Exception: @@ -349,22 +341,20 @@ class Contentline(unicode): raise ValueError("Content line could not be parsed into parts: %r:" " %s" % (self, exc)) - @staticmethod - def from_ical(st, strict=False): + @classmethod + def from_ical(cls, ical, strict=False): """Unfold the content lines in an iCalendar into long content lines. """ - try: - # a fold is carriage return followed by either a space or a tab - return Contentline(FOLD.sub('', st), strict=strict) - except: - raise ValueError(u'Expected StringType with content line') + ical = to_unicode(ical) + # a fold is carriage return followed by either a space or a tab + return cls(FOLD.sub('', ical), strict=strict) def to_ical(self): """Long content lines are folded so they are less than 75 characters. wide. """ value = self.encode(DEFAULT_ENCODING) - return foldline(value, newline='\r\n') + return foldline(value) class Contentlines(list): @@ -392,31 +382,5 @@ class Contentlines(list): raise ValueError('Expected StringType with content lines') -# ran this: -# sample = open('./samples/test.ics', 'rb').read() # binary file in windows! -# lines = Contentlines.from_ical(sample) -# for line in lines[:-1]: -# print line.parts() - -# got this: -# ('BEGIN', Parameters({}), 'VCALENDAR') -# ('METHOD', Parameters({}), 'Request') -# ('PRODID', Parameters({}), '-//My product//mxm.dk/') -# ('VERSION', Parameters({}), '2.0') -# ('BEGIN', Parameters({}), 'VEVENT') -# ('DESCRIPTION', Parameters({}), 'This is a very long description that ...') -# ('PARTICIPANT', Parameters({'CN': 'Max M'}), 'MAILTO:maxm@mxm.dk') -# ('DTEND', Parameters({}), '20050107T160000') -# ('DTSTART', Parameters({}), '20050107T120000') -# ('SUMMARY', Parameters({}), 'A second event') -# ('END', Parameters({}), 'VEVENT') -# ('BEGIN', Parameters({}), 'VEVENT') -# ('DTEND', Parameters({}), '20050108T235900') -# ('DTSTART', Parameters({}), '20050108T230000') -# ('SUMMARY', Parameters({}), 'A single event') -# ('UID', Parameters({}), '42') -# ('END', Parameters({}), 'VEVENT') -# ('END', Parameters({}), 'VCALENDAR') - # XXX: what kind of hack is this? import depends to be at end -from icalendar.prop import vText +from .prop import vText diff --git a/src/icalendar/prop.py b/src/icalendar/prop.py index 11b50dd..de82e9c 100644 --- a/src/icalendar/prop.py +++ b/src/icalendar/prop.py @@ -36,6 +36,7 @@ These types are mainly used for parsing and file generation. But you can set them directly. """ +from __future__ import absolute_import import re import pytz import binascii @@ -48,15 +49,13 @@ from datetime import ( tzinfo, ) from dateutil.tz import tzutc -from icalendar import SEQUENCE_TYPES -from icalendar import DEFAULT_ENCODING -from icalendar.caselessdict import CaselessDict -from icalendar.parser import ( +from . import SEQUENCE_TYPES, DEFAULT_ENCODING, to_unicode +from .caselessdict import CaselessDict +from .parser import ( Parameters, escape_char, unescape_char, tzid_from_dt, - safe_unicode ) DATE_PART = r'(\d+)D' @@ -96,7 +95,7 @@ class vBoolean(int): """Returns specific string according to state. """ - bool_map = CaselessDict(true=True, false=False) + BOOL_MAP = CaselessDict(true=True, false=False) def __new__(cls, *args, **kwargs): self = super(vBoolean, cls).__new__(cls, *args, **kwargs) @@ -108,10 +107,10 @@ class vBoolean(int): return 'TRUE' return 'FALSE' - @staticmethod - def from_ical(ical): + @classmethod + def from_ical(cls, ical): try: - return vBoolean.bool_map[ical] + return cls.BOOL_MAP[ical] except: raise ValueError("Expected 'TRUE' or 'FALSE'. Got %s" % ical) @@ -121,7 +120,7 @@ class vCalAddress(unicode): """ def __new__(cls, value, encoding=DEFAULT_ENCODING): - value = safe_unicode(value, encoding=encoding) + value = to_unicode(value, encoding=encoding) self = super(vCalAddress, cls).__new__(cls, value) self.params = Parameters() return self @@ -551,7 +550,7 @@ class vWeekday(unicode): }) def __new__(cls, value, encoding=DEFAULT_ENCODING): - value = safe_unicode(value, encoding=encoding) + value = to_unicode(value, encoding=encoding) self = super(vWeekday, cls).__new__(cls, value) match = WEEKDAY_RULE.match(self) if match is None: @@ -593,7 +592,7 @@ class vFrequency(unicode): }) def __new__(cls, value, encoding=DEFAULT_ENCODING): - value = safe_unicode(value, encoding=encoding) + value = to_unicode(value, encoding=encoding) self = super(vFrequency, cls).__new__(cls, value) if not self in vFrequency.frequencies: raise ValueError('Expected frequency, got: %s' % self) @@ -650,9 +649,9 @@ class vRecur(CaselessDict): result = [] for key, vals in self.sorted_items(): typ = self.types[key] - if not type(vals) in SEQUENCE_TYPES: + if not isinstance(vals, SEQUENCE_TYPES): vals = [vals] - vals = ','.join([typ(val).to_ical() for val in vals]) + vals = ','.join(typ(val).to_ical() for val in vals) result.append('%s=%s' % (key, vals)) return ';'.join(result) @@ -680,7 +679,7 @@ class vText(unicode): """ def __new__(cls, value, encoding=DEFAULT_ENCODING): - value = safe_unicode(value, encoding=encoding) + value = to_unicode(value, encoding=encoding) self = super(vText, cls).__new__(cls, value) self.encoding = encoding self.params = Parameters() @@ -694,11 +693,8 @@ class vText(unicode): @staticmethod def from_ical(ical): - try: - ical_unesc = unescape_char(ical) - return vText(ical_unesc) - except: - raise ValueError('Expected ical text, got: %s' % ical) + ical_unesc = unescape_char(ical) + return vText(ical_unesc) class vTime(object): @@ -734,7 +730,7 @@ class vUri(unicode): """ def __new__(cls, value, encoding=DEFAULT_ENCODING): - value = safe_unicode(value, encoding=encoding) + value = to_unicode(value, encoding=encoding) self = super(vUri, cls).__new__(cls, value) self.params = Parameters() return self @@ -834,7 +830,7 @@ class vInline(unicode): """ def __new__(cls, value, encoding=DEFAULT_ENCODING): - value = safe_unicode(value, encoding=encoding) + value = to_unicode(value, encoding=encoding) self = super(vInline, cls).__new__(cls, value) self.params = Parameters() return self diff --git a/src/icalendar/tests/test_cal.py b/src/icalendar/tests/test_cal.py index ae8c731..61229cf 100644 --- a/src/icalendar/tests/test_cal.py +++ b/src/icalendar/tests/test_cal.py @@ -7,10 +7,8 @@ import pytz class TestCalComponent(unittest.TestCase): def test_cal_Component(self): - Component = icalendar.cal.Component - Calendar = icalendar.cal.Calendar - Event = icalendar.cal.Event - prop = icalendar.prop + from icalendar.cal import Component, Calendar, Event + from icalendar import prop # A component is like a dictionary with extra methods and attributes. c = Component() @@ -105,7 +103,7 @@ class TestCalComponent(unittest.TestCase): # Text fields which span multiple mulitple lines require proper # indenting c = Calendar() - c['description']=u'Paragraph one\n\nParagraph two' + c['description'] = u'Paragraph one\n\nParagraph two' self.assertEqual(c.to_ical(), 'BEGIN:VCALENDAR\r\nDESCRIPTION:Paragraph one\\n\\nParagraph two' + '\r\nEND:VCALENDAR\r\n') @@ -154,10 +152,13 @@ class TestCalComponent(unittest.TestCase): # timezone, crated, dtstamp and last-modified must be in UTC. Component = icalendar.cal.Component comp = Component() - comp.add('dtstart', datetime(2010,10,10,10,0,0,tzinfo=pytz.timezone("Europe/Vienna"))) - comp.add('created', datetime(2010,10,10,12,0,0)) - comp.add('dtstamp', datetime(2010,10,10,14,0,0,tzinfo=pytz.timezone("Europe/Vienna"))) - comp.add('last-modified', datetime(2010,10,10,16,0,0,tzinfo=pytz.utc)) + comp.add('dtstart', datetime(2010, 10, 10, 10, 0, 0, + tzinfo=pytz.timezone("Europe/Vienna"))) + comp.add('created', datetime(2010, 10, 10, 12, 0, 0)) + comp.add('dtstamp', datetime(2010, 10, 10, 14, 0, 0, + tzinfo=pytz.timezone("Europe/Vienna"))) + comp.add('last-modified', datetime(2010, 10, 10, 16, 0, 0, + tzinfo=pytz.utc)) lines = comp.to_ical().splitlines() self.assertTrue( @@ -168,8 +169,6 @@ class TestCalComponent(unittest.TestCase): self.assertTrue( "LAST-MODIFIED;VALUE=DATE-TIME:20101010T160000Z" in lines) - - def test_cal_Component_from_ical(self): # RecurrenceIDs may contain a TZID parameter, if so, they should create # a tz localized datetime, otherwise, create a naive datetime @@ -212,7 +211,7 @@ class TestCal(unittest.TestCase): event = icalendar.cal.Event() event['summary'] = 'Python meeting about calendaring' event['uid'] = '42' - event.set('dtstart', datetime(2005,4,4,8,0,0)) + event.set('dtstart', datetime(2005, 4, 4, 8, 0, 0)) cal.add_component(event) self.assertEqual( cal.subcomponents[0].to_ical(), diff --git a/src/icalendar/tests/test_icalendar.py b/src/icalendar/tests/test_icalendar.py index e2577c2..49abd6b 100644 --- a/src/icalendar/tests/test_icalendar.py +++ b/src/icalendar/tests/test_icalendar.py @@ -133,14 +133,14 @@ class IcalendarTestCase (unittest.TestCase): parts = ('SUMMARY', Parameters(), vText('INternational char æ ø å')) self.assertEqual( Contentline.from_parts(parts), - 'SUMMARY:INternational char \xc3\xa6 \xc3\xb8 \xc3\xa5' + u'SUMMARY:INternational char æ ø å' ) # A value can also be unicode parts = ('SUMMARY', Parameters(), vText(u'INternational char æ ø å')) self.assertEqual( Contentline.from_parts(parts), - 'SUMMARY:INternational char \xc3\xa6 \xc3\xb8 \xc3\xa5' + u'SUMMARY:INternational char æ ø å' ) # Traversing could look like this. @@ -227,10 +227,10 @@ class IcalendarTestCase (unittest.TestCase): ) def test_value_double_quoting(self): - from icalendar.parser import dQuote - self.assertEqual(dQuote('Max'), 'Max') - self.assertEqual(dQuote('Rasmussen, Max'), '"Rasmussen, Max"') - self.assertEqual(dQuote('name:value'), '"name:value"') + from icalendar.parser import dquote + self.assertEqual(dquote('Max'), 'Max') + self.assertEqual(dquote('Rasmussen, Max'), '"Rasmussen, Max"') + self.assertEqual(dquote('name:value'), '"name:value"') def test_q_split(self): from icalendar.parser import q_split diff --git a/src/icalendar/tests/test_parser.py b/src/icalendar/tests/test_parser.py index 2cbd0d1..027e535 100644 --- a/src/icalendar/tests/test_parser.py +++ b/src/icalendar/tests/test_parser.py @@ -1,19 +1,20 @@ # -*- coding: utf-8 -*- import unittest -import icalendar +from .. import to_unicode class TestCalComponent(unittest.TestCase): def test_cal_Component(self): - safe_unicode = icalendar.parser.safe_unicode - self.assertEqual(safe_unicode('spam'), u'spam') - self.assertEqual(safe_unicode(u'spam'), u'spam') - self.assertEqual(safe_unicode(u'spam'.encode('utf-8')), u'spam') - self.assertEqual(safe_unicode('\xc6\xb5'), u'\u01b5') - self.assertEqual(safe_unicode(u'\xc6\xb5'.encode('iso-8859-1')), + self.assertEqual(to_unicode('spam'), u'spam') + self.assertEqual(to_unicode(u'spam'), u'spam') + self.assertEqual(to_unicode(u'spam'.encode('utf-8')), u'spam') + self.assertEqual(to_unicode('\xc6\xb5'), u'\u01b5') + self.assertEqual(to_unicode(u'\xc6\xb5'.encode('iso-8859-1')), u'\u01b5') - self.assertEqual(safe_unicode('\xc6\xb5', encoding='ascii'), u'\u01b5') - self.assertEqual(safe_unicode(1), 1) - self.assertEqual(safe_unicode(None), None) + self.assertEqual(to_unicode('\xc6\xb5', encoding='ascii'), u'\u01b5') + with self.assertRaises(AssertionError): + to_unicode(1) + with self.assertRaises(AssertionError): + to_unicode(None) diff --git a/src/icalendar/tests/test_prop.py b/src/icalendar/tests/test_prop.py index 0f11727..3a2848f 100644 --- a/src/icalendar/tests/test_prop.py +++ b/src/icalendar/tests/test_prop.py @@ -1,14 +1,13 @@ from datetime import datetime, date, timedelta, time import unittest -import icalendar import pytz class TestProp(unittest.TestCase): def test_prop_vBinary(self): - vBinary = icalendar.prop.vBinary + from ..prop import vBinary txt = 'This is gibberish' txt_ical = 'VGhpcyBpcyBnaWJiZXJpc2g=' @@ -23,7 +22,7 @@ class TestProp(unittest.TestCase): self.assertEqual( str(vBinary('txt').params), - "Parameters({'VALUE': 'BINARY', 'ENCODING': 'BASE64'})" + "Parameters({u'VALUE': 'BINARY', u'ENCODING': 'BASE64'})" ) # Long data should not have line breaks, as that would interfere @@ -33,7 +32,7 @@ class TestProp(unittest.TestCase): self.assertEqual(vBinary.from_ical(txt_ical), txt) def test_prop_vBoolean(self): - vBoolean = icalendar.prop.vBoolean + from ..prop import vBoolean self.assertEqual(vBoolean(True).to_ical(), 'TRUE') self.assertEqual(vBoolean(0).to_ical(), 'FALSE') @@ -43,29 +42,29 @@ class TestProp(unittest.TestCase): self.assertEqual(vBoolean.from_ical('true'), True) def test_prop_vCalAddress(self): - vCalAddress = icalendar.prop.vCalAddress + from ..prop import vCalAddress txt = 'MAILTO:maxm@mxm.dk' a = vCalAddress(txt) a.params['cn'] = 'Max M' self.assertEqual(a.to_ical(), txt) - self.assertEqual(str(a.params), "Parameters({'CN': 'Max M'})") + self.assertEqual(str(a.params), "Parameters({u'CN': 'Max M'})") self.assertEqual(vCalAddress.from_ical(txt), 'MAILTO:maxm@mxm.dk') def test_prop_vFloat(self): - vFloat = icalendar.prop.vFloat + from ..prop import vFloat self.assertEqual(vFloat(1.0).to_ical(), '1.0') self.assertEqual(vFloat.from_ical('42'), 42.0) self.assertEqual(vFloat(42).to_ical(), '42.0') def test_prop_vInt(self): - vInt = icalendar.prop.vInt + from ..prop import vInt self.assertEqual(vInt(42).to_ical(), '42') self.assertEqual(vInt.from_ical('13'), 13) self.assertRaises(ValueError, vInt.from_ical, '1s3') def test_prop_vDDDLists(self): - vDDDLists = icalendar.prop.vDDDLists + from ..prop import vDDDLists dt_list = vDDDLists.from_ical('19960402T010000Z') self.assertTrue(isinstance(dt_list, list)) @@ -89,7 +88,7 @@ class TestProp(unittest.TestCase): self.assertEqual(dt_list.to_ical(), '20000101T000000,20001111T000000') def test_prop_vDDDTypes(self): - vDDDTypes = icalendar.prop.vDDDTypes + from ..prop import vDDDTypes self.assertTrue(isinstance(vDDDTypes.from_ical('20010101T123000'), datetime)) @@ -107,7 +106,7 @@ class TestProp(unittest.TestCase): self.assertRaises(ValueError, vDDDTypes, 42) def test_prop_vDate(self): - vDate = icalendar.prop.vDate + from ..prop import vDate self.assertEqual(vDate(date(2001, 1, 1)).to_ical(), '20010101') self.assertEqual(vDate(date(1899, 1, 1)).to_ical(), '18990101') @@ -117,7 +116,7 @@ class TestProp(unittest.TestCase): self.assertRaises(ValueError, vDate, 'd') def test_prop_vDatetime(self): - vDatetime = icalendar.prop.vDatetime + from ..prop import vDatetime dt = datetime(2001, 1, 1, 12, 30, 0) self.assertEqual(vDatetime(dt).to_ical(), '20010101T123000') @@ -153,7 +152,7 @@ class TestProp(unittest.TestCase): self.assertEqual(vDatetime(dat).to_ical(), '20101010T000000') def test_prop_vDuration(self): - vDuration = icalendar.prop.vDuration + from ..prop import vDuration self.assertEqual(vDuration(timedelta(11)).to_ical(), 'P11D') self.assertEqual(vDuration(timedelta(-14)).to_ical(), '-P14D') @@ -181,7 +180,7 @@ class TestProp(unittest.TestCase): self.assertRaises(ValueError, vDuration, 11) def test_prop_vPeriod(self): - vPeriod = icalendar.prop.vPeriod + from ..prop import vPeriod # One day in exact datetimes per = (datetime(2000, 1, 1), datetime(2000, 1, 2)) @@ -226,7 +225,7 @@ class TestProp(unittest.TestCase): self.assertEqual(p.to_ical(), '20000101T000000/P31D') def test_prop_vWeekday(self): - vWeekday = icalendar.prop.vWeekday + from ..prop import vWeekday self.assertEqual(vWeekday('mo').to_ical(), 'MO') self.assertRaises(ValueError, vWeekday, 'erwer') @@ -238,14 +237,14 @@ class TestProp(unittest.TestCase): self.assertEqual(vWeekday('-tu').to_ical(), '-TU') def test_prop_vFrequency(self): - vFrequency = icalendar.prop.vFrequency + from ..prop import vFrequency self.assertRaises(ValueError, vFrequency, 'bad test') self.assertEqual(vFrequency('daily').to_ical(), 'DAILY') self.assertEqual(vFrequency('daily').from_ical('MONTHLY'), 'MONTHLY') def test_prop_vRecur(self): - vRecur = icalendar.prop.vRecur + from ..prop import vRecur # Let's see how close we can get to one from the rfc: # FREQ=YEARLY;INTERVAL=2;BYMONTH=1;BYDAY=SU;BYHOUR=8,9;BYMINUTE=30 @@ -317,7 +316,7 @@ class TestProp(unittest.TestCase): self.assertRaises(ValueError, vRecur.from_ical, 'BYDAY=12') def test_prop_vText(self): - vText = icalendar.prop.vText + from ..prop import vText self.assertEqual(vText(u'Simple text').to_ical(), 'Simple text') @@ -350,7 +349,7 @@ class TestProp(unittest.TestCase): # with the official U+FFFD REPLACEMENT CHARACTER. def test_prop_vTime(self): - vTime = icalendar.prop.vTime + from ..prop import vTime self.assertEqual(vTime(12, 30, 0).to_ical(), '123000') self.assertEqual(vTime.from_ical('123000'), time(12, 30)) @@ -359,7 +358,7 @@ class TestProp(unittest.TestCase): self.assertRaises(ValueError, vTime.from_ical, '263000') def test_prop_vUri(self): - vUri = icalendar.prop.vUri + from ..prop import vUri self.assertEqual(vUri('http://www.example.com/').to_ical(), 'http://www.example.com/') @@ -367,7 +366,7 @@ class TestProp(unittest.TestCase): 'http://www.example.com/') def test_prop_vGeo(self): - vGeo = icalendar.prop.vGeo + from ..prop import vGeo # Pass a list self.assertEqual(vGeo([1.2, 3.0]).to_ical(), '1.2;3.0') @@ -383,7 +382,7 @@ class TestProp(unittest.TestCase): self.assertRaises(ValueError, vGeo, 'g') def test_prop_vUTCOffset(self): - vUTCOffset = icalendar.prop.vUTCOffset + from ..prop import vUTCOffset self.assertEqual(vUTCOffset(timedelta(hours=2)).to_ical(), '+0200') @@ -418,17 +417,18 @@ class TestProp(unittest.TestCase): self.assertRaises(ValueError, vUTCOffset.from_ical, '+2400') def test_prop_vInline(self): - vInline = icalendar.prop.vInline + from ..prop import vInline self.assertEqual(vInline('Some text'), 'Some text') self.assertEqual(vInline.from_ical('Some text'), 'Some text') t2 = vInline('other text') t2.params['cn'] = 'Test Osterone' - self.assertEqual(str(t2.params), "Parameters({'CN': 'Test Osterone'})") + self.assertEqual(str(t2.params), + "Parameters({u'CN': 'Test Osterone'})") def test_prop_TypesFactory(self): - TypesFactory = icalendar.prop.TypesFactory + from ..prop import TypesFactory # To get a type you can use it like this. factory = TypesFactory() @@ -463,7 +463,8 @@ class TestPropertyValues(unittest.TestCase): def test_vDDDLists_timezone(self): """Test vDDDLists with timezone information. """ - vevent = icalendar.Event() + from .. import Event + vevent = Event() at = pytz.timezone('Europe/Vienna') dt1 = at.localize(datetime(2013, 1, 1)) dt2 = at.localize(datetime(2013, 1, 2)) diff --git a/src/icalendar/tests/test_property_params.py b/src/icalendar/tests/test_property_params.py index d7846ec..967f217 100644 --- a/src/icalendar/tests/test_property_params.py +++ b/src/icalendar/tests/test_property_params.py @@ -1,8 +1,9 @@ # coding: utf-8 -import icalendar import unittest +from .. import vCalAddress, Calendar, Event, Parameters + class TestPropertyParams(unittest.TestCase): @@ -10,12 +11,12 @@ class TestPropertyParams(unittest.TestCase): # Property parameters with values containing a COLON character, a # SEMICOLON character or a COMMA character MUST be placed in quoted # text. - cal_address = icalendar.vCalAddress('mailto:john.doe@example.org') + cal_address = vCalAddress('mailto:john.doe@example.org') cal_address.params["CN"] = "Doe, John" - ical = icalendar.Calendar() + ical = Calendar() ical.add('organizer', cal_address) - ical_str = icalendar.Calendar.to_ical(ical) + ical_str = Calendar.to_ical(ical) exp_str = """BEGIN:VCALENDAR\r\nORGANIZER;CN="Doe, John":"""\ """mailto:john.doe@example.org\r\nEND:VCALENDAR\r\n""" @@ -23,13 +24,13 @@ class TestPropertyParams(unittest.TestCase): # other way around: ensure the property parameters can be restored from # an icalendar string. - ical2 = icalendar.Calendar.from_ical(ical_str) + ical2 = Calendar.from_ical(ical_str) self.assertEqual(ical2.get('ORGANIZER').params.get('CN'), 'Doe, John') def test_unicode_param(self): - cal_address = icalendar.vCalAddress('mailto:john.doe@example.org') + cal_address = vCalAddress('mailto:john.doe@example.org') cal_address.params["CN"] = "Джон Доу" - vevent = icalendar.Event() + vevent = Event() vevent['ORGANIZER'] = cal_address self.assertEqual( vevent.to_ical(), @@ -55,8 +56,8 @@ class TestPropertyParams(unittest.TestCase): @param cn_param: CN parameter value to test for quoting @param cn_quoted: expected quoted parameter in icalendar format """ - vevent = icalendar.Event() - attendee = icalendar.vCalAddress('test@mail.com') + vevent = Event() + attendee = vCalAddress('test@mail.com') attendee.params['CN'] = cn_param vevent.add('ATTENDEE', attendee) self.assertEqual( @@ -71,14 +72,14 @@ class TestPropertyParams(unittest.TestCase): for char in NON_SAFE_CHARS: cn_escaped = ur"Society\%s 2014" % char cn_decoded = ur"Society%s 2014" % char - vevent = icalendar.Event.from_ical( + vevent = Event.from_ical( u'BEGIN:VEVENT\r\n' u'ORGANIZER;CN=%s:that\r\n' u'END:VEVENT\r\n' % cn_escaped ) self.assertEqual(vevent['ORGANIZER'].params['CN'], cn_decoded) - vevent = icalendar.Event.from_ical( + vevent = Event.from_ical( u'BEGIN:VEVENT\r\n' u'ORGANIZER;CN=that\\, that\\; that\\\\ that\\:' u':это\\, то\\; that\\\\ that\\:\r\n' @@ -94,7 +95,6 @@ class TestPropertyParams(unittest.TestCase): ) def test_parameters_class(self): - from icalendar import Parameters # Simple parameter:value pair p = Parameters(parameter1='Value1') diff --git a/src/icalendar/tools.py b/src/icalendar/tools.py index 9dddd55..d118abc 100644 --- a/src/icalendar/tools.py +++ b/src/icalendar/tools.py @@ -1,3 +1,4 @@ +from __future__ import absolute_import import sys import random import textwrap @@ -6,7 +7,7 @@ from string import ( digits, ) from datetime import datetime -from icalendar.prop import ( +from .prop import ( vText, vDatetime, )