Commits

Anonymous committed 2481b6d

1st commit

  • Participants

Comments (0)

Files changed (9)

+# use glob syntax.
+syntax: glob
+*.pyc
+*.pyo
+*~
+.*.swp
+*.log
+*.dat
+Thumbs.db
+
+# switch to regexp syntax.
+syntax:regexp
+/\.
+DS_Store
+

milky/__init__.py

+#!/usr/bin/env python2.6
+# -*- coding: utf-8 -*-
+
+from milky.api import API, DATE_FORMAT, \
+        PERMS_READ, PERMS_WRITE, PERMS_DELETE, \
+        HAS_DUE_TIME, HAS_NOT_DUE_TIME, \
+        FREQ_YEARLY, FREQ_MONTHLY, FREQ_WEEKLY, FREQ_DAILY, \
+        PRIORITY_HIGH, PRIORITY_MEDIUM, PRIORITY_LOW, PRIORITY_NONE
+from milky.error import MilkyError, RTMSystemError, RTMRequestError, \
+        ERRCODE_UNKNOWN, ERRCODE_NETWORK, ERRCODE_JSON, ERRCODE_LOGIN_FAILED
+
+#!/usr/bin/env python2.6
+# -*- coding: utf-8 -*-
+
+import logging
+import types
+import urllib
+import hashlib
+
+try:
+    import json
+except ImportError:
+    try:
+        import json
+    except ImportError:
+        from django.utils import json
+
+from milky.error import RTMSystemError, RTMRequestError, \
+        ERRCODE_NETWORK, ERRCODE_JSON, ERRCODE_UNKNOWN
+from milky import request
+from milky.datastructures import SortedDict
+
+API_URL = 'http://api.rememberthemilk.com/services/rest/'
+AUTH_URL = 'http://www.rememberthemilk.com/services/auth/'
+
+CHARSET = 'utf-8'
+DATE_FORMAT = '%Y-%m-%dT%H:%M:%SZ'
+PERMS_READ = u'read'
+PERMS_WRITE = u'write'
+PERMS_DELETE = u'delete'
+
+# For parametars
+_PALAM_TRUE = 1
+_PARAM_FALSE = 0
+
+# has_due_time parametars for task due
+HAS_DUE_TIME = _PALAM_TRUE
+HAS_NOT_DUE_TIME = _PARAM_FALSE
+
+# For priority
+PRIORITY_HIGH = 1
+PRIORITY_MEDIUM = 2
+PRIORITY_LOW = 3
+PRIORITY_NONE = 0
+
+# Recurrence frequencies
+FREQ_YEARLY = u'YEARLY'
+FREQ_MONTHLY = u'MONTHLY'
+FREQ_WEEKLY = u'WEEKLY'
+FREQ_DAILY = u'DAILY'
+
+class API(object):
+    """rememberthemilk.com API.
+    
+    Args:
+        api_key - RTM API key.
+        shared_secret - RTM shared secret.
+        perms - Access permissions (Default: "read")
+        token - token for granted access (Optional)
+
+    Permissions:
+        read - gives the ability to read task, contact, group and list 
+            details and contents.
+        write - gives the ability to add and modify task, contact, 
+            group and list details and contents (also allows you to read).
+        delete - gives the ability to delete tasks, contacts, groups 
+            and lists (also allows you to read and write).
+    """
+
+    def __init__(self, api_key, shared_secret, perms=PERMS_READ, 
+            frob=None, token=None, user_agent=None):
+
+        """Create RTM instance."""
+
+        self.api_key = api_key
+        self.shared_secret = shared_secret
+        self.perms = perms
+        self.frob = frob
+        self.token = token
+        self.user_agent = user_agent
+
+        if user_agent:
+            class RTMURLopener(urllib.FancyURLopener):
+                version = user_agent
+            urllib._urlopener = RTMURLopener()
+
+        for prefix, methods in request.METHODS.items():
+            setattr(self, prefix, Request(self, prefix, methods))
+
+    def __sign(self, params):
+        """Generate sign with MD5 hash."""
+
+        params.keyOrder.sort() # Sort parameter keys (alphabetical)
+        pairs = ''.join(['%s%s' % (k, v) for k, v in params.items()])
+        if type(pairs) is unicode:
+            pairs = pairs.encode(CHARSET)
+        return hashlib.md5(self.shared_secret+pairs).hexdigest()
+
+    def __call(self, url, params):
+        if params:
+            url = '%s?%s' % (url, urllib.urlencode(SortedDict(
+                [(k, v.encode(CHARSET) if type(v) is types.UnicodeType else v) \
+                        for (k, v) in params.items()])))
+        logging.debug(url)
+        return urllib.urlopen(url)
+
+    def get(self, method, auth_required, model_cls, **params):
+        params = SortedDict(params)
+        params['method'] = method
+        params['api_key'] = self.api_key
+        params['format'] = 'json'
+        if auth_required:
+            params['auth_token'] = self.get_token()
+        params['api_sig'] = self.__sign(params)
+
+        try:
+            row = self.__call(API_URL, params).read()
+        except Exception, e:
+            raise RTMSystemError('Cannot connect RTM.', ERRCODE_NETWORK)
+        logging.debug(row)
+
+        try:
+            rsp = SortedDict(json.loads(row))['rsp']
+        except Exception, e:
+            raise RTMSystemError('Cannot parse response.', ERRCODE_JSON)
+
+        if rsp['stat'] != 'ok':
+            if rsp.has_key('err') \
+                    and rsp['err'].has_key('msg') \
+                    and rsp['err'].has_key('code'):
+                logging.debug(row)
+                msg = rsp['err']['msg'].encode(CHARSET)
+                code = int(rsp['err']['code'])
+                logging.warn("%s (%d)" % (msg, code))
+                raise RTMRequestError(msg, code)
+            else:
+                raise RTMSystemError('An unknown error has occurred.', ERRCODE_UNKNOWN)
+
+        return model_cls._parse(rsp)
+
+    def get_frob(self):
+        if not self.frob:
+            self.frob = self.auth.getFrob()
+        return self.frob
+
+    def get_auth_url(self):
+        params = SortedDict([
+                ('api_key', self.api_key),
+                ('perms', self.perms),
+                ('frob', self.get_frob()), ])
+        params['api_sig'] = self.__sign(params)
+        return '%s?%s' % (AUTH_URL, urllib.urlencode(params))
+
+    def get_token(self):
+        if not self.token:
+            auth = self.auth.getToken(frob=self.get_frob())
+            self.token = auth.token
+        return self.token
+
+class Request(object):
+    """API request creator."""
+
+    def __init__(self, rtm, prefix, methods):
+        self.rtm = rtm
+        self.prefix = prefix
+        self.methods = methods
+
+    def __getattr__(self, attr):
+        if attr not in self.methods:
+            raise AttributeError, 'No such attribute %s' % attr
+        auth_required, required_args, optional_args, \
+                model_cls = self.methods[attr]
+        if self.prefix == 'tasksnotes':
+            method = 'rtm.tasks.notes.%s' % attr
+        else:
+            method = 'rtm.%s.%s' % (self.prefix, attr)
+
+        return lambda **params: self.__call(
+                method, auth_required, required_args, optional_args, 
+                model_cls, **params)
+
+    def __call(self, method, auth_required, required_args, optional_args, 
+            model_cls, **params):
+
+        for required_arg in required_args:
+            if required_arg not in params:
+                raise TypeError, 'Missing required parameter %s' % required_arg
+
+        return self.rtm.get(method, auth_required, model_cls, **params)
+
+def test_rtm():
+    from milky import test_configs as configs
+    rtm = API(configs.RTM_API_KEY, configs.RTM_SHARED_SECRET, PERMS_DELETE)
+    print rtm.test.echo()
+
+if __name__ == '__main__':
+    test_rtm()
+

milky/datastructures.py

+#!/usr/bin/env python2.6
+# -*- coding: utf-8 -*-
+
+from types import GeneratorType
+
+class SortedDict(dict):
+    """
+    A dictionary that keeps its keys in the order in which they're inserted.
+    """
+    def __new__(cls, *args, **kwargs):
+        instance = super(SortedDict, cls).__new__(cls, *args, **kwargs)
+        instance.keyOrder = []
+        return instance
+
+    def __init__(self, data=None):
+        if data is None:
+            data = {}
+        elif isinstance(data, GeneratorType):
+            # Unfortunately we need to be able to read a generator twice.  Once
+            # to get the data into self with our super().__init__ call and a
+            # second time to setup keyOrder correctly
+            data = list(data)
+        super(SortedDict, self).__init__(data)
+        if isinstance(data, dict):
+            self.keyOrder = data.keys()
+        else:
+            self.keyOrder = []
+            seen = set()
+            for key, value in data:
+                if key not in seen:
+                    self.keyOrder.append(key)
+                    seen.add(key)
+
+    def __deepcopy__(self, memo):
+        return self.__class__([(key, deepcopy(value, memo))
+                               for key, value in self.iteritems()])
+
+    def __setitem__(self, key, value):
+        if key not in self:
+            self.keyOrder.append(key)
+        super(SortedDict, self).__setitem__(key, value)
+
+    def __delitem__(self, key):
+        super(SortedDict, self).__delitem__(key)
+        self.keyOrder.remove(key)
+
+    def __iter__(self):
+        return iter(self.keyOrder)
+
+    def pop(self, k, *args):
+        result = super(SortedDict, self).pop(k, *args)
+        try:
+            self.keyOrder.remove(k)
+        except ValueError:
+            # Key wasn't in the dictionary in the first place. No problem.
+            pass
+        return result
+
+    def popitem(self):
+        result = super(SortedDict, self).popitem()
+        self.keyOrder.remove(result[0])
+        return result
+
+    def items(self):
+        return zip(self.keyOrder, self.values())
+
+    def iteritems(self):
+        for key in self.keyOrder:
+            yield key, self[key]
+
+    def keys(self):
+        return self.keyOrder[:]
+
+    def iterkeys(self):
+        return iter(self.keyOrder)
+
+    def values(self):
+        return map(self.__getitem__, self.keyOrder)
+
+    def itervalues(self):
+        for key in self.keyOrder:
+            yield self[key]
+
+    def update(self, dict_):
+        for k, v in dict_.iteritems():
+            self[k] = v
+
+    def setdefault(self, key, default):
+        if key not in self:
+            self.keyOrder.append(key)
+        return super(SortedDict, self).setdefault(key, default)
+
+    def value_for_index(self, index):
+        """Returns the value of the item at the given zero-based index."""
+        return self[self.keyOrder[index]]
+
+    def insert(self, index, key, value):
+        """Inserts the key, value pair before the item with the given index."""
+        if key in self.keyOrder:
+            n = self.keyOrder.index(key)
+            del self.keyOrder[n]
+            if n < index:
+                index -= 1
+        self.keyOrder.insert(index, key)
+        super(SortedDict, self).__setitem__(key, value)
+
+    def copy(self):
+        """Returns a copy of this object."""
+        # This way of initializing the copy means it works for subclasses, too.
+        obj = self.__class__(self)
+        obj.keyOrder = self.keyOrder[:]
+        return obj
+
+    def __repr__(self):
+        """
+        Replaces the normal dict.__repr__ with a version that returns the keys
+        in their sorted order.
+        """
+        return '{%s}' % ', '.join(['%r: %r' % (k, v) for k, v in self.items()])
+
+    def clear(self):
+        super(SortedDict, self).clear()
+        self.keyOrder = []
+
+#!/usr/bin/env python2.6
+# -*- coding: utf-8 -*-
+
+ERRCODE_UNKNOWN = -999
+ERRCODE_NETWORK = -10
+ERRCODE_JSON = -20
+ERRCODE_LOGIN_FAILED = 98
+
+class MilkyError(Exception):
+    """Milky exception"""
+
+    def __init__(self, msg, no, response=None):
+        super(MilkyError, self).__init__(msg)
+        if type(msg) is unicode:
+            msg = msg.encode('utf-8')
+        self.msg = msg
+        self.no = int(no)
+        self.response = response
+
+    def __str__(self):
+        return '%s (%d)' % (self.msg, self.no)
+
+# API error for the system.
+class RTMSystemError(MilkyError): pass
+# API error for the user parameters.
+class RTMRequestError(MilkyError): pass
+
+#!/usr/bin/env python2.6
+# -*- coding: utf-8 -*-
+
+import re
+import datetime
+
+class ResultSet(list):
+    """A list like object that holds results from a RTM API query."""
+
+class ModelBase(object):
+    """Base model class"""
+
+    def __init__(self):
+        """Setup default datas."""
+        pass
+
+    def __repr__(self):
+        return u'<%s - %s>' % (self.__class__.__name__, 
+                u', '.join([c for c in dir(self) if not c.startswith(u'_')]))
+
+    def __getstate__(self):
+        return dict(self.__dict__)
+
+    @classmethod
+    def _parse(cls, json):
+        """Parse a JSON object into a model instance."""
+        raise NotImplementedError
+
+    @classmethod
+    def _parse_list(cls, json_list):
+        """Parse a list of JSON objects into a result set of model instances."""
+        if not json_list:
+            return ResultSet()
+
+        if type(json_list) is not list:
+            json_list = [json_list, ]
+        return ResultSet(cls._parse(obj) for obj in json_list if obj)
+
+class Frob(ModelBase):
+    @classmethod
+    def _parse(cls, json):
+        return json[u'frob']
+
+class Stat(ModelBase):
+    @classmethod
+    def _parse(cls, json):
+        if json[u'stat'] == u'ok':
+            return True
+        return False
+
+class Timeline(ModelBase):
+    @classmethod
+    def _parse(cls, json):
+        return json[u'timeline']
+
+class User(ModelBase):
+    @classmethod
+    def _parse(cls, json):
+        user = cls()
+        for k, v in json[u'user'].items():
+            if k in [u'id', ]:
+                setattr(user, k, int(v))
+            else:
+                setattr(user, k, v and v or None)
+        return user
+
+class Auth(ModelBase):
+    @classmethod
+    def _parse(cls, json):
+        auth = cls()
+        for k, v in json[u'auth'].items():
+            if k == u'user':
+                user = User._parse(json[u'auth'])
+                setattr(auth, k, user)
+            else:
+                setattr(auth, k, v and v or None)
+        return auth
+
+class List(ModelBase):
+    @classmethod
+    def _parse(cls, json):
+
+        # for rtm.lists.add, archive, delete, setName, unarchive
+        if json.has_key(u'list'):
+            json = json[u'list']
+
+        _list = cls()
+        for k, v in json.items():
+            if k in [u'locked', u'archived', u'deleted', u'smart', ]:
+                setattr(_list, k, v==u'1' and True or False)
+            elif k in [u'id', u'sort_order', u'position', ]:
+                setattr(_list, k, int(v))
+            else:
+                setattr(_list, k, v and v or None)
+        return _list
+
+class Lists(ModelBase):
+    @classmethod
+    def _parse(cls, json):
+        if not json[u'lists'] or not json[u'lists'].has_key(u'list'):
+            return ResultSet()
+        return List._parse_list(json[u'lists'][u'list'])
+
+# For task recurrences
+RE_FREQ = re.compile(r"FREQ=(?P<freq>[a-z]+)", re.I)
+RE_INTERVAL = re.compile(r"INTERVAL=(?P<interval>[\d]+)", re.I)
+RE_WEEKLY_BYDAY = re.compile(r"BYDAY=(?P<byday>[a-z,]+)", re.I)
+RE_MONTHLY_BYDAY = re.compile(r"BYDAY=(?P<byday>[-\w]+)", re.I)
+RE_BYMONTHDAY = re.compile(r"BYMONTHDAY=(?P<bymonthday>[\d]+)", re.I)
+RE_UNTIL = re.compile(r"UNTIL=(?P<until>[\w]+)", re.I)
+RE_COUNT = re.compile(r"COUNT=(?P<count>[\d]+)", re.I)
+
+class Recurrence(ModelBase):
+    def __init__(self):
+        super(Recurrence, self).__init__()
+        self.freq = None
+        self.interval = None
+        self.byday = None
+        self.bymonthday = None
+        self.until = None
+        self.count = None
+
+    @classmethod
+    def _parse(cls, json):
+        recurrence = cls()
+
+        for k, v in json.items():
+            if k == u'$t':
+                m = RE_FREQ.search(v)
+                if m:
+                    recurrence.freq = m.group('freq').upper()
+
+                m = RE_INTERVAL.search(v)
+                if m:
+                    recurrence.interval = m.group('interval') and int(m.group('interval')) or None
+
+                m = RE_WEEKLY_BYDAY.search(v)
+                if m:
+                    recurrence.byday = m.group('byday').upper().split(u',')
+
+                m = RE_MONTHLY_BYDAY.search(v)
+                if m:
+                    recurrence.byday = m.group('byday').upper()
+
+                m = RE_BYMONTHDAY.search(v)
+                if m:
+                    recurrence.bymonthday = m.group('bymonthday') and int(m.group('bymonthday')) or None
+
+                m = RE_UNTIL.search(v)
+                if m:
+                    recurrence.until = m.group('until') and datetime.datetime.strptime(
+                            v.upper(), "%Y%m%dT%H%M%SZ")
+
+                m = RE_COUNT.search(v)
+                if m:
+                    recurrence.count = m.group('count') and int(m.group('count')) or None
+            elif k in [u'every', ]:
+                setattr(recurrence, k, v and int(v) or None)
+            else:
+                setattr(recurrence, k, v)
+
+        return recurrence
+
+# For task estimates
+RE_DAYS = re.compile(r"(?P<days>[\d.]+)\s*d", re.I)
+RE_HOURS = re.compile(r"(?P<hours>[\d.]+)\s*h", re.I)
+RE_MINUTES = re.compile(r"(?P<minutes>[\d.]+)\s*m", re.I)
+
+class Task(ModelBase):
+    @classmethod
+    def _parse(cls, json):
+        task = cls()
+
+        for k, v in json.items():
+            if k == u'priority':
+                if v == u'N':
+                    setattr(task, k, None)
+                else:
+                    setattr(task, k, v and int(v) or None)
+            elif k in [u'has_due_time', ]:
+                setattr(task, k, v==u'1' and True or False)
+            elif k in [u'added', u'completed', u'deleted', u'due']:
+                setattr(task, k, v and datetime.datetime.strptime(
+                    v, '%Y-%m-%dT%H:%M:%SZ') or None)
+            elif k in [u'postponed', ]:
+                setattr(task, k, int(v))
+            elif k in [u'estimate', ]:
+                estimate = None
+                if v:
+                    days = 0.0
+                    hours = 0.0
+                    minutes = 0.0
+
+                    m = RE_DAYS.search(v)
+                    if m:
+                        days = float(m.group('days'))
+                    m = RE_HOURS.search(v)
+                    if m:
+                        hours = float(m.group('hours'))
+                    m = RE_MINUTES.search(v)
+                    if m:
+                        minutes = float(m.group('minutes'))
+                    if days or hours or minutes:
+                        estimate = datetime.timedelta(days=days, hours=hours, minutes=minutes)
+
+                setattr(task, k, estimate)
+            else:
+                setattr(task, k, v and v or None)
+
+        return task
+
+class Note(ModelBase):
+    @classmethod
+    def _parse(cls, json):
+        note = cls()
+
+        # for rtm.tasks.notes.add, edit
+        if json.has_key(u'note'):
+            json = json[u'note']
+
+        for k, v in json.items():
+            if k == u'$t':
+                setattr(note, u'text', v)
+            elif k in [u'id', ]:
+                setattr(note, k, int(v))
+            else:
+                setattr(note, k, v and v or None)
+        return note
+
+class TaskSeries(ModelBase):
+    def __init__(self):
+        super(TaskSeries, self).__init__()
+        self.task = ResultSet()
+        self.rrule = None
+        self.tags = ResultSet()
+        self.notes = ResultSet()
+        self.participants = ResultSet()
+
+    @classmethod
+    def _parse(cls, json):
+        taskseries = cls()
+
+        for k, v in json.items():
+            if k == u'task':
+                taskseries.task = Task._parse_list(v)
+            elif k == u'rrule':
+                taskseries.rrule = Recurrence._parse(v)
+            elif k == u'tags':
+                if v and v.has_key(u'tag'):
+                    if type(v[u'tag']) is list:
+                        taskseries.tags = v[u'tag']
+                    else:
+                        taskseries.tags = ResultSet(v[u'tag'], )
+            elif k == u'notes':
+                if v and v.has_key(u'note'):
+                    taskseries.notes = Note._parse_list(v[u'note'])
+            elif k == u'participants':
+                if v and v.has_key(u'contact'):
+                    taskseries.participants = Contact._parse_list(v[u'contact'])
+            elif k in [u'created', u'modified', ]:
+                setattr(taskseries, k, v and datetime.datetime.strptime(
+                    v, '%Y-%m-%dT%H:%M:%SZ') or None)
+            elif k in [u'location_id', ]:
+                setattr(taskseries, k, v and int(v) or None)
+            else:
+                setattr(taskseries, k, v and v or None)
+        return taskseries
+
+class TaskList(ModelBase):
+    def __init__(self):
+        super(TaskList, self).__init__()
+        self.taskseries = ResultSet()
+
+    @classmethod
+    def _parse(cls, json):
+        _list = cls()
+
+        # for rtm.tasks.add
+        if json.has_key(u'list'):
+            json = json[u'list']
+
+        for k, v in json.items():
+            if k == u'taskseries':
+                _list.taskseries = TaskSeries._parse_list(v)
+            elif k in [u'id', ]:
+                setattr(_list, k, int(v))
+            else:
+                setattr(_list, k, v and v or None)
+        return _list
+
+class Tasks(ModelBase):
+    def __init__(self):
+        super(Tasks, self).__init__()
+        self.lists = ResultSet()
+
+    @classmethod
+    def _parse(cls, json):
+        tasks = cls()
+
+        for k, v in json[u'tasks'].items():
+            if k == u'list':
+                tasks.lists = TaskList._parse_list(v)
+            else:
+                setattr(tasks, k, v and v or None)
+        return tasks
+
+class Contact(ModelBase):
+    @classmethod
+    def _parse(cls, json):
+        contact = cls()
+
+        # for rtm.contacts.delete
+        if json.has_key(u'contact'):
+            json = json[u'contact']
+
+        for k, v in json.items():
+            if k in [u'id', ]:
+                setattr(contact, k, int(v))
+            else:
+                setattr(contact, k, v and v or None)
+        return contact
+
+class Contacts(ModelBase):
+    @classmethod
+    def _parse(cls, json):
+        if not json[u'contacts'] or not json[u'contacts'].has_key(u'contact'):
+            return ResultSet()
+        return Contact._parse_list(json[u'contacts'][u'contact']);
+
+class Group(ModelBase):
+    def __init__(self):
+        super(Group, self).__init__()
+        self.contacts = ResultSet()
+
+    @classmethod
+    def _parse(cls, json):
+        group = cls()
+
+        # for rtm.group.delete
+        if json.has_key(u'group'):
+            json = json[u'group']
+
+        for k, v in json.items():
+            if k == u'contacts':
+                group.contacts = Contacts._parse(json)
+            elif k in [u'id', ]:
+                setattr(group, k, int(v))
+            else:
+                setattr(group, k, v and v or None)
+        return group
+
+class Groups(ModelBase):
+    @classmethod
+    def _parse(cls, json):
+        if not json[u'groups'] or not json[u'groups'].has_key(u'group'):
+            return ResultSet()
+        return Group._parse_list(json[u'groups'][u'group']);
+
+class Argument(ModelBase):
+    @classmethod
+    def _parse(cls, json):
+        argument = cls()
+        for k, v in json.items():
+            if k == u'$t':
+                setattr(argument, u'description', v)
+            elif k in [u'optional', ]:
+                setattr(argument, k, v==u'1' and True or False)
+            else:
+                setattr(argument, k, v and v or None)
+        return argument
+
+class Error(ModelBase):
+    @classmethod
+    def _parse(cls, json):
+        error = cls()
+        for k, v in json.items():
+            if k == u'$t':
+                setattr(error, u'description', v and v or None)
+            elif k in [u'code', ]:
+                setattr(error, k, int(v))
+            else:
+                setattr(error, k, v and v or None)
+        return error
+
+class Method(ModelBase):
+    def __init__(self):
+        super(Method, self).__init__()
+        self.arguments = ResultSet()
+        self.errors = ResultSet()
+
+    @classmethod
+    def _parse(cls, json):
+        method = cls()
+
+        for k, v in json[u'method'].items():
+            if k == u'arguments' and v.has_key(u'argument'):
+                method.arguments = Argument._parse_list(v[u'argument'])
+            elif k == u'errors' and v.has_key(u'error'):
+                method.errors = Error._parse_list(v[u'error'])
+            elif k in [u'needslogin', u'needssigning', u'requiredperms', ]:
+                setattr(method, k, v==u'1' and True or False)
+            else:
+                setattr(method, k, v and v or None)
+        return method
+
+class Methods(ModelBase):
+    @classmethod
+    def _parse(cls, json):
+        return json[u'methods'][u'method']
+
+class Timezone(ModelBase):
+    @classmethod
+    def _parse(cls, json):
+        timezone = cls()
+        for k, v in json.items():
+            if k in [u'id', u'dst', u'offset', u'current_offset',]:
+                setattr(timezone, k, int(v))
+            else:
+                setattr(timezone, k, v and v or None)
+        return timezone
+
+class Timezones(ModelBase):
+    @classmethod
+    def _parse(cls, json):
+        return Timezone._parse_list(json[u'timezones'][u'timezone']);
+
+class Time(ModelBase):
+    @classmethod
+    def _parse(cls, json):
+        time = cls()
+        for k, v in json[u'time'].items():
+            if k == u'$t':
+                setattr(time, u'time', v and v or None)
+            else:
+                setattr(time, k, v and v or None)
+        return time
+
+class Location(ModelBase):
+    @classmethod
+    def _parse(cls, json):
+        location = cls()
+        for k, v in json.items():
+            if k in [u'viewable', ]:
+                setattr(location, k, v==u'1' and True or False)
+            elif k in [u'id', u'zoom', ]:
+                setattr(location, k, int(v))
+            elif k in [u'longitude', u'latitude', ]:
+                setattr(location, k, v and float(v) or None)
+            else:
+                setattr(location, k, v and v or None)
+        return location
+
+class Locations(ModelBase):
+    @classmethod
+    def _parse(cls, json):
+        if not json[u'locations'] or not json[u'locations'].has_key(u'location'):
+            return ResultSet()
+        return Location._parse_list(json[u'locations'][u'location'])
+
+class Settings(ModelBase):
+    @classmethod
+    def _parse(cls, json):
+        settings = cls()
+        for k, v in json[u'settings'].items():
+            if k in [u'defaultlist', u'dateformat', u'timeformat', ]:
+                setattr(settings, k, int(v))
+            else:
+                setattr(settings, k, v and v or None)
+        return settings
+
+#!/usr/bin/env python2.6
+# -*- coding: utf-8 -*-
+
+from milky import models
+
+"""Method properties.
+
+{'prefix': {'method': (auth_required, 
+    (required_args, ), (optional_args, ), 
+    response_model), }, }
+"""
+METHODS = {
+    'auth': {
+        'checkToken': (False, 
+            ('auth_token', ), (), 
+            models.Auth, ),
+        'getFrob': (False, 
+            (), (), 
+            models.Frob, ),
+        'getToken': (False, 
+            ('frob', ), (), 
+            models.Auth, ), },
+    'contacts': {
+        'add': (True, 
+            ('timeline', 'contact', ), (), 
+            models.Contact, ),
+        'delete': (True, 
+            ('timeline', 'contact_id', ), (), 
+            models.Stat, ),
+        'getList': (True, 
+            (), (), 
+            models.Contacts, ), },
+    'groups': {
+        'add': (True, 
+            ('timeline', 'group', ), (), 
+            models.Group, ),
+        'addContact': (True, 
+            ('timeline', 'group_id', 'contact_id', ), (), 
+            models.Stat, ),
+        'delete': (True, 
+            ('timeline', 'group_id', ), (), 
+            models.Stat, ),
+        'getList': (True, 
+            (), (), 
+            models.Groups, ),
+        'removeContact': (True, 
+            ('timeline', 'group_id', 'contact_id', ), (), 
+            models.Stat, ), },
+    'lists': {
+        'add': (True, 
+            ('timeline', 'name', ), ('filter', ), 
+            models.List, ),
+        'archive': (True, 
+            ('timeline', 'list_id', ), (), 
+            models.List, ),
+        'delete': (True, 
+            ('timeline', 'list_id', ), (), 
+            models.List, ),
+        'getList': (True, 
+            (), (), 
+            models.Lists, ),
+        'setDefaultList': (True, 
+            ('timeline', ), ('list_id', ), 
+            models.Stat, ),
+        'setName': (True, 
+            ('timeline', 'list_id', 'name', ), (), 
+            models.List, ),
+        'unarchive': (True, 
+            ('timeline', ), ('list_id', ), 
+            models.List, ), },
+    'locations': {
+        'getList': (True, 
+            (), (), 
+            models.Locations, ), },
+    'reflection': {
+        'getMethodInfo': (False, 
+            ('method_name', ), (), 
+            models.Method, ),
+        'getMethods': (False, 
+            (), (), 
+            models.Methods, ), },
+    'settings': {
+        'getList': (True, 
+            (), (), 
+            models.Settings, ), },
+    'tasks': {
+        'add': (True, 
+            ('timeline', 'name', ), ('list_id', 'parse', ), 
+            models.TaskList, ),
+        'addTags': (True, 
+            ('timeline', 'list_id', 'taskseries_id', 'task_id', 'tags', ), (), 
+            models.TaskList, ),
+        'complete': (True, 
+            ('timeline', 'list_id', 'taskseries_id', 'task_id', ), (), 
+            models.TaskList, ),
+        'delete': (True, 
+            ('timeline', 'list_id', 'taskseries_id', 'task_id', ), (), 
+            models.TaskList, ),
+        'getList': (True, 
+            (), ('list_id', 'filter', 'last_sync', ), 
+            models.Tasks, ),
+        'movePriority': (True, 
+            ('timeline', 'list_id', 'taskseries_id', 'task_id', 'direction', ), (), 
+            models.TaskList, ),
+        'moveTo': (True, 
+            ('timeline', 'from_list_id', 'to_list_id', 'taskseries_id', 'task_id', ), (), 
+            models.TaskList, ),
+        'postpone': (True, 
+            ('timeline', 'list_id', 'taskseries_id', 'task_id', ), (), 
+            models.TaskList, ),
+        'removeTags': (True, 
+            ('timeline', 'list_id', 'taskseries_id', 'task_id', 'tags', ), (), 
+            models.TaskList, ),
+        'setDueDate': (True, 
+            ('timeline', 'list_id', 'taskseries_id', 'task_id', ), ('due', 'has_due_time', 'parse', ), 
+            models.TaskList, ),
+        'setEstimate': (True, 
+            ('timeline', 'list_id', 'taskseries_id', 'task_id', ), ('estimate', ), 
+            models.TaskList, ),
+        'setLocation': (True, 
+            ('timeline', 'list_id', 'taskseries_id', 'task_id', ), ('location_id', ), 
+            models.TaskList, ),
+        'setName': (True, 
+            ('timeline', 'list_id', 'taskseries_id', 'task_id', 'name', ), (), 
+            models.TaskList, ),
+        'setPriority': (True, 
+            ('timeline', 'list_id', 'taskseries_id', 'task_id', ), ('priority', ), 
+            models.TaskList, ),
+        'setRecurrence': (True, 
+            ('timeline', 'list_id', 'taskseries_id', 'task_id', ), ('repeat', ), 
+            models.TaskList, ),
+        'setTags': (True, 
+            ('timeline', 'list_id', 'taskseries_id', 'task_id', ), ('tags', ), 
+            models.TaskList, ),
+        'setURL': (True, 
+            ('timeline', 'list_id', 'taskseries_id', 'task_id', ), ('url', ), 
+            models.TaskList, ),
+        'uncomplete': (True, 
+            ('timeline', 'list_id', 'taskseries_id', 'task_id', ), (), 
+            models.TaskList, ), },
+    'tasksnotes': {
+        'add': (True, 
+            ('timeline', 'list_id', 'taskseries_id', 'task_id', 'note_title', 'note_text', ), (), 
+            models.Note, ),
+        'delete': (True, 
+            ('timeline', 'note_id', ), (), 
+            models.Stat, ),
+        'edit': (True, 
+            ('timeline', 'note_id', 'note_title', 'note_text', ), (), 
+            models.Note, ), },
+    'test': {
+        'echo': (False, 
+            (), (), 
+            models.Stat, ),
+        'login': (True, 
+            (), (), 
+            models.User, ), },
+    'time': {
+        'convert': (True, 
+            ('to_timezone', ), ('from_timezone', 'to_timezone', 'time', ), 
+            models.Time, ),
+        'parse': (True, 
+            ('text', ), ('timezone', 'dateformat', ), 
+            models.Time, ), },
+    'timelines': {
+        'create': (True, 
+            (), (), 
+            models.Timeline, ), },
+    'timezones': {
+        'getList': (True, 
+            (), (), 
+            models.Timezones, ), },
+    'transactions': {
+        'undo': (True, 
+            ('timeline', 'transaction_id', ), (), 
+            models.Stat, ), }, }
+
+RTM_API_KEY = ''
+RTM_SHARED_SECRET = ''
+
+TESTUSER_MAIN = {
+    u'fullname': u'Test Dev',
+    u'username': u'devtest',
+    u'id': 2419634,
+    u'frob': u'',
+    u'token': u'',
+}
+
+TESTUSER_1 = {
+    u'username': u'',
+}
+
+TESTUSER_2 = {
+    u'username': u'',
+}
+
+#!/usr/bin/env python2.6
+# -*- coding: utf-8 -*-
+
+import logging
+import datetime
+import unittest
+
+import milky
+import test_configs as configs
+
+RTM_API_KEY = configs.RTM_API_KEY
+RTM_SHARED_SECRET = configs.RTM_SHARED_SECRET
+PERMS = milky.PERMS_DELETE
+
+"""Define test configurations in test_configs.py
+RTM_API_KEY = '<your api key>'
+RTM_SHARED_SECRET = '<your api sig>'
+
+TESTUSER_MAIN = {
+    u'fullname': u'<main user fullname>',
+    u'username': u'<main user name>',
+    u'id': u'<main user id>',
+    u'frob': u'',
+    u'token': u'',
+}
+TESTUSER_1 = {
+    u'username': u'<some user name>', }
+TESTUSER_2 = {
+    u'username': u'<some user name>', }
+"""
+
+class TestAuthFrob(unittest.TestCase):
+    def setUp(self):
+        self.rtm = milky.API(RTM_API_KEY, RTM_SHARED_SECRET, 
+                PERMS)
+        self.assertEqual(self.rtm.frob, None)
+
+    def test_get_auth_url(self):
+        import webbrowser
+        auth_url = self.rtm.get_auth_url()
+        self.assertTrue(self.rtm.frob is not None)
+        logging.info('Frob: %s' % self.rtm.frob)
+        logging.info('Auth url: %s' % auth_url)
+        webbrowser.open(auth_url)
+
+class TestAuthToken(unittest.TestCase):
+    def setUp(self):
+        self.rtm = milky.API(RTM_API_KEY, RTM_SHARED_SECRET, 
+                PERMS, frob=configs.TESTUSER_MAIN['frob'])
+        self.token = self.rtm.get_token()
+        logging.info('Token: %s' % self.token)
+
+    def test_checkToken(self):
+        """rtm.auth.checkToken"""
+
+        auth = self.rtm.auth.checkToken(auth_token=self.rtm.token)
+        logging.info('Auth token: %s' % auth.token)
+        self.assertEqual(auth.token, self.token)
+        logging.info('Auth perms: %s' % auth.perms)
+        self.assertEqual(auth.perms, PERMS)
+        logging.info('Auth user ID: %s' % auth.user.id)
+        self.assertEqual(auth.user.id, configs.TESTUSER_MAIN['id'])
+        logging.info('Auth username: %s' % auth.user.username)
+        self.assertEqual(auth.user.username, configs.TESTUSER_MAIN['username'])
+        logging.info('Auth fullname: %s' % auth.user.fullname)
+        self.assertEqual(auth.user.fullname, configs.TESTUSER_MAIN['fullname'])
+
+class TestBase(unittest.TestCase):
+    def setUp(self):
+        self.rtm = milky.API(RTM_API_KEY, RTM_SHARED_SECRET, 
+                PERMS, token=configs.TESTUSER_MAIN['token'])
+
+        # Test account (devtest)
+        self.testuser_main = configs.TESTUSER_MAIN
+
+        self.testusers = [
+                configs.TESTUSER_1,
+                configs.TESTUSER_2]
+
+        # Test groups
+        self.groupnames = [u'test', u'テスト', ]
+        # Test lists
+        self.test_list = u'Milky テスト'
+        # Test locations
+        self.locationnames = [u'home', u'office']
+
+class TestTest(TestBase):
+    def test_echo(self):
+        """rtm.test.echo"""
+
+        stat = self.rtm.test.echo()
+        logging.info('Echo : %s' % stat)
+        self.assertEqual(stat, True)
+
+    def test_login(self):
+        """rtm.test.login"""
+
+        user = self.rtm.test.login()
+        logging.info('User ID: %s' % user.id)
+        self.assertEqual(user.id, self.testuser_main['id'])
+        logging.info('Username: %s' % user.username)
+        self.assertEqual(user.username, self.testuser_main['username'])
+
+class TestReflection(TestBase):
+    def test_get_method_info(self):
+        """rtm.reflection.getMethodInfo"""
+
+        method_name = u'rtm.test.login'
+        method = self.rtm.reflection.getMethodInfo(method_name=method_name)
+        logging.info('Method info: %s' % method.name)
+        logging.info('Method description: %s' % method.description)
+        self.assertEqual(method.name, method_name)
+        logging.info('Method need login: %s' % method.needslogin)
+        self.assertEqual(method.needslogin, True)
+        logging.info('Method need signing: %s' % method.needssigning)
+        self.assertEqual(method.needssigning, True)
+        logging.info('Method required perms: %s' % method.requiredperms)
+        self.assertEqual(method.requiredperms, True)
+        logging.info('Method response: %s' % method.response)
+        for argument in method.arguments:
+            logging.info('Argument: %s: %s (%s)' % (
+                argument.name, argument.description, argument.optional))
+        for error in method.errors:
+            logging.info('Error: %s %s - %s' % (
+                error.code, error.message, error.description))
+
+    def test_get_methods(self):
+        """rtm.test.getMethods"""
+
+        methods = self.rtm.reflection.getMethods()
+        for method in methods:
+            logging.info('Method: %s' % method)
+        self.assertTrue(u'rtm.auth.checkToken' in methods)
+        self.assertTrue(u'rtm.auth.getFrob' in methods)
+        self.assertTrue(u'rtm.auth.getToken' in methods)
+
+class TestTimezones(TestBase):
+    def test_getlist(self):
+        """rtm.timezones.getList"""
+        
+        timezones = self.rtm.timezones.getList()
+        settings = self.rtm.settings.getList()
+
+        for timezone in timezones:
+            logging.info('Timezone: %s %s %s %s %s' % (
+                timezone.name, timezone.id, timezone.dst, 
+                timezone.offset, timezone.current_offset))
+        self.assertTrue(
+                settings.timezone in [timezone.name for timezone in timezones])
+
+class TestTime(TestBase):
+    def setUp(self):
+        super(TestTime, self).setUp()
+
+        timezones = self.rtm.timezones.getList()
+        settings = self.rtm.settings.getList()
+
+        for timezone in timezones:
+            if timezone.name == settings.timezone:
+                self.timezone = timezone
+
+    def test_convert(self):
+        utc_now = datetime.datetime.utcnow()
+        locale_now = utc_now + datetime.timedelta(0, int(self.timezone.current_offset))
+
+        logging.info('UTC: %s, Locale: %s' % (utc_now, locale_now))
+        time = self.rtm.time.convert(to_timezone=self.timezone.name, from_timezone='UTC', 
+                time=utc_now.isoformat())
+        self.assertEqual(time.timezone, self.timezone.name)
+        self.assertEqual(time.time, locale_now.strftime('%Y-%m-%dT%H:%M:%S'))
+
+class TestLocations(TestBase):
+    def test_get_locations(self):
+        locations = self.rtm.locations.getList()
+        for location in self.locationnames:
+            self.assertTrue(location in \
+                    [location.name for location in locations])
+
+        for location in locations:
+            logging.info('Location: %s %d %d %d %.3f %.3f' % (location.name, location.id, 
+                location.zoom, location.viewable,
+                location.longitude, location.latitude))
+
+class TestSettings(TestBase):
+    def test_getlist(self):
+        """rtm.settings.getList"""
+
+        settings = self.rtm.settings.getList()
+        logging.info('Language: %s' % settings.language)
+        self.assertEqual(settings.language, u"ja")
+        logging.info('Default list: %s' % settings.defaultlist)
+        self.assertTrue(hasattr(settings, "defaultlist"))
+        logging.info('Timezone: %s' % settings.timezone)
+        self.assertEqual(settings.timezone, u"Asia/Tokyo")
+        logging.info('Date format: %s' % settings.dateformat)
+        self.assertEqual(settings.dateformat, 1)
+        logging.info('Time format: %s' % settings.timeformat)
+        self.assertEqual(settings.timeformat, 1)
+
+class TestGroupUser(TestBase):
+    def add_contacts(self):
+        """rtm.contacts.add"""
+
+        for username in [user['username'] for user in self.testusers]:
+            timeline = self.rtm.timelines.create()
+            logging.debug(timeline)
+            contact = self.rtm.contacts.add(contact=username, timeline=timeline)
+            self.assertEqual(contact.username, username)
+
+            logging.info('Contact: %s %s %s' % (
+                contact.username, contact.id, contact.fullname))
+        self.contacts = self.rtm.contacts.getList()
+
+        self.contacts = self.rtm.contacts.getList()
+        for username in [user['username'] for user in self.testusers]:
+            self.assertTrue(username in \
+                    [contact.username for contact in self.contacts])
+
+    def delete_contacts(self):
+        """rtm.contacts.delete"""
+
+        self.contacts = self.rtm.contacts.getList()
+        for username in [user['username'] for user in self.testusers]:
+            for contact in self.contacts:
+                if username == contact.username:
+                    logging.info('Delete contact: %s %s' % (
+                        username, contact.id))
+                    timeline = self.rtm.timelines.create()
+                    logging.debug(timeline)
+                    self.assertTrue(self.rtm.contacts.delete(
+                        contact_id=contact.id, timeline=timeline))
+
+        self.contacts = self.rtm.contacts.getList()
+        for username in [user['username'] for user in self.testusers]:
+            self.assertTrue(username not in \
+                    [contact.username for contact in self.contacts])
+
+    def add_groups(self):
+        """rtm.groups.add"""
+
+        for groupname in self.groupnames:
+            timeline = self.rtm.timelines.create()
+            logging.debug(timeline)
+            group = self.rtm.groups.add(group=groupname, timeline=timeline)
+            self.assertEqual(group.name, groupname)
+
+            logging.info('Group: %s %s' % (
+                group.name, group.id))
+
+        self.groups = self.rtm.groups.getList()
+        for groupname in self.groupnames:
+            self.assertTrue(groupname in [group.name for group in self.groups])
+
+    def delete_groups(self):
+        """rtm.groups.delete"""
+
+        self.groups = self.rtm.groups.getList()
+        for groupname in self.groupnames:
+            for group in self.groups:
+                if groupname == group.name:
+                    logging.info('Delete group: %s %s' % (
+                        groupname, group.id))
+                    timeline = self.rtm.timelines.create()
+                    logging.debug(timeline)
+                    self.assertTrue(self.rtm.groups.delete(
+                        group_id=group.id, timeline=timeline))
+
+        self.groups = self.rtm.groups.getList()
+        for groupname in self.groupnames:
+            self.assertTrue(groupname not in [group.name for group in self.groups])
+
+    def add_contact_to_group(self):
+        """rtm.groups.addContact"""
+
+        group = self.groups[0]
+        for contact in self.contacts:
+            timeline = self.rtm.timelines.create()
+            logging.debug(timeline)
+            self.assertTrue(self.rtm.groups.addContact(
+                group_id=group.id, contact_id=contact.id,
+                timeline=timeline))
+
+        self.groups = self.rtm.groups.getList()
+        group = self.groups[0]
+        for contact in self.contacts:
+            self.assertTrue(contact.id in \
+                    [contact.id for contact in group.contacts])
+
+    def remove_contact_from_group(self):
+        """rtm.groups.removeContact"""
+
+        group = self.groups[0]
+        for contact in self.contacts:
+            timeline = self.rtm.timelines.create()
+            logging.debug(timeline)
+            self.assertTrue(self.rtm.groups.removeContact(
+                group_id=group.id, contact_id=contact.id,
+                timeline=timeline))
+
+        self.groups = self.rtm.groups.getList()
+        group = self.groups[0]
+        for contact in self.contacts:
+            self.assertTrue(contact.id not in \
+                    [contact.id for contact in group.contacts])
+
+    def test(self):
+        """rtm.groups, rtm.contacts"""
+
+        # Initialize
+        self.delete_contacts()
+        self.delete_groups()
+
+        # Add contact & group
+        self.add_contacts()
+        self.add_groups()
+
+        # Add contact to group
+        self.add_contact_to_group()
+
+        # Remove contact from group
+        self.remove_contact_from_group()
+
+        # Initialize
+        self.delete_contacts()
+        self.delete_groups()
+
+class TestListsTasksNotes(TestBase):
+
+    def get_lists(self):
+        self.lists = self.rtm.lists.getList()
+        for _list in self.lists:
+            logging.info('List: %s %s %s %s %s %s %s %s' % (
+                _list.name, 
+                _list.id, 
+                _list.locked, 
+                _list.deleted, 
+                _list.archived, 
+                _list.position, 
+                _list.sort_order, 
+                _list.smart))
+
+    def add_list(self):
+        self.assertTrue(len([_list.id for _list in self.lists \
+                if _list.name == self.test_list and not _list.deleted]) == 0)
+
+        timeline = self.rtm.timelines.create()
+        logging.debug(timeline)
+        self._list = self.rtm.lists.add(name=self.test_list,
+                timeline=timeline)
+        logging.debug('Add list %s %s' % (self._list.name, self._list.id))
+
+        # Refresh
+        self.lists = self.rtm.lists.getList()
+        self.assertTrue(len([_list.id for _list in self.lists \
+                if _list.name == self.test_list and not _list.deleted]) == 1)
+        self.assertEqual(self._list.name, self.test_list)
+        self.assertEqual(self._list.deleted, False)
+        self.assertEqual(self._list.archived, False)
+        self.assertEqual(self._list.locked, False)
+
+    def delete_list(self):
+        for _list in self.lists:
+            if _list.name == self.test_list:
+                timeline = self.rtm.timelines.create()
+                logging.debug(timeline)
+                self._list = self.rtm.lists.delete(list_id=_list.id,
+                        timeline=timeline)
+                logging.debug('Delete list %s %s' % (_list.name, _list.id))
+                self.assertEqual(self._list.name, self.test_list)
+                self.assertEqual(self._list.deleted, True)
+
+        self.lists = self.rtm.lists.getList()
+        self.assertTrue(len([_list.id for _list in self.lists \
+                if _list.name == self.test_list and not _list.deleted]) == 0)
+
+    def get_list_id_by_name(self, name):
+        for _list in self.lists:
+            if _list.name == name:
+                return _list.id
+        return None
+
+    def default_list(self):
+        # Get default list ID from settings.
+        settings = self.rtm.settings.getList()
+        if not settings.defaultlist:
+            self.defaultlist_bakcup = self.get_list_id_by_name(u'Inbox')
+        else:
+            self.defaultlist_bakcup = settings.defaultlist
+        logging.info('Default list: %s' % self.defaultlist_bakcup)
+
+        # Change default list
+        timeline = self.rtm.timelines.create()
+        logging.debug(timeline)
+        self.rtm.lists.setDefaultList(list_id=self._list.id,
+                timeline=timeline)
+        settings = self.rtm.settings.getList()
+        self.assertEqual(settings.defaultlist, self._list.id)
+        logging.info('Default list: %s' % settings.defaultlist)
+
+        # Back to default list
+        timeline = self.rtm.timelines.create()
+        logging.debug(timeline)
+        self.rtm.lists.setDefaultList(list_id=self.defaultlist_bakcup,
+                timeline=timeline)
+        settings = self.rtm.settings.getList()
+        self.assertEqual(settings.defaultlist, self.defaultlist_bakcup)
+        logging.info('Default list: %s' % settings.defaultlist)
+
+    def archive_list(self):
+        self.assertEqual(self._list.archived, False)
+
+        # Archive list
+        timeline = self.rtm.timelines.create()
+        logging.debug(timeline)
+        self._list = self.rtm.lists.archive(list_id=self._list.id,
+                timeline=timeline)
+        self.assertEqual(self._list.archived, True)
+
+        # Unarchive list
+        timeline = self.rtm.timelines.create()
+        logging.debug(timeline)
+        self._list = self.rtm.lists.unarchive(list_id=self._list.id,
+                timeline=timeline)
+        self.assertEqual(self._list.archived, False)
+
+    def set_name_list(self):
+        new_test_list = u'New Milky テスト'
+        self.assertEqual(self._list.name, self.test_list)
+        logging.info('List name: %s' % self._list.name)
+
+        # Set new name
+        timeline = self.rtm.timelines.create()
+        logging.debug(timeline)
+        self._list = self.rtm.lists.setName(list_id=self._list.id,
+                name=new_test_list,
+                timeline=timeline)
+        self.assertEqual(self._list.name, new_test_list)
+        logging.info('List name: %s' % self._list.name)
+
+        # Set back old name
+        timeline = self.rtm.timelines.create()
+        logging.debug(timeline)
+        self._list = self.rtm.lists.setName(list_id=self._list.id,
+                name=self.test_list,
+                timeline=timeline)
+        self.assertEqual(self._list.name, self.test_list)
+        logging.info('List name: %s' % self._list.name)
+
+    def get_tasks(self):
+        self.tasks = self.rtm.tasks.getList()
+
+        logging.info('Tasks: %s' % self.tasks.rev)
+        for tasklist in self.tasks.lists:
+            logging.info('List: %s' % tasklist.id)
+            for taskseries in tasklist.taskseries:
+                logging.info('TaskSeries: %s %s %s %s %s %s %s' % (
+                    taskseries.name, taskseries.id, 
+                    taskseries.url,
+                    taskseries.source,
+                    taskseries.location_id, 
+                    taskseries.created, taskseries.modified))
+                for note in taskseries.notes:
+                    logging.debug('Note: %s %s %s %s %s' % (
+                        note.title, note.text, note.id, 
+                        note.created, note.modified))
+                for participant in taskseries.participants:
+                    logging.debug('Participant: %s %s %s' % (
+                        participant.fullname, 
+                        participant.id, participant.username))
+                logging.debug('Tags: %s' % ', '.join(taskseries.tags))
+
+                for task in taskseries.task:
+                    logging.debug('Task: %s %s %s %s %s %s %s %s %s' % (
+                        task.id, task.due, task.has_due_time, 
+                        task.added, task.deleted, task.completed, 
+                        task.priority, task.postponed, task.estimate))
+
+    def add_task(self):
+        self.list_tasks = self.rtm.tasks.getList(list_id=self._list.id)
+        self.assertEqual(len(self.list_tasks.lists[0].taskseries), 0)
+        # Test task 1
+        timeline = self.rtm.timelines.create()
+        logging.debug(timeline)
+        self.test_task_1 = self.rtm.tasks.add(
+                list_id=self._list.id,
+                name=u'テスト 1 (Normal)',
+                timeline=timeline)
+
+        # Test task 2 (Smart Add)
+        timeline = self.rtm.timelines.create()
+        logging.debug(timeline)
+        self.test_task_2 = self.rtm.tasks.add(
+                list_id=self._list.id,
+                name=u'テスト 2 (Smart Add) ^tomorrow !1 #test @office *weekly =10 min http://twitter.com/Surgo',
+                parse=1,
+                timeline=timeline)
+        self.list_tasks = self.rtm.tasks.getList(list_id=self._list.id)
+        self.assertEqual(len(self.list_tasks.lists[0].taskseries), 2)
+
+    def listing_task(self):
+        logging.info('List: %s' % self.test_task_1.id)
+        self.assertEqual(self.test_task_1.id, self._list.id)
+
+        # Move list to default_list (Maybe Inbox).
+        timeline = self.rtm.timelines.create()
+        logging.debug(timeline)
+        self.test_task_1 = self.rtm.tasks.moveTo(
+                from_list_id=self._list.id,
+                to_list_id=self.defaultlist_bakcup,
+                taskseries_id=self.test_task_1.taskseries[0].id,
+                task_id=self.test_task_1.taskseries[0].task[0].id,
+                timeline=timeline)
+        logging.info('List: %s' % self.test_task_1.id)
+        self.assertEqual(self.test_task_1.id, self.defaultlist_bakcup)
+
+        # Move list to Milky テスト.
+        timeline = self.rtm.timelines.create()
+        logging.debug(timeline)
+        self.test_task_1 = self.rtm.tasks.moveTo(
+                from_list_id=self.defaultlist_bakcup,
+                to_list_id=self._list.id,
+                taskseries_id=self.test_task_1.taskseries[0].id,
+                task_id=self.test_task_1.taskseries[0].task[0].id,
+                timeline=timeline)
+        logging.info('List: %s' % self.test_task_1.id)
+        self.assertEqual(self.test_task_1.id, self._list.id)
+
+    def tagging_task(self):
+        # Add tags
+        tags = [u'テスト', u'漢字', u'English', u'A', ]
+        logging.info('Tags: %s' % ', '.join(self.test_task_1.taskseries[0].tags))
+        [self.assertTrue(tag.lower() not in self.test_task_1.taskseries[0].tags) \
+                for tag in tags]
+        timeline = self.rtm.timelines.create()
+        logging.debug(timeline)
+        self.test_task_1 = self.rtm.tasks.addTags(
+                list_id=self._list.id,
+                taskseries_id=self.test_task_1.taskseries[0].id,
+                task_id=self.test_task_1.taskseries[0].task[0].id,
+                tags=','.join(tags),
+                timeline=timeline)
+        logging.info('Tags: %s' % ', '.join(self.test_task_1.taskseries[0].tags))
+        [self.assertTrue(tag.lower() in self.test_task_1.taskseries[0].tags) \
+                for tag in tags]
+
+        # Remove tags
+        tags = [u'漢字', u'A', ]
+        remove_tags = [u'テスト', u'English', ]
+        timeline = self.rtm.timelines.create()
+        logging.debug(timeline)
+        self.test_task_1 = self.rtm.tasks.removeTags(
+                list_id=self._list.id,
+                taskseries_id=self.test_task_1.taskseries[0].id,
+                task_id=self.test_task_1.taskseries[0].task[0].id,
+                tags=','.join(remove_tags),
+                timeline=timeline)
+        logging.info('Tags: %s' % ', '.join(self.test_task_1.taskseries[0].tags))
+        [self.assertTrue(tag.lower() not in self.test_task_1.taskseries[0].tags) \
+                for tag in remove_tags]
+        [self.assertTrue(tag.lower() in self.test_task_1.taskseries[0].tags) \
+                for tag in tags]
+
+        # Reset tags
+        replace_tags = [u'置換', u'replace', ]
+        timeline = self.rtm.timelines.create()
+        logging.debug(timeline)
+        self.test_task_1 = self.rtm.tasks.setTags(
+                list_id=self._list.id,
+                taskseries_id=self.test_task_1.taskseries[0].id,
+                task_id=self.test_task_1.taskseries[0].task[0].id,
+                tags=','.join(replace_tags),
+                timeline=timeline)
+        logging.info('Tags: %s' % ', '.join(self.test_task_1.taskseries[0].tags))
+        [self.assertTrue(tag.lower() not in self.test_task_1.taskseries[0].tags) \
+                for tag in tags]
+        [self.assertTrue(tag.lower() in self.test_task_1.taskseries[0].tags) \
+                for tag in replace_tags]
+
+    def prioritize_task(self):
+        # Set priority 3
+        logging.info('Priority: %s' % self.test_task_1.taskseries[0].task[0].priority)
+        self.assertTrue(self.test_task_1.taskseries[0].task[0].priority is None)
+        timeline = self.rtm.timelines.create()
+        logging.debug(timeline)
+        self.test_task_1 = self.rtm.tasks.setPriority(
+                list_id=self._list.id,
+                taskseries_id=self.test_task_1.taskseries[0].id,
+                task_id=self.test_task_1.taskseries[0].task[0].id,
+                priority=3,
+                timeline=timeline)
+        logging.info('Priority: %s' % self.test_task_1.taskseries[0].task[0].priority)
+        self.assertEqual(self.test_task_1.taskseries[0].task[0].priority, 3)
+        # Move priority 2
+        timeline = self.rtm.timelines.create()
+        logging.debug(timeline)
+        self.test_task_1 = self.rtm.tasks.movePriority(
+                list_id=self._list.id,
+                taskseries_id=self.test_task_1.taskseries[0].id,
+                task_id=self.test_task_1.taskseries[0].task[0].id,
+                direction=u'up',
+                timeline=timeline)
+        logging.info('Priority: %s' % self.test_task_1.taskseries[0].task[0].priority)
+        self.assertEqual(self.test_task_1.taskseries[0].task[0].priority, 2)
+        # Move priority 3
+        timeline = self.rtm.timelines.create()
+        logging.debug(timeline)
+        self.test_task_1 = self.rtm.tasks.movePriority(
+                list_id=self._list.id,
+                taskseries_id=self.test_task_1.taskseries[0].id,
+                task_id=self.test_task_1.taskseries[0].task[0].id,
+                direction=u'down',
+                timeline=timeline)
+        logging.info('Priority: %s' % self.test_task_1.taskseries[0].task[0].priority)
+        self.assertEqual(self.test_task_1.taskseries[0].task[0].priority, 3)
+        # Unset priority
+        timeline = self.rtm.timelines.create()
+        logging.debug(timeline)
+        self.test_task_1 = self.rtm.tasks.setPriority(
+                list_id=self._list.id,
+                taskseries_id=self.test_task_1.taskseries[0].id,
+                task_id=self.test_task_1.taskseries[0].task[0].id,
+                priority=u'N',
+                timeline=timeline)
+        logging.info('Priority: %s' % self.test_task_1.taskseries[0].task[0].priority)
+        self.assertTrue(self.test_task_1.taskseries[0].task[0].priority is None)
+
+    def addinfo_task(self):
+        # Change name
+        new_name = u'テスト !"#$%&\'()=-~|^\\{}`:*+;_?/>.<,'
+        logging.info('Name: %s' % self.test_task_1.taskseries[0].name)
+        self.assertEqual(self.test_task_1.taskseries[0].name, u'テスト 1 (Normal)')
+        timeline = self.rtm.timelines.create()
+        logging.debug(timeline)
+        self.test_task_1 = self.rtm.tasks.setName(
+                list_id=self._list.id,
+                taskseries_id=self.test_task_1.taskseries[0].id,
+                task_id=self.test_task_1.taskseries[0].task[0].id,
+                name=new_name,
+                timeline=timeline)
+        logging.info('Name: %s' % self.test_task_1.taskseries[0].name)
+        self.assertEqual(self.test_task_1.taskseries[0].name, new_name)
+
+        # Set location
+        location = self.rtm.locations.getList()[0]
+        logging.info('Set location to: %s (%s)' % (location.name, location.id))
+        logging.info('Location: %s' % self.test_task_1.taskseries[0].location_id)
+        timeline = self.rtm.timelines.create()
+        logging.debug(timeline)
+        self.test_task_1 = self.rtm.tasks.setLocation(
+                list_id=self._list.id,
+                taskseries_id=self.test_task_1.taskseries[0].id,
+                task_id=self.test_task_1.taskseries[0].task[0].id,
+                location_id=location.id,
+                timeline=timeline)
+        logging.info('Location: %s' % self.test_task_1.taskseries[0].location_id)
+        self.assertEqual(self.test_task_1.taskseries[0].location_id, location.id)
+
+        # Set url
+        url = u'http://www.rememberthemilk.com/'
+        logging.info('Set url to: %s' % url)
+        logging.info('URL: %s' % self.test_task_1.taskseries[0].url)
+        timeline = self.rtm.timelines.create()
+        logging.debug(timeline)
+        self.test_task_1 = self.rtm.tasks.setURL(
+                list_id=self._list.id,
+                taskseries_id=self.test_task_1.taskseries[0].id,
+                task_id=self.test_task_1.taskseries[0].task[0].id,
+                url=url,
+                timeline=timeline)
+        logging.info('URL: %s' % self.test_task_1.taskseries[0].url)
+        self.assertEqual(self.test_task_1.taskseries[0].url, url)
+
+    def scheduling_task(self):
+        logging.info('Due date: %s' % self.test_task_1.taskseries[0].task[0].due)
+        logging.info('Has due time: %s' % self.test_task_1.taskseries[0].task[0].has_due_time)
+        self.assertTrue(self.test_task_1.taskseries[0].task[0].due is None)
+        self.assertTrue(not self.test_task_1.taskseries[0].task[0].has_due_time)
+
+        # Set due date
+        today = datetime.date.today()
+        due_date = datetime.datetime(today.year, today.month, today.day, 0, 0) + datetime.timedelta(days=1)
+        timeline = self.rtm.timelines.create()
+        logging.debug(timeline)
+        self.test_task_1 = self.rtm.tasks.setDueDate(
+                list_id=self._list.id,
+                taskseries_id=self.test_task_1.taskseries[0].id,
+                task_id=self.test_task_1.taskseries[0].task[0].id,
+                due=due_date.strftime("%Y-%m-%dT%H:%M:%SZ"),
+                timeline=timeline)
+        logging.info('Due date: %s' % self.test_task_1.taskseries[0].task[0].due)
+        logging.info('Has due time: %s' % self.test_task_1.taskseries[0].task[0].has_due_time)
+        self.assertEqual(due_date, self.test_task_1.taskseries[0].task[0].due)
+        self.assertTrue(not self.test_task_1.taskseries[0].task[0].has_due_time)
+
+        # Set due date with due time
+        due_date = datetime.datetime(due_date.year, due_date.month, due_date.day, 12, 30)
+        timeline = self.rtm.timelines.create()
+        logging.debug(timeline)
+        self.test_task_1 = self.rtm.tasks.setDueDate(
+                list_id=self._list.id,
+                taskseries_id=self.test_task_1.taskseries[0].id,
+                task_id=self.test_task_1.taskseries[0].task[0].id,
+                due=due_date.strftime("%Y-%m-%dT%H:%M:%SZ"),
+                has_due_time=milky.HAS_DUE_TIME,
+                timeline=timeline)
+        logging.info('Due date: %s' % self.test_task_1.taskseries[0].task[0].due)
+        logging.info('Has due time: %s' % self.test_task_1.taskseries[0].task[0].has_due_time)
+        self.assertEqual(due_date, self.test_task_1.taskseries[0].task[0].due)
+        self.assertTrue(self.test_task_1.taskseries[0].task[0].has_due_time)
+
+        # Postpone
+        due_date = due_date + datetime.timedelta(days=1)
+        timeline = self.rtm.timelines.create()
+        logging.debug(timeline)
+        self.test_task_1 = self.rtm.tasks.postpone(
+                list_id=self._list.id,
+                taskseries_id=self.test_task_1.taskseries[0].id,
+                task_id=self.test_task_1.taskseries[0].task[0].id,
+                timeline=timeline)
+        logging.info('Due date: %s' % self.test_task_1.taskseries[0].task[0].due)
+        logging.info('Has due time: %s' % self.test_task_1.taskseries[0].task[0].has_due_time)
+        self.assertEqual(due_date, self.test_task_1.taskseries[0].task[0].due)
+        self.assertTrue(self.test_task_1.taskseries[0].task[0].has_due_time)
+
+        # Estimate (所要時間)
+        logging.info('Estimate: %s' % self.test_task_1.taskseries[0].task[0].estimate)
+        estimate='0.5 days'
+        delta = datetime.timedelta(days=0.5)
+        self.assertTrue(self.test_task_1.taskseries[0].task[0].estimate is None)
+        timeline = self.rtm.timelines.create()
+        logging.debug(timeline)
+        self.test_task_1 = self.rtm.tasks.setEstimate(
+                list_id=self._list.id,
+                taskseries_id=self.test_task_1.taskseries[0].id,
+                task_id=self.test_task_1.taskseries[0].task[0].id,
+                estimate=estimate,
+                timeline=timeline)
+        logging.info('Estimate: %s' % self.test_task_1.taskseries[0].task[0].estimate)
+        self.assertEqual(delta, self.test_task_1.taskseries[0].task[0].estimate)
+
+        # Recurrence (再起)
+        """Recurrence examples:
+
+            Every Tuesday - {"every":"1","$t":"FREQ=WEEKLY;INTERVAL=1;BYDAY=TU"}
+            Every Monday and Wednesday - {"every":"1","$t":"FREQ=WEEKLY;INTERVAL=1;BYDAY=MO,WE"}
+            Every weekday - {"every":"1","$t":"FREQ=WEEKLY;INTERVAL=1;BYDAY=MO,TU,WE,TH,FR"}
+            Every day - {"every":"1","$t":"FREQ=DAILY;INTERVAL=1"}
+            Every week - {"every":"1","$t":"FREQ=WEEKLY;INTERVAL=1"}
+            Every 2 weeks - {"every":"1","$t":"FREQ=WEEKLY;INTERVAL=2"}
+            Every month - {"every":"1","$t":"FREQ=MONTHLY;INTERVAL=1"}
+            Every 6 months - {"every":"1","$t":"FREQ=MONTHLY;INTERVAL=6"}
+            Every year - {"every":"1","$t":"FREQ=YEARLY;INTERVAL=1"}
+            Every month on the 4th - {"every":"1","$t":"FREQ=MONTHLY;INTERVAL=1;BYMONTHDAY=4"}
+            Every month on the 3rd Tuesday - {"every":"1","$t":"FREQ=MONTHLY;INTERVAL=1;BYDAY=3TU"}
+            Every month on the last Monday - {"every":"1","$t":"FREQ=MONTHLY;INTERVAL=1;BYDAY=-1MO"}
+            Every month on the 2nd last Friday - {"every":"1","$t":"FREQ=MONTHLY;INTERVAL=1;BYDAY=-2FR"}
+            Every week until 1/1/2007 - {"every":"1","$t":"FREQ=WEEKLY;INTERVAL=1;UNTIL=20070101T000000"}
+            Every week for 20 times - {"every":"1","$t":"FREQ=WEEKLY;INTERVAL=1;COUNT=20"}
+            Every year on the last Monday - Contain bags.
+        """
+        logging.info('Recurrence: %s' % self.test_task_1.taskseries[0].rrule)
+        self.assertTrue(self.test_task_1.taskseries[0].rrule is None)
+        timeline = self.rtm.timelines.create()
+        logging.debug(timeline)
+        self.test_task_1 = self.rtm.tasks.setRecurrence(
+                list_id=self._list.id,
+                taskseries_id=self.test_task_1.taskseries[0].id,
+                task_id=self.test_task_1.taskseries[0].task[0].id,
+                repeat='Every week for 20 times',
+                timeline=timeline)
+        logging.info('Recurrence: %s %s %s %s %s %s %s' % (
+            self.test_task_1.taskseries[0].rrule.every,
+            self.test_task_1.taskseries[0].rrule.freq,
+            self.test_task_1.taskseries[0].rrule.interval,
+            self.test_task_1.taskseries[0].rrule.byday,
+            self.test_task_1.taskseries[0].rrule.bymonthday,
+            self.test_task_1.taskseries[0].rrule.until,
+            self.test_task_1.taskseries[0].rrule.count,))
+        self.assertEqual(self.test_task_1.taskseries[0].rrule.every, 1)
+        self.assertEqual(self.test_task_1.taskseries[0].rrule.freq, milky.FREQ_WEEKLY)
+        self.assertEqual(self.test_task_1.taskseries[0].rrule.interval, 1)
+        self.assertEqual(self.test_task_1.taskseries[0].rrule.byday, None)
+        self.assertEqual(self.test_task_1.taskseries[0].rrule.bymonthday, None)
+        self.assertEqual(self.test_task_1.taskseries[0].rrule.until, None)
+        self.assertEqual(self.test_task_1.taskseries[0].rrule.count, 20)
+
+        timeline = self.rtm.timelines.create()
+        logging.debug(timeline)
+        self.test_task_1 = self.rtm.tasks.setRecurrence(
+                list_id=self._list.id,
+                taskseries_id=self.test_task_1.taskseries[0].id,
+                task_id=self.test_task_1.taskseries[0].task[0].id,
+                repeat='Every month on the 3rd Tuesday',
+                timeline=timeline)
+        logging.info('Recurrence: %s %s %s %s %s %s %s' % (
+            self.test_task_1.taskseries[0].rrule.every,
+            self.test_task_1.taskseries[0].rrule.freq,
+            self.test_task_1.taskseries[0].rrule.interval,
+            self.test_task_1.taskseries[0].rrule.byday,
+            self.test_task_1.taskseries[0].rrule.bymonthday,
+            self.test_task_1.taskseries[0].rrule.until,
+            self.test_task_1.taskseries[0].rrule.count,))
+        self.assertEqual(self.test_task_1.taskseries[0].rrule.every, 1)
+        self.assertEqual(self.test_task_1.taskseries[0].rrule.freq, milky.FREQ_MONTHLY)
+        self.assertEqual(self.test_task_1.taskseries[0].rrule.interval, 1)
+        self.assertEqual(self.test_task_1.taskseries[0].rrule.byday, u'3TU')
+        self.assertEqual(self.test_task_1.taskseries[0].rrule.bymonthday, None)
+        self.assertEqual(self.test_task_1.taskseries[0].rrule.until, None)
+        self.assertEqual(self.test_task_1.taskseries[0].rrule.count, None)
+
+    def notes_task(self):
+        logging.info('Note: %s' % self.test_task_1.taskseries[0].notes)
+        self.assertTrue(len(self.test_task_1.taskseries[0].notes) == 0)
+
+        # Add note
+        note_title = u"Note test"
+        note_text = u"ABC\nDEF\nGHI"
+        timeline = self.rtm.timelines.create()
+        logging.debug(timeline)
+        note = self.rtm.tasksnotes.add(
+                list_id=self._list.id,
+                taskseries_id=self.test_task_1.taskseries[0].id,
+                task_id=self.test_task_1.taskseries[0].task[0].id,
+                note_title=note_title,
+                note_text=note_text,
+                timeline=timeline)
+        logging.info('Note: %s %s %s %s %s' % (
+            note.title,
+            note.text,
+            note.id,
+            note.created,
+            note.modified))
+        self.assertEqual(note.title, note_title)
+        self.assertEqual(note.text, note_text)
+
+        # Edit note
+        note_title = u"ノートテスト"
+        note_text = u"あいうえお\nかきくけこ\nさしすせそ"
+        timeline = self.rtm.timelines.create()
+        logging.debug(timeline)
+        note = self.rtm.tasksnotes.edit(
+                note_id=note.id,
+                note_title=note_title,
+                note_text=note_text,
+                timeline=timeline)
+        print note
+        logging.info('Note: %s %s %s %s %s' % (
+            note.title,
+            note.text,
+            note.id,
+            note.created,
+            note.modified))
+        self.assertEqual(note.title, note_title)
+        self.assertEqual(note.text, note_text)
+
+        # Delete note
+        timeline = self.rtm.timelines.create()
+        logging.debug(timeline)
+        self.assertTrue(self.rtm.tasksnotes.delete(
+                note_id=note.id,
+                timeline=timeline))
+
+    def completion_task(self):
+        # Complete
+        logging.info('Completed: %s' % self.test_task_1.taskseries[0].task[0].completed)
+        self.assertTrue(self.test_task_1.taskseries[0].task[0].completed is None)
+        timeline = self.rtm.timelines.create()
+        logging.debug(timeline)
+        self.test_task_1 = self.rtm.tasks.complete(
+                list_id=self._list.id,
+                taskseries_id=self.test_task_1.taskseries[0].id,
+                task_id=self.test_task_1.taskseries[0].task[0].id,
+                timeline=timeline)
+        logging.info('Completed: %s' % self.test_task_1.taskseries[0].task[0].completed)
+        self.assertTrue(self.test_task_1.taskseries[0].task[0].completed is not None)
+
+        # Uncomplete
+        timeline = self.rtm.timelines.create()
+        logging.debug(timeline)
+        self.test_task_1 = self.rtm.tasks.uncomplete(
+                list_id=self._list.id,
+                taskseries_id=self.test_task_1.taskseries[0].id,
+                task_id=self.test_task_1.taskseries[0].task[0].id,
+                timeline=timeline)
+        logging.info('Completed: %s' % self.test_task_1.taskseries[0].task[0].completed)
+        self.assertTrue(self.test_task_1.taskseries[0].task[0].completed is None)
+
+    def delete_tasks(self):
+        pass
+
+    def test(self):
+        # Get lists and tasks
+        self.get_lists()
+        self.get_tasks()
+
+        # Initialize
+        self.delete_list()
+        self.delete_tasks()
+
+        # For list
+        self.add_list()
+        self.default_list()
+        self.archive_list()
+        self.set_name_list()
+
+        # For tasks
+        self.add_task()
+        self.listing_task()
+        self.tagging_task()
+        self.prioritize_task()
+        self.addinfo_task()
+        self.scheduling_task()
+        self.completion_task()
+        self.notes_task()
+
+        # Initialize
+        self.delete_tasks()
+        self.delete_list()
+
+if __name__ == '__main__':
+    logging.basicConfig(level=logging.INFO, filename='test.log')
+
+    TEST_CASES = [
+            # TestAuthFrob,
+            # TestAuthToken,
+            TestTest,
+            TestReflection,
+            TestTimezones,
+            # TestTime, # Not worked?
+            TestLocations,
+            TestSettings,
+            TestGroupUser,
+            TestListsTasksNotes,
+            ]
+    for test_cls in TEST_CASES:
+        testsuite = unittest.TestLoader().loadTestsFromTestCase(test_cls)
+        unittest.TextTestRunner(verbosity=2).run(testsuite)
+