kopia lustrzana https://github.com/collective/icalendar
CaselessDict keys are now automatically converted to unicode.
Test fixes. Small refactoring and polishing.pull/96/head
rodzic
f44a20b1d9
commit
104387cc4a
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
|
|
Ładowanie…
Reference in New Issue