Skip to content

Commit

Permalink
support for unix timestamps
Browse files Browse the repository at this point in the history
  • Loading branch information
annatisch committed May 17, 2016
1 parent 64e0b96 commit 71f8973
Show file tree
Hide file tree
Showing 4 changed files with 114 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
import isodate
import tempfile
import json
from datetime import date, datetime, timedelta
from datetime import date, datetime, timedelta, tzinfo
import os
from os.path import dirname, pardir, join, realpath, sep, pardir

Expand Down Expand Up @@ -69,6 +69,14 @@ def test_integer(self):
#client.int_model.get_underflow_int32()
#client.int_model.get_underflow_int64()

unix_date = datetime(year=2016, month=4, day=13)
client.int_model.put_unix_time_date(unix_date)
self.assertEqual(unix_date.utctimetuple(), client.int_model.get_unix_time().utctimetuple())
self.assertIsNone(client.int_model.get_null_unix_time())

with self.assertRaises(DeserializationError):
client.int_model.get_invalid_unix_time()


if __name__ == '__main__':
unittest.main()
8 changes: 7 additions & 1 deletion AutoRest/Generators/Python/Python/ClientModelExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,11 @@ public static string ToPythonRuntimeTypeString(this IType type)
{
return "duration";
}

if (known.Type == KnownPrimaryType.UnixTime)
{
return "unix-time";
}
}

var sequenceType = type as SequenceType;
Expand Down Expand Up @@ -246,7 +251,8 @@ public static string GetPythonSerializationType(IType type)
{
{ KnownPrimaryType.DateTime, "iso-8601" },
{ KnownPrimaryType.DateTimeRfc1123, "rfc-1123" },
{ KnownPrimaryType.TimeSpan, "duration" }
{ KnownPrimaryType.TimeSpan, "duration" },
{ KnownPrimaryType.UnixTime, "unix-time" }
};
PrimaryType primaryType = type as PrimaryType;
if (primaryType != null)
Expand Down
2 changes: 1 addition & 1 deletion AutoRest/Generators/Python/Python/PythonCodeNamer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -378,7 +378,7 @@ private static IType NormalizePrimaryType(PrimaryType primaryType)
}
else if (primaryType.Type == KnownPrimaryType.UnixTime)
{
primaryType.Name = "long";
primaryType.Name = "datetime";
}
else if (primaryType.Type == KnownPrimaryType.Object) // Revisit here
{
Expand Down
114 changes: 97 additions & 17 deletions ClientRuntimes/Python/msrest/msrest/serialization.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,13 @@
# --------------------------------------------------------------------------

from base64 import b64decode, b64encode
import calendar
import datetime
import decimal
from enum import Enum
import importlib
import json
import logging
import re
try:
from urllib import quote
Expand All @@ -49,6 +52,31 @@
except NameError:
basestring = str

_LOGGER = logging.getLogger(__name__)


class UTC(datetime.tzinfo):
"""Time Zone info for handling UTC"""

def utcoffset(self, dt):
"""UTF offset for UTC is 0."""
return datetime.timedelta(0)

def tzname(self, dt):
"""Timestamp representation."""
return "Z"

def dst(self, dt):
"""No daylight saving for UTC."""
return datetime.timedelta(hours=1)


try:
from datetime import timezone
TZ_UTC = timezone.utc
except ImportError:
TZ_UTC = UTC()


class Model(object):
"""Mixin for all client request body/response body models to support
Expand Down Expand Up @@ -112,6 +140,24 @@ def _classify(cls, response, objects):
raise TypeError("Object cannot be classified futher.")


def _convert_to_datatype(params, localtype):
"""Convert a dict-like object to the given datatype
"""
return _recursive_convert_to_datatype(params, localtype.__name__, importlib.import_module('..', localtype.__module__))


def _recursive_convert_to_datatype(params, str_localtype, models_module):
"""Convert a dict-like object to the given datatype
"""
if isinstance(params, list):
return [_recursive_convert_to_datatype(data, str_localtype[1:-1], models_module) for data in params]
localtype = getattr(models_module, str_localtype) if hasattr(models_module, str_localtype) else None
if not localtype:
return params
result = {key: _recursive_convert_to_datatype(params[key], localtype._attribute_map[key]['type'], models_module) for key in params}
return localtype(**result)


class Serializer(object):
"""Request object model serializer."""

Expand Down Expand Up @@ -139,6 +185,7 @@ def __init__(self):
self.serialize_type = {
'iso-8601': Serializer.serialize_iso,
'rfc-1123': Serializer.serialize_rfc,
'unix-time': Serializer.serialize_unix,
'duration': Serializer.serialize_duration,
'date': Serializer.serialize_date,
'decimal': Serializer.serialize_decimal,
Expand Down Expand Up @@ -235,7 +282,11 @@ def body(self, data, data_type, **kwargs):
"""
if data is None:
raise ValidationError("required", "body", True)
return self._serialize(data, data_type, **kwargs)
elif isinstance(data_type, str):
return self._serialize(data, data_type, **kwargs)
elif not isinstance(data, data_type):
data = _convert_to_datatype(data, data_type)
return self._serialize(data, data_type.__name__, **kwargs)

def url(self, name, data, data_type, **kwargs):
"""Serialize data intended for a URL path.
Expand Down Expand Up @@ -527,6 +578,8 @@ def serialize_rfc(attr, **kwargs):
:raises: TypeError if format invalid.
"""
try:
if not attr.tzinfo:
_LOGGER.warning("Datetime with no tzinfo will be considered UTC.")
utc = attr.utctimetuple()
except AttributeError:
raise TypeError("RFC1123 object must be valid Datetime object.")
Expand All @@ -546,9 +599,14 @@ def serialize_iso(attr, **kwargs):
"""
if isinstance(attr, str):
attr = isodate.parse_datetime(attr)

try:
utc = attr.utctimetuple()
try:
if not attr.tzinfo:
_LOGGER.warning("Datetime with no tzinfo will be considered UTC.")
utc = attr.utctimetuple()
except AttributeError:
raise TypeError(
"ISO-8601 object must be valid Datetime object.")
if utc.tm_year > 9999 or utc.tm_year < 1:
raise OverflowError("Hit max or min date")

Expand All @@ -561,6 +619,24 @@ def serialize_iso(attr, **kwargs):
msg = "Unable to serialize datetime object."
raise_with_traceback(SerializationError, msg, err)

@staticmethod
def serialize_unix(attr, **kwargs):
"""Serialize Datetime object into IntTime format.
This is represented as seconds.
:param Datetime attr: Object to be serialized.
:rtype: int
:raises: SerializationError if format invalid
"""
if isinstance(attr, int):
return attr
try:
if not attr.tzinfo:
_LOGGER.warning("Datetime with no tzinfo will be considered UTC.")
return int(calendar.timegm(attr.utctimetuple()))
except AttributeError:
raise TypeError("Unix time object must be valid Datetime object.")


class Deserializer(object):
"""Response object model deserializer.
Expand All @@ -579,6 +655,7 @@ def __init__(self, classes={}):
self.deserialize_type = {
'iso-8601': Deserializer.deserialize_iso,
'rfc-1123': Deserializer.deserialize_rfc,
'unix-time': Deserializer.deserialize_unix,
'duration': Deserializer.deserialize_duration,
'date': Deserializer.deserialize_date,
'decimal': Deserializer.deserialize_decimal,
Expand Down Expand Up @@ -958,7 +1035,8 @@ def deserialize_rfc(attr):
try:
date_obj = datetime.datetime.strptime(
attr, "%a, %d %b %Y %H:%M:%S %Z")
date_obj = date_obj.replace(tzinfo=UTC())
if not date_obj.tzinfo:
date_obj = date_obj.replace(tzinfo=TZ_UTC)
except ValueError as err:
msg = "Cannot deserialize to rfc datetime object."
raise_with_traceback(DeserializationError, msg, err)
Expand Down Expand Up @@ -1000,18 +1078,20 @@ def deserialize_iso(attr):
else:
return date_obj

@staticmethod
def deserialize_unix(attr):
"""Serialize Datetime object into IntTime format.
This is represented as seconds.
class UTC(datetime.tzinfo):
"""Time Zone info for handling UTC"""

def utcoffset(self, dt):
"""UTF offset for UTC is 0."""
return datetime.timedelta(hours=0, minutes=0)

def tzname(self, dt):
"""Timestamp representation."""
return "Z"
:param int attr: Object to be serialized.
:rtype: Datetime
:raises: DeserializationError if format invalid
"""
try:
date_obj = datetime.datetime.fromtimestamp(attr, TZ_UTC)
except ValueError as err:
msg = "Cannot deserialize to unix datetime object."
raise_with_traceback(DeserializationError, msg, err)
else:
return date_obj

def dst(self, dt):
"""No daylight saving for UTC."""
return datetime.timedelta(0)

0 comments on commit 71f8973

Please sign in to comment.