[CalendarServer-changes] [11638] PyCalendar/branches/json-2
source_changes at macosforge.org
source_changes at macosforge.org
Mon Aug 26 09:31:33 PDT 2013
Revision: 11638
http://trac.calendarserver.org//changeset/11638
Author: cdaboo at apple.com
Date: 2013-08-26 09:31:33 -0700 (Mon, 26 Aug 2013)
Log Message:
-----------
Improved api to better support handling of multiple data types. Various fixes.
Modified Paths:
--------------
PyCalendar/branches/json-2/.pydevproject
PyCalendar/branches/json-2/src/pycalendar/containerbase.py
PyCalendar/branches/json-2/src/pycalendar/icalendar/calendar.py
PyCalendar/branches/json-2/src/pycalendar/icalendar/property.py
PyCalendar/branches/json-2/src/pycalendar/icalendar/recurrence.py
PyCalendar/branches/json-2/src/pycalendar/icalendar/requeststatusvalue.py
PyCalendar/branches/json-2/src/pycalendar/icalendar/tests/test_json.py
PyCalendar/branches/json-2/src/pycalendar/property.py
PyCalendar/branches/json-2/src/pycalendar/utcoffsetvalue.py
PyCalendar/branches/json-2/src/pycalendar/vcard/card.py
PyCalendar/branches/json-2/src/pycalendar/vcard/property.py
PyCalendar/branches/json-2/src/pycalendar/vcard/tests/test_card.py
Modified: PyCalendar/branches/json-2/.pydevproject
===================================================================
--- PyCalendar/branches/json-2/.pydevproject 2013-08-26 16:28:00 UTC (rev 11637)
+++ PyCalendar/branches/json-2/.pydevproject 2013-08-26 16:31:33 UTC (rev 11638)
@@ -2,7 +2,7 @@
<?eclipse-pydev version="1.0"?>
<pydev_project>
-<pydev_property name="org.python.pydev.PYTHON_PROJECT_VERSION">python 2.5</pydev_property>
+<pydev_property name="org.python.pydev.PYTHON_PROJECT_VERSION">python 2.7</pydev_property>
<pydev_pathproperty name="org.python.pydev.PROJECT_SOURCE_PATH">
<path>/PyCalendar-json2/src</path>
</pydev_pathproperty>
Modified: PyCalendar/branches/json-2/src/pycalendar/containerbase.py
===================================================================
--- PyCalendar/branches/json-2/src/pycalendar/containerbase.py 2013-08-26 16:28:00 UTC (rev 11637)
+++ PyCalendar/branches/json-2/src/pycalendar/containerbase.py 2013-08-26 16:31:33 UTC (rev 11638)
@@ -34,6 +34,9 @@
sComponentType = None
sPropertyType = None
+ sFormatText = None
+ sFormatJSON = None
+
@classmethod
def setPRODID(cls, prodid):
cls.sProdID = prodid
@@ -79,10 +82,35 @@
@classmethod
+ def parseData(cls, data, format=None):
+ """
+ Parse a source of data that can either be a stream (file like object) or a string. Also,
+ it can be in text or json formats.
+
+ @param data: the data to parse
+ @type data: C{str} or C{File-like}
+ @param format: format (MIME media type) of data.
+ @type format: C{str}
+ """
+
+ if format is None or format == cls.sFormatText:
+ return cls.parseTextData(data)
+
+ elif format == cls.sFormatJSON:
+ return cls.parseJSONData(data)
+
+
+ @classmethod
def parseText(cls, data):
+ return cls.parseTextData(data)
+
+ @classmethod
+ def parseTextData(cls, data):
+ if isinstance(data, str):
+ data = StringIO(data)
cal = cls(add_defaults=False)
- if cal.parse(StringIO(data)):
+ if cal.parse(data):
return cal
else:
return None
@@ -193,11 +221,25 @@
@classmethod
- def parseJSONText(cls, data):
+ def parseJSONData(cls, data):
+ if not isinstance(data, str):
+ data = data.read()
+ try:
+ jobject = json.loads(data)
+ except ValueError, e:
+ raise InvalidData(e, data)
+ return cls.parseJSON(jobject, None, cls(add_defaults=False))
- try:
- return cls.parseJSON(json.loads(data), None, cls(add_defaults=False))
- except Exception:
+
+ def getText(self, format=None):
+
+ if format is None or format == self.sFormatText:
+ s = StringIO()
+ self.generate(s)
+ return s.getvalue()
+ elif format == self.sFormatJSON:
+ return self.getTextJSON()
+ else:
return None
Modified: PyCalendar/branches/json-2/src/pycalendar/icalendar/calendar.py
===================================================================
--- PyCalendar/branches/json-2/src/pycalendar/icalendar/calendar.py 2013-08-26 16:28:00 UTC (rev 11637)
+++ PyCalendar/branches/json-2/src/pycalendar/icalendar/calendar.py 2013-08-26 16:31:33 UTC (rev 11638)
@@ -49,6 +49,9 @@
sComponentType = Component
sPropertyType = Property
+ sFormatText = "text/calendar"
+ sFormatJSON = "application/calendar+json"
+
propertyCardinality_1 = (
definitions.cICalProperty_PRODID,
definitions.cICalProperty_VERSION,
@@ -221,33 +224,35 @@
while readFoldedLine(ins, lines):
+ line = lines[0]
+
if state == LOOK_FOR_VCALENDAR:
# Look for start
- if lines[0] == self.getBeginDelimiter():
+ if line == self.getBeginDelimiter():
# Next state
state = GET_PROPERTY_OR_COMPONENT
# Handle blank line
- elif len(lines[0]) == 0:
+ elif len(line) == 0:
# Raise if requested, otherwise just ignore
if ParserContext.BLANK_LINES_IN_DATA == ParserContext.PARSER_RAISE:
raise InvalidData("iCalendar data has blank lines")
# Unrecognized data
else:
- raise InvalidData("iCalendar data not recognized", lines[0])
+ raise InvalidData("iCalendar data not recognized", line)
elif state == GET_PROPERTY_OR_COMPONENT:
# Parse property or look for start of component
- if lines[0].startswith("BEGIN:"):
+ if line.startswith("BEGIN:"):
# Push previous details to stack
componentstack.append((comp, compend,))
# Start a new component
- comp = Component.makeComponent(lines[0][6:], comp)
+ comp = self.sComponentType.makeComponent(line[6:], comp)
compend = comp.getEndDelimiter()
# Cache as result - but only the first one, we ignore the rest
@@ -258,13 +263,13 @@
if comp.getType() == definitions.cICalComponent_VTIMEZONE:
got_timezone = True
- elif lines[0] == self.getEndDelimiter():
+ elif line == self.getEndDelimiter():
# Change state
state = GOT_VCALENDAR
# Look for end of current component
- elif lines[0] == compend:
+ elif line == compend:
# Finalise the component (this caches data from the properties)
comp.finalise()
@@ -274,7 +279,7 @@
comp, compend = componentstack.pop()
# Blank line
- elif len(lines[0]) == 0:
+ elif len(line) == 0:
# Raise if requested, otherwise just ignore
if ParserContext.BLANK_LINES_IN_DATA == ParserContext.PARSER_RAISE:
raise InvalidData("iCalendar data has blank lines")
@@ -287,7 +292,7 @@
else:
# Parse parameter/value for top-level calendar item
- prop = Property.parseText(lines[0])
+ prop = self.sPropertyType.parseText(line)
# Check for valid property
if comp is not self:
@@ -336,12 +341,16 @@
del self.mMasterComponentsByTypeAndUID[component.getType()][uid]
- def getText(self, includeTimezones=False):
- s = StringIO()
- self.generate(s, includeTimezones=includeTimezones)
- return s.getvalue()
+ def getText(self, includeTimezones=False, format=None):
+ if format is None or format == self.sFormatText:
+ s = StringIO()
+ self.generate(s, includeTimezones=includeTimezones)
+ return s.getvalue()
+ elif format == self.sFormatJSON:
+ return self.getTextJSON(includeTimezones)
+
def generate(self, os, includeTimezones=False):
# Make sure all required timezones are in this object
if includeTimezones:
Modified: PyCalendar/branches/json-2/src/pycalendar/icalendar/property.py
===================================================================
--- PyCalendar/branches/json-2/src/pycalendar/icalendar/property.py 2013-08-26 16:28:00 UTC (rev 11637)
+++ PyCalendar/branches/json-2/src/pycalendar/icalendar/property.py 2013-08-26 16:31:33 UTC (rev 11638)
@@ -173,9 +173,7 @@
def __init__(self, name=None, value=None, valuetype=None):
- self.mName = name if name is not None else ""
- self.mParameters = {}
- self.mValue = None
+ super(Property, self).__init__(name, value, valuetype)
# The None check speeds up .duplicate()
if value is None:
Modified: PyCalendar/branches/json-2/src/pycalendar/icalendar/recurrence.py
===================================================================
--- PyCalendar/branches/json-2/src/pycalendar/icalendar/recurrence.py 2013-08-26 16:28:00 UTC (rev 11637)
+++ PyCalendar/branches/json-2/src/pycalendar/icalendar/recurrence.py 2013-08-26 16:31:33 UTC (rev 11638)
@@ -735,6 +735,8 @@
):
if not isinstance(value, str) and not isinstance(value, unicode) and not isinstance(value, int):
value = ",".join(map(str, value))
+ elif name == "until":
+ value = value.replace("-", "").replace(":", "")
items.append("%s=%s" % (name.upper(), value,))
self.parse(";".join(items))
Modified: PyCalendar/branches/json-2/src/pycalendar/icalendar/requeststatusvalue.py
===================================================================
--- PyCalendar/branches/json-2/src/pycalendar/icalendar/requeststatusvalue.py 2013-08-26 16:28:00 UTC (rev 11637)
+++ PyCalendar/branches/json-2/src/pycalendar/icalendar/requeststatusvalue.py 2013-08-26 16:31:33 UTC (rev 11638)
@@ -20,6 +20,7 @@
from pycalendar.icalendar import xmldefinitions
from pycalendar.parser import ParserContext
from pycalendar.value import Value
+from pycalendar import xmldefinitions as xmldefinitions_top
import xml.etree.cElementTree as XML
class RequestStatusValue(Value):
@@ -113,4 +114,4 @@
def setValue(self, value):
self.mValue = value
-Value.registerType(Value.VALUETYPE_REQUEST_STATUS, RequestStatusValue, None)
+Value.registerType(Value.VALUETYPE_REQUEST_STATUS, RequestStatusValue, None, xmldefinitions_top.value_text)
Modified: PyCalendar/branches/json-2/src/pycalendar/icalendar/tests/test_json.py
===================================================================
--- PyCalendar/branches/json-2/src/pycalendar/icalendar/tests/test_json.py 2013-08-26 16:28:00 UTC (rev 11637)
+++ PyCalendar/branches/json-2/src/pycalendar/icalendar/tests/test_json.py 2013-08-26 16:31:33 UTC (rev 11638)
@@ -15,6 +15,7 @@
##
from pycalendar.icalendar.calendar import Calendar
+from pycalendar.icalendar.property import Property
import difflib
import unittest
@@ -410,7 +411,7 @@
cal1 = Calendar.parseText(caldata)
test1 = cal1.getText()
- cal2 = Calendar.parseJSONText(jcaldata)
+ cal2 = Calendar.parseJSONData(jcaldata)
test2 = cal2.getText()
self.assertEqual(
@@ -461,7 +462,7 @@
cal1 = Calendar.parseText(icaldata)
test1 = cal1.getText()
- cal2 = Calendar.parseJSONText(jcaldata)
+ cal2 = Calendar.parseJSONData(jcaldata)
test2 = cal2.getText()
self.assertEqual(
@@ -516,8 +517,8 @@
}
],
["tzname", {}, "text", "EST"],
- ["tzoffsetfrom", {}, "utc-offset", "-04:00"],
- ["tzoffsetto", {}, "utc-offset", "-05:00"]
+ ["tzoffsetfrom", {}, "utc-offset", "-04:00:00"],
+ ["tzoffsetto", {}, "utc-offset", "-05:00:00"]
],
[]
]
@@ -619,7 +620,7 @@
cal1 = Calendar.parseText(icaldata)
test1 = cal1.getText()
- cal2 = Calendar.parseJSONText(jcaldata)
+ cal2 = Calendar.parseJSONData(jcaldata)
test2 = cal2.getText()
self.assertEqual(
@@ -627,3 +628,48 @@
test2,
"\n".join(difflib.unified_diff(str(test1).splitlines(), test2.splitlines()))
)
+
+
+
+class TestJSONProperty(unittest.TestCase):
+
+ test_data = (
+ # Different value types
+ ["attach", {}, "binary", "VGVzdA=="],
+ ["organizer", {}, "cal-address", "mailto:jdoe at example.com"],
+ ["dtstart", {"tzid": "US/Eastern"}, "date-time", "2006-02-26T12:00:00"],
+ ["dtstart", {}, "date", "2006-02-26"],
+ ["dtstart", {}, "date-time", "2006-02-26T13:00:00Z"],
+ ["x-foo", {}, "text", "BAR"],
+ ["duration", {}, "duration", "PT10M"],
+ ["sequence", {}, "integer", 1],
+ ["rdate", {}, "date-time", "2006-02-26T12:00:00Z", "2006-02-27T12:00:00Z"],
+ ["freebusy", {}, "period", ["2006-02-26T12:00:00Z", "2006-02-27T12:00:00Z"]],
+ ["rrule", {}, "recur", {"freq":"MONTHLY", "count": 3, "byday":["TU", "WE", "TH"], "bysetpos":[-1]}],
+ ["request-status", {}, "text", ["2.0", "Success"]],
+ ["geo", {}, "float", [-2.1, 3.2]],
+ ["uri", {}, "uri", "http://www.example.com"],
+ ["tzoffsetfrom", {}, "utc-offset", "-05:00"],
+ ["tzoffsetfrom", {}, "utc-offset", "-04:58:57"],
+ ["x-foo", {}, "float", -1.23],
+ ["x-test", {}, "text", "Some:, text."],
+ ["x-apple-structured-location", {}, "uri", "geo:123.123,123.123"],
+ ["rrule", {}, "recur", {"freq":"MONTHLY", "until": "2013-01-01T00:00:00Z"}],
+
+ # Various parameters
+ ["dtstart", {"tzid": "Somewhere, else"}, "date-time", "2006-02-26T12:00:00"],
+ ["attendee", {"partstat": "ACCEPTED", "role": "CHAIR"}, "cal-address", "mailto:jdoe at example.com"],
+ ["x-foo", {"x-bar": "Some\\ntext"}, "text", "foobar"],
+
+ # Parameter escaping
+ ["attendee", {"cn": "My 'Test' Name", "role": "CHAIR"}, "cal-address", "mailto:jdoe at example.com"],
+ )
+
+
+ def testParseGenerate(self):
+
+ for data in TestJSONProperty.test_data:
+ prop = Property.parseJSON(data)
+ jobject = []
+ prop.writeJSON(jobject)
+ self.assertEqual(jobject[0], data, "Failed parse/generate: %s to %s" % (data, jobject[0],))
Modified: PyCalendar/branches/json-2/src/pycalendar/property.py
===================================================================
--- PyCalendar/branches/json-2/src/pycalendar/property.py 2013-08-26 16:28:00 UTC (rev 11637)
+++ PyCalendar/branches/json-2/src/pycalendar/property.py 2013-08-26 16:31:33 UTC (rev 11638)
@@ -56,9 +56,12 @@
def __init__(self, name=None, value=None, valuetype=None):
- raise NotImplementedError
+ self.mName = name if name is not None else ""
+ self.mParameters = {}
+ self.mValue = None
+
def duplicate(self):
raise NotImplementedError
@@ -517,8 +520,7 @@
# Check whether custom value is set
if self.sValue in self.mParameters:
attr = self.getParameterValue(self.sValue)
- if attr != self.sText:
- value_type = self.sValueTypeMap.get(attr, value_type)
+ value_type = self.sValueTypeMap.get(attr, value_type)
# Check for specials
if self.mName.upper() in self.sSpecialVariants:
Modified: PyCalendar/branches/json-2/src/pycalendar/utcoffsetvalue.py
===================================================================
--- PyCalendar/branches/json-2/src/pycalendar/utcoffsetvalue.py 2013-08-26 16:28:00 UTC (rev 11637)
+++ PyCalendar/branches/json-2/src/pycalendar/utcoffsetvalue.py 2013-08-26 16:31:33 UTC (rev 11638)
@@ -40,7 +40,7 @@
# Must be of specific lengths
datalen = len(data)
- if datalen not in ((6, 8,) if fullISO else (5, 7,)):
+ if datalen not in ((6, 9,) if fullISO else (5, 7,)):
self.mValue = 0
raise ValueError
@@ -59,7 +59,8 @@
# Get seconds if present
secs = 0
if datalen > 6:
- secs = int(data[index + 2:])
+ index = 7 if fullISO else 5
+ secs = int(data[index:])
self.mValue = ((hours * 60) + mins) * 60 + secs
if not plus:
Modified: PyCalendar/branches/json-2/src/pycalendar/vcard/card.py
===================================================================
--- PyCalendar/branches/json-2/src/pycalendar/vcard/card.py 2013-08-26 16:28:00 UTC (rev 11637)
+++ PyCalendar/branches/json-2/src/pycalendar/vcard/card.py 2013-08-26 16:31:33 UTC (rev 11638)
@@ -14,6 +14,7 @@
# limitations under the License.
##
+from cStringIO import StringIO
from pycalendar.containerbase import ContainerBase
from pycalendar.exceptions import InvalidData
from pycalendar.parser import ParserContext
@@ -23,6 +24,7 @@
Property_PRODID, Property_UID
from pycalendar.vcard.property import Property
from pycalendar.vcard.validation import VCARD_VALUE_CHECKS
+import json
class Card(ContainerBase):
@@ -30,6 +32,9 @@
sComponentType = None
sPropertyType = Property
+ sFormatText = "text/vcard"
+ sFormatJSON = "application/vcard+json"
+
propertyCardinality_1 = (
definitions.Property_VERSION,
definitions.Property_N,
@@ -64,12 +69,24 @@
)
- @staticmethod
- def parseMultiple(ins):
+ @classmethod
+ def parseMultipleData(cls, data, format):
+ if format == cls.sFormatText:
+ return cls.parseMultipleTextData(data)
+ elif format == cls.sFormatJSON:
+ return cls.parseMultipleJSONData(data)
+
+
+ @classmethod
+ def parseMultipleTextData(cls, ins):
+
+ if isinstance(ins, str):
+ ins = StringIO(ins)
+
results = []
- card = Card(add_defaults=False)
+ card = cls(add_defaults=False)
LOOK_FOR_VCARD = 0
GET_PROPERTY = 1
@@ -142,6 +159,21 @@
return results
+ @classmethod
+ def parseMultipleJSONData(cls, data):
+
+ if not isinstance(data, str):
+ data = data.read()
+ try:
+ jobjects = json.loads(data)
+ except ValueError, e:
+ raise InvalidData(e, data)
+ results = []
+ for jobject in jobjects:
+ results.append(cls.parseJSON(jobject, None, cls(add_defaults=False)))
+ return results
+
+
def addDefaultProperties(self):
self.addProperty(Property(definitions.Property_PRODID, Card.sProdID))
self.addProperty(Property(definitions.Property_VERSION, "3.0"))
Modified: PyCalendar/branches/json-2/src/pycalendar/vcard/property.py
===================================================================
--- PyCalendar/branches/json-2/src/pycalendar/vcard/property.py 2013-08-26 16:28:00 UTC (rev 11637)
+++ PyCalendar/branches/json-2/src/pycalendar/vcard/property.py 2013-08-26 16:31:33 UTC (rev 11638)
@@ -141,11 +141,14 @@
def __init__(self, group=None, name=None, value=None, valuetype=None):
+ super(Property, self).__init__(name, value, valuetype)
+
self.mGroup = group
- self.mName = name if name is not None else ""
- self.mParameters = {}
- self.mValue = None
+ # The None check speeds up .duplicate()
+ if value is None:
+ pass
+
if isinstance(value, int):
self._init_attr_value_int(value)
Modified: PyCalendar/branches/json-2/src/pycalendar/vcard/tests/test_card.py
===================================================================
--- PyCalendar/branches/json-2/src/pycalendar/vcard/tests/test_card.py 2013-08-26 16:28:00 UTC (rev 11637)
+++ PyCalendar/branches/json-2/src/pycalendar/vcard/tests/test_card.py 2013-08-26 16:31:33 UTC (rev 11638)
@@ -343,7 +343,7 @@
for item, results in data:
- cards = Card.parseMultiple(StringIO.StringIO(item))
+ cards = Card.parseMultipleTextData(StringIO.StringIO(item))
self.assertEqual(len(cards), len(results))
for card, result in zip(cards, results):
self.assertEqual(str(card), result, "\n".join(difflib.unified_diff(str(card).splitlines(), result.splitlines())))
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20130826/b3d657ca/attachment-0001.html>
More information about the calendarserver-changes
mailing list