Format groups/policy/metadata in JSON/XML replies.
authorAntony Chazapis <chazapis@gmail.com>
Fri, 2 Dec 2011 08:52:50 +0000 (10:52 +0200)
committerAntony Chazapis <chazapis@gmail.com>
Fri, 2 Dec 2011 08:52:50 +0000 (10:52 +0200)
Fixes #1719

docs/source/devguide.rst
pithos/api/functions.py
pithos/api/templates/accounts.xml
pithos/api/templates/containers.xml
pithos/api/templates/objects.xml
pithos/api/templatetags/__init__.py [new file with mode: 0644]
pithos/api/templatetags/get_type.py [new file with mode: 0644]
pithos/api/util.py

index 5e0f576..55dbc1f 100644 (file)
@@ -25,7 +25,8 @@ Document Revisions
 =========================  ================================
 Revision                   Description
 =========================  ================================
-0.8 (Nov 29, 2011)         Update allowed versioning values.
+0.8 (Dec 2, 2011)          Update allowed versioning values.
+\                          Change policy/meta formatting in JSON/XML replies.
 0.7 (Nov 21, 2011)         Suggest upload/download methods using hashmaps.
 \                          Propose syncing algorithm.
 \                          Support cross-account object copy and move.
@@ -163,7 +164,7 @@ Example ``format=json`` reply:
 
 ::
 
-  [{"name": "user", "last_modified": "2011-07-19T10:48:16"}, ...]
+  [{"name": "user", "last_modified": "2011-12-02T08:10:41.565891+00:00"}, ...]
 
 Example ``format=xml`` reply:
 
@@ -173,7 +174,7 @@ Example ``format=xml`` reply:
   <accounts>
     <account>
       <name>user</name>
-      <last_modified>2011-07-19T10:48:16</last_modified>
+      <last_modified>2011-12-02T08:10:41.565891+00:00</last_modified>
     </account>
     <account>...</account>
   </accounts>
@@ -185,7 +186,7 @@ Return Code                  Description
 204 (No Content)             The user has no access to other accounts (only for non-extended replies)
 ===========================  =====================
 
-Will use a ``200`` return code if the reply is of type json/xml.
+Will use a ``200`` return code if the reply is of type JSON/XML.
 
 Account Level
 ^^^^^^^^^^^^^
@@ -281,7 +282,41 @@ x_container_policy_*         Container behavior and limits
 x_container_meta_*           Optional user defined metadata
 ===========================  ============================
 
-For examples of container details returned in JSON/XML formats refer to the OOS API documentation.
+Example ``format=json`` reply:
+
+::
+
+  [{"name": "pithos",
+    "bytes": 62452,
+    "count": 8374,
+    "last_modified": "2011-12-02T08:10:41.565891+00:00",
+    "x_container_policy": {"quota": "53687091200", "versioning": "auto"},
+    "x_container_meta": {"a": "b", "1": "2"}}, ...]
+
+Example ``format=xml`` reply:
+
+::
+
+  <?xml version="1.0" encoding="UTF-8"?>
+  <account name="user">
+    <container>
+      <name>pithos</name>
+      <bytes>62452</bytes>
+      <count>8374</count>
+      <last_modified>2011-12-02T08:10:41.565891+00:00</last_modified>
+      <x_container_policy>
+        <key>quota</key><value>53687091200</value>
+        <key>versioning</key><value>auto</value>
+      </x_container_policy>
+      <x_container_meta>
+        <key>a</key><value>b</value>
+        <key>1</key><value>2</value>
+      </x_container_meta>
+    </container>
+    <container>...</container>
+  </account>
+
+For more examples of container details returned in JSON/XML formats refer to the OOS API documentation. In addition to the OOS API, Pithos returns all fields. Policy and metadata values are grouped and returned as key-value pairs.
 
 ===========================  =====================
 Return Code                  Description
@@ -292,7 +327,7 @@ Return Code                  Description
 412 (Precondition Failed)    The condition set can not be satisfied
 ===========================  =====================
 
-Will use a ``200`` return code if the reply is of type json/xml.
+Will use a ``200`` return code if the reply is of type JSON/XML.
 
 
 POST
@@ -456,7 +491,45 @@ Virtual directory markers are only included when ``delimiter`` is explicitly set
 In JSON results they appear as dictionaries with only a ``"subdir"`` key. In XML results they appear interleaved with ``<object>`` tags as ``<subdir name="..." />``.
 In case there is an object with the same name as a virtual directory marker, the object will be returned.
 
-For examples of object details returned in JSON/XML formats refer to the OOS API documentation.
+Example ``format=json`` reply:
+
+::
+
+  [{"name": "object",
+    "bytes": 0,
+    "hash": "d41d8cd98f00b204e9800998ecf8427e",
+    "content_type": "application/octet-stream",
+    "last_modified": "2011-12-02T08:10:41.565891+00:00",
+    "x_object_meta": {"asdf": "qwerty"},
+    "x_object_hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+    "x_object_version": 98,
+    "x_object_version_timestamp": "1322813441.565891",
+    "x_object_modified_by": "user"}, ...]
+
+Example ``format=xml`` reply:
+
+::
+
+  <?xml version="1.0" encoding="UTF-8"?>
+  <container name="pithos">
+    <object>
+      <name>object</name>
+      <bytes>0</bytes>
+      <hash>d41d8cd98f00b204e9800998ecf8427e</hash>
+      <content_type>application/octet-stream</content_type>
+      <last_modified>2011-12-02T08:10:41.565891+00:00</last_modified>
+      <x_object_meta>
+        <key>asdf</key><value>qwerty</value>
+      </x_object_meta>
+      <x_object_hash>e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855</x_object_hash>
+      <x_object_version>98</x_object_version>
+      <x_object_version_timestamp>1322813441.565891</x_object_version_timestamp>
+      <x_object_modified_by>chazapis</x_object_modified_by>
+    </object>
+    <object>...</object>
+  </container>
+
+For more examples of container details returned in JSON/XML formats refer to the OOS API documentation. In addition to the OOS API, Pithos returns all fields. Metadata values are grouped and returned as key-value pairs.
 
 ===========================  ===============================
 Return Code                  Description
@@ -467,7 +540,7 @@ Return Code                  Description
 412 (Precondition Failed)    The condition set can not be satisfied
 ===========================  ===============================
 
-Will use a ``200`` return code if the reply is of type json/xml.
+Will use a ``200`` return code if the reply is of type JSON/XML.
 
 
 PUT
@@ -674,7 +747,7 @@ Example ``format=json`` reply:
 
 ::
 
-  {"versions": [[23, 1307700892], [28, 1307700898], ...]}
+  {"versions": [[85, "1322734861.248469"], [86, "1322734905.009272"], ...]}
 
 Example ``format=xml`` reply:
 
@@ -682,8 +755,8 @@ Example ``format=xml`` reply:
 
   <?xml version="1.0" encoding="UTF-8"?>
   <object name="file">
-    <version timestamp="1307700892">23</version>
-    <version timestamp="1307700898">28</version>
+    <version timestamp="1322734861.248469">85</version>
+    <version timestamp="1322734905.009272">86</version>
     <version timestamp="...">...</version>
   </object>
 
@@ -978,7 +1051,7 @@ List of differences from the OOS API:
 * Headers ``X-Container-Block-*`` at the container level, exposing the underlying storage characteristics.
 * All metadata replies, at all levels, include latest modification information.
 * At all levels, a ``HEAD`` or ``GET`` request may use ``If-Modified-Since`` and ``If-Unmodified-Since`` headers.
-* Container/object lists include all associated metadata if the reply is of type json/xml. Some names are kept to their OOS API equivalents for compatibility.
+* Container/object lists include all associated metadata if the reply is of type JSON/XML. Some names are kept to their OOS API equivalents for compatibility.
 * Option to include only shared containers/objects in listings.
 * Object metadata allowed, in addition to ``X-Object-Meta-*``: ``Content-Encoding``, ``Content-Disposition``, ``X-Object-Manifest``. These are all replaced with every update operation, except if using the ``update`` parameter (in which case individual keys can also be deleted). Deleting meta by providing empty values also works when copying/moving an object.
 * Multi-range object ``GET`` support as outlined in RFC2616.
@@ -1006,7 +1079,7 @@ Clarifications/suggestions:
 * A ``GET`` reply for a level will include all headers of the corresponding ``HEAD`` request.
 * To avoid conflicts between objects and virtual directory markers in container listings, it is recommended that object names do not end with the delimiter used.
 * The ``Accept`` header may be used in requests instead of the ``format`` parameter to specify the desired request/reply format. The parameter overrides the header.
-* Container/object lists use a ``200`` return code if the reply is of type json/xml. The reply will include an empty json/xml.
+* Container/object lists use a ``200`` return code if the reply is of type JSON/XML. The reply will include an empty JSON/XML.
 * In headers, dates are formatted according to RFC 1123. In extended information listings, the ``last_modified`` field is formatted according to ISO 8601 (for OOS API compatibility). All other fields (Pithos extensions) use integer tiemstamps.
 * The ``Last-Modified`` header value always reflects the actual latest change timestamp, regardless of time control parameters and version requests. Time precondition checks with ``If-Modified-Since`` and ``If-Unmodified-Since`` headers are applied to this value.
 * A copy/move using ``PUT``/``COPY``/``MOVE`` will always update metadata, keeping all old values except the ones redefined in the request headers.
index b93ff6e..da27288 100644 (file)
@@ -155,6 +155,8 @@ def account_list(request):
     
     account_meta = []
     for x in accounts:
+        if x == request.user_uniq:
+            continue
         try:
             meta = request.backend.get_account_meta(request.user_uniq, x)
             groups = request.backend.get_account_groups(request.user_uniq, x)
@@ -163,8 +165,13 @@ def account_list(request):
         else:
             rename_meta_key(meta, 'modified', 'last_modified')
             rename_meta_key(meta, 'until_timestamp', 'x_account_until_timestamp')
-            for k, v in groups.iteritems():
-                meta['X-Container-Group-' + k] = ','.join(v)
+            m = dict([(k[15:], v) for k, v in meta.iteritems() if k.startswith('X-Account-Meta-')])
+            for k in m:
+                del(meta['X-Account-Meta-' + k])
+            if m:
+                meta['X-Account-Meta'] = printable_header_dict(m)
+            if groups:
+                meta['X-Account-Group'] = printable_header_dict(dict([(k, ','.join(v)) for k, v in groups.iteritems()]))
             account_meta.append(printable_header_dict(meta))
     if request.serialization == 'xml':
         data = render_to_string('accounts.xml', {'accounts': account_meta})
@@ -283,8 +290,13 @@ def container_list(request, v_account):
         else:
             rename_meta_key(meta, 'modified', 'last_modified')
             rename_meta_key(meta, 'until_timestamp', 'x_container_until_timestamp')
-            for k, v in policy.iteritems():
-                meta['X-Container-Policy-' + k] = v
+            m = dict([(k[17:], v) for k, v in meta.iteritems() if k.startswith('X-Container-Meta-')])
+            for k in m:
+                del(meta['X-Container-Meta-' + k])
+            if m:
+                meta['X-Container-Meta'] = printable_header_dict(m)
+            if policy:
+                meta['X-Container-Policy'] = printable_header_dict(dict([(k, v) for k, v in policy.iteritems()]))
             container_meta.append(printable_header_dict(meta))
     if request.serialization == 'xml':
         data = render_to_string('containers.xml', {'account': v_account, 'containers': container_meta})
@@ -538,6 +550,11 @@ def object_list(request, v_account, v_container):
                 rename_meta_key(meta, 'modified_by', 'x_object_modified_by')
                 rename_meta_key(meta, 'version', 'x_object_version')
                 rename_meta_key(meta, 'version_timestamp', 'x_object_version_timestamp')
+                m = dict([(k[14:], v) for k, v in meta.iteritems() if k.startswith('X-Object-Meta-')])
+                for k in m:
+                    del(meta['X-Object-Meta-' + k])
+                if m:
+                    meta['X-Object-Meta'] = printable_header_dict(m)
                 update_sharing_meta(request, permissions, v_account, v_container, x[0], meta)
                 update_public_meta(public, meta)
                 object_meta.append(printable_header_dict(meta))
index f83c34a..8db6fb2 100644 (file)
@@ -1,10 +1,13 @@
 <?xml version="1.0" encoding="UTF-8"?>
-
+{% load get_type %}
 <accounts>
   {% for account in accounts %}
   <account>
   {% for key, value in account.items %}
-    <{{ key }}>{{ value }}</{{ key }}>
+    <{{ key }}>{% if value|get_type == "dict" %}
+      {% for k, v in value.iteritems %}<key>{{ k }}</key><value>{{ v }}</value>
+      {% endfor %}
+    {% else %}{{ value }}{% endif %}</{{ key }}>
   {% endfor %}
   </account>
   {% endfor %}
index 681ab1e..fb635b9 100644 (file)
@@ -1,10 +1,13 @@
 <?xml version="1.0" encoding="UTF-8"?>
-
+{% load get_type %}
 <account name="{{ account }}">
   {% for container in containers %}
   <container>
   {% for key, value in container.items %}
-    <{{ key }}>{{ value }}</{{ key }}>
+    <{{ key }}>{% if value|get_type == "dict" %}
+      {% for k, v in value.iteritems %}<key>{{ k }}</key><value>{{ v }}</value>
+      {% endfor %}
+    {% else %}{{ value }}{% endif %}</{{ key }}>
   {% endfor %}
   </container>
   {% endfor %}
index 3c58cee..f28c22b 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-
+{% load get_type %}
 <container name="{{ container }}">
   {% for object in objects %}
   {% if object.subdir %}
@@ -7,7 +7,10 @@
   {% else %}
   <object>
   {% for key, value in object.items %}
-    <{{ key }}>{{ value }}</{{ key }}>
+    <{{ key }}>{% if value|get_type == "dict" %}
+      {% for k, v in value.iteritems %}<key>{{ k }}</key><value>{{ v }}</value>
+      {% endfor %}
+    {% else %}{{ value }}{% endif %}</{{ key }}>
   {% endfor %}
   </object>
   {% endif %}
diff --git a/pithos/api/templatetags/__init__.py b/pithos/api/templatetags/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/pithos/api/templatetags/get_type.py b/pithos/api/templatetags/get_type.py
new file mode 100644 (file)
index 0000000..cb98d4c
--- /dev/null
@@ -0,0 +1,40 @@
+# Copyright 2011 GRNET S.A. All rights reserved.
+# 
+# Redistribution and use in source and binary forms, with or
+# without modification, are permitted provided that the following
+# conditions are met:
+# 
+#   1. Redistributions of source code must retain the above
+#      copyright notice, this list of conditions and the following
+#      disclaimer.
+# 
+#   2. Redistributions in binary form must reproduce the above
+#      copyright notice, this list of conditions and the following
+#      disclaimer in the documentation and/or other materials
+#      provided with the distribution.
+# 
+# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
+# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+# 
+# The views and conclusions contained in the software and
+# documentation are those of the authors and should not be
+# interpreted as representing official policies, either expressed
+# or implied, of GRNET S.A.
+
+from django import template
+
+register = template.Library()
+
+@register.filter
+def get_type(value):  
+    return value.__class__.__name__  
index c2d835c..9a56b54 100644 (file)
@@ -98,7 +98,8 @@ def printable_header_dict(d):
     Format 'last_modified' timestamp.
     """
     
-    d['last_modified'] = isoformat(datetime.fromtimestamp(d['last_modified']))
+    if 'last_modified' in d:
+        d['last_modified'] = isoformat(datetime.fromtimestamp(d['last_modified']))
     return dict([(k.lower().replace('-', '_'), v) for k, v in d.iteritems()])
 
 def format_header_key(k):