[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