Repository: treasure-data/td-client-python

Issue:
Missing created_time and user_name in list_schedules api
Schedule API returns the following for each scheduled.
But, created_time and user_name are missing

```
$ curl -H "AUTHORIZATION: TD1 XXXXX"  "http://api.treasuredata.com/v3/schedule/list"
...
{
"name":"xxx",
"cron":null,
"timezone":"UTC",
"delay":0,
"created_at":"2016-08-15T23:03:59Z",
"type":"presto",
"query":"xxxx",
"database":"api_production",
"user_name":"YYYY",
"priority":0,
"retry_limit":0,
"result":"",
"next_time":null
}
```

https://github.com/treasure-data/td-client-python/blob/master/tdclient/schedule_api.py#L52-L63



Comments:

Patch:
diff --git a/tdclient/client.py b/tdclient/client.py
index 691b225..85c2258 100644
--- a/tdclient/client.py
+++ b/tdclient/client.py
@@ -527,10 +527,7 @@ class Client(object):
         [:class:`tdclient.models.Schedule`]
         """
         result = self.api.list_schedules()
-        def schedule(m):
-            name,cron,query,database,result_url,timezone,delay,next_time,priority,retry_limit,org_name = m
-            return models.Schedule(self, name, cron, query, database, result_url, timezone, delay, next_time, priority, retry_limit, org_name)
-        return [ schedule(m) for m in result ]
+        return [ models.Schedule(self, m.get("name"), m.get("cron"), m.get("query"), **m) for m in result ]
 
     def update_schedule(self, name, params=None):
         """
diff --git a/tdclient/schedule_api.py b/tdclient/schedule_api.py
index 8d9ec3b..02e7106 100644
--- a/tdclient/schedule_api.py
+++ b/tdclient/schedule_api.py
@@ -50,17 +50,12 @@ class ScheduleAPI(object):
                 self.raise_error("List schedules failed", res, body)
             js = self.checked_json(body, ["schedules"])
             def schedule(m):
-                name = m.get("name")
-                cron = m.get("cron")
-                query = m.get("query")
-                database = m.get("database")
-                result_url = m.get("result")
-                timezone = m.get("timezone", "UTC")
-                delay = m.get("delay")
-                next_time = self._parsedate(self.get_or_else(m, "next_time", "1970-01-01T00:00:00Z"), "%Y-%m-%dT%H:%M:%SZ")
-                priority = m.get("priority")
-                retry_limit = m.get("retry_limit")
-                return (name, cron, query, database, result_url, timezone, delay, next_time, priority, retry_limit, None) # same as database
+                m = dict(m)
+                if "timezone" not in m:
+                    m["timezone"] = "UTC"
+                m["created_at"] = self._parsedate(self.get_or_else(m, "created_at", "1970-01-01T00:00:00Z"), "%Y-%m-%dT%H:%M:%SZ")
+                m["next_time"] = self._parsedate(self.get_or_else(m, "next_time", "1970-01-01T00:00:00Z"), "%Y-%m-%dT%H:%M:%SZ")
+                return m
             return [ schedule(m) for m in js["schedules"] ]
 
     def update_schedule(self, name, params=None):
diff --git a/tdclient/schedule_model.py b/tdclient/schedule_model.py
index 104550d..888ae08 100644
--- a/tdclient/schedule_model.py
+++ b/tdclient/schedule_model.py
@@ -24,19 +24,27 @@ class Schedule(Model):
     """Schedule on Treasure Data Service
     """
 
-    def __init__(self, client, name, cron, query, database=None, result_url=None, timezone=None, delay=None, next_time=None, priority=None, retry_limit=None, org_name=None):
+    def __init__(self, client, name, cron, query, **kwargs):
         super(Schedule, self).__init__(client)
         self._name = name
         self._cron = cron
+        self._timezone = kwargs.get("timezone")
+        self._delay = kwargs.get("delay")
+        self._created_at = kwargs.get("created_at")
+        self._type = kwargs.get("type")
         self._query = query
-        self._database = database
-        self._result_url = result_url
-        self._timezone = timezone
-        self._delay = delay
-        self._next_time = next_time
-        self._priority = priority
-        self._retry_limit = retry_limit
-        self._org_name = org_name
+        self._database = kwargs.get("database")
+        self._user_name = kwargs.get("user_name")
+        self._priority = kwargs.get("priority")
+        self._retry_limit = kwargs.get("retry_limit")
+        if "result_url" in kwargs:
+            # backward compatibility for td-client-python < 0.6.0
+            # TODO: remove this code if not necessary with fixing test
+            self._result = kwargs.get("result_url")
+        else:
+            self._result = kwargs.get("result")
+        self._next_time = kwargs.get("next_time")
+        self._org_name = kwargs.get("org_name")
 
     @property
     def name(self):
@@ -68,7 +76,7 @@ class Schedule(Model):
     def result_url(self):
         """The result output configuration in URL form of a scheduled job
         """
-        return self._result_url
+        return self._result
 
     @property
     def timezone(self):
@@ -88,7 +96,10 @@ class Schedule(Model):
     def priority(self):
         """The priority of a scheduled job
         """
-        return self._priority
+        if self._priority in Job.JOB_PRIORITY:
+            return Job.JOB_PRIORITY[self._priority]
+        else:
+            return str(self._priority)
 
     @property
     def retry_limit(self):
@@ -111,6 +122,27 @@ class Schedule(Model):
         """
         return self._next_time
 
+    @property
+    def created_at(self):
+        """
+        TODO: add docstring
+        """
+        return self._created_at
+
+    @property
+    def type(self):
+        """
+        TODO: add docstring
+        """
+        return self._type
+
+    @property
+    def user_name(self):
+        """
+        TODO: add docstring
+        """
+        return self._user_name
+
     def run(self, time, num=None):
         """Run a scheduled job
         """


Test patch:
diff --git a/tdclient/test/schedule_api_test.py b/tdclient/test/schedule_api_test.py
index b7f18bb..faca3d1 100644
--- a/tdclient/test/schedule_api_test.py
+++ b/tdclient/test/schedule_api_test.py
@@ -70,13 +70,54 @@ def test_delete_schedule_success():
 
 def test_list_schedules_success():
     td = api.API("APIKEY")
-    # TODO: should be replaced by wire dump
     body = b"""
         {
             "schedules":[
-                {"name":"foo","cron":"* * * * *","query":"SELECT COUNT(1) FROM nasdaq;","database":"sample_datasets","result":"","timezone":"UTC","delay":"","next_time":"","priority":"","retry_limit":""},
-                {"name":"bar","cron":"* * * * *","query":"SELECT COUNT(1) FROM nasdaq;","database":"sample_datasets","result":"","timezone":"UTC","delay":"","next_time":"","priority":"","retry_limit":""},
-                {"name":"baz","cron":"* * * * *","query":"SELECT COUNT(1) FROM nasdaq;","database":"sample_datasets","result":"","timezone":"UTC","delay":"","next_time":"","priority":"","retry_limit":""}
+                {
+                    "name": "foo",
+                    "cron": null,
+                    "timezone": "UTC",
+                    "delay": 0,
+                    "created_at": "2016-08-02T17:58:40Z",
+                    "type": "presto",
+                    "query": "SELECT COUNT(1) FROM nasdaq;",
+                    "database": "sample_datasets",
+                    "user_name": "Yuu Yamashita",
+                    "priority": 0,
+                    "retry_limit": 0,
+                    "result": "",
+                    "next_time": null
+                },
+                {
+                    "name": "bar",
+                    "cron": "0 0 * * *",
+                    "timezone": "UTC",
+                    "delay": 0,
+                    "created_at": "2016-08-02T18:01:04Z",
+                    "type": "presto",
+                    "query": "SELECT COUNT(1) FROM nasdaq;",
+                    "database": "sample_datasets",
+                    "user_name": "Kazuki Ota",
+                    "priority": 0,
+                    "retry_limit": 0,
+                    "result": "",
+                    "next_time": "2016-09-24T00:00:00Z"
+                },
+                {
+                    "name": "baz",
+                    "cron": "* * * * *",
+                    "timezone": "UTC",
+                    "delay": 0,
+                    "created_at": "2016-03-02T23:01:59Z",
+                    "type": "hive",
+                    "query": "SELECT COUNT(1) FROM nasdaq;",
+                    "database": "sample_datasets",
+                    "user_name": "Yuu Yamashita",
+                    "priority": 0,
+                    "retry_limit": 0,
+                    "result": "",
+                    "next_time": "2016-07-06T00:00:00Z"
+                }
             ]
         }
     """
@@ -84,6 +125,22 @@ def test_list_schedules_success():
     schedules = td.list_schedules()
     td.get.assert_called_with("/v3/schedule/list")
     assert len(schedules) == 3
+    next_time = sorted([ schedule.get("next_time") for schedule in schedules if "next_time" in schedule ])
+    assert len(next_time) == 3
+    assert next_time[2].year == 2016
+    assert next_time[2].month == 9
+    assert next_time[2].day == 24
+    assert next_time[2].hour == 0
+    assert next_time[2].minute == 0
+    assert next_time[2].second == 0
+    created_at = sorted([ schedule.get("created_at") for schedule in schedules if "created_at" in schedule ])
+    assert len(created_at) == 3
+    assert created_at[2].year == 2016
+    assert created_at[2].month == 8
+    assert created_at[2].day == 2
+    assert created_at[2].hour == 18
+    assert created_at[2].minute == 1
+    assert created_at[2].second == 4
 
 def test_list_schedules_failure():
     td = api.API("APIKEY")
@@ -100,13 +157,59 @@ def test_update_schedule_success():
 
 def test_history_success():
     td = api.API("APIKEY")
-    # TODO: should be replaced by wire dump
     body = b"""
         {
             "history": [
-                {"job_id":"12345"},
-                {"job_id":"67890"}
-            ]
+                {
+                    "query": "SELECT COUNT(1) FROM nasdaq;",
+                    "type": "presto",
+                    "priority": 0,
+                    "retry_limit": 0,
+                    "duration": 1,
+                    "status": "success",
+                    "cpu_time": null,
+                    "result_size": 30,
+                    "job_id": "12345",
+                    "created_at": "2016-04-13 05:24:59 UTC",
+                    "updated_at": "2016-04-13 05:25:02 UTC",
+                    "start_at": "2016-04-13 05:25:00 UTC",
+                    "end_at": "2016-04-13 05:25:01 UTC",
+                    "num_records": 1,
+                    "database": "sample_datasets",
+                    "user_name": "Ryuta Kamizono",
+                    "result": "",
+                    "url": "https://console.treasuredata.com/jobs/12345",
+                    "hive_result_schema": "[[\\"_col0\\", \\"bigint\\"]]",
+                    "organization": null,
+                    "scheduled_at": ""
+                },
+                {
+                    "query": "SELECT COUNT(1) FROM nasdaq;",
+                    "type": "presto",
+                    "priority": 0,
+                    "retry_limit": 0,
+                    "duration": 1,
+                    "status": "success",
+                    "cpu_time": null,
+                    "result_size": 30,
+                    "job_id": "67890",
+                    "created_at": "2016-04-13 05:24:59 UTC",
+                    "updated_at": "2016-04-13 05:25:02 UTC",
+                    "start_at": "2016-04-13 05:25:00 UTC",
+                    "end_at": "2016-04-13 05:25:01 UTC",
+                    "num_records": 1,
+                    "database": "sample_datasets",
+                    "user_name": "Ryuta Kamizono",
+                    "result": "",
+                    "url": "https://console.treasuredata.com/jobs/67890",
+                    "hive_result_schema": "[[\\"_col0\\", \\"bigint\\"]]",
+                    "organization": null,
+                    "scheduled_at": ""
+                }
+            ],
+            "count": 2,
+            "from": 0,
+            "to": 20
         }
     """
     td.get = mock.MagicMock(return_value=make_response(200, body))