Skip to content

Commit

Permalink
changed DateTime to always create a pd.Timestamp
Browse files Browse the repository at this point in the history
  • Loading branch information
jamespeterschinner committed Dec 20, 2017
1 parent 1dcc4f8 commit dc8d279
Show file tree
Hide file tree
Showing 6 changed files with 97 additions and 46 deletions.
13 changes: 2 additions & 11 deletions async_v20/definitions/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -201,8 +201,6 @@ def dict(self, json=False, datetime_format=None):
accuracy: - int: The accuracy to round PriceValues to.
"""

if json is True and datetime_format is None:
raise ValueError(f'Must specify datetime_format when creating JSON')

def fields():

Expand Down Expand Up @@ -232,15 +230,8 @@ def fields():
# seems to be most useful type. We will make sure to cast them back
# to strings when sending JSON data to OANDA
attr = str(attr)
elif json and isinstance(attr, pd.Timestamp):
attr = attr.datetime_format(datetime_format)
elif not json and datetime_format and isinstance(attr, pd.Timestamp):
if datetime_format == 'UNIX':
attr = attr.value
elif datetime_format == 'RFC3339':
attr = attr.datetime_format(datetime_format)
else:
raise ValueError(f'{datetime_format} is not a valid value')
elif isinstance(attr, pd.Timestamp):
attr = attr.format(datetime_format, json=json)

yield field, attr

Expand Down
2 changes: 1 addition & 1 deletion async_v20/definitions/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
def domain_check(value, example=None, possible_values=None):
if example:
try:
assert len(example) == len(value)
assert len(str(example)) == len(str(value))
except (AssertionError, TypeError):
msg = f'{value} does not match length of example {example}'
raise ValueError(msg)
Expand Down
81 changes: 48 additions & 33 deletions async_v20/definitions/primitives.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from .helpers import domain_check
import pandas as pd
import numpy as np
from .helpers import domain_check

__all__ = ['AcceptDatetimeFormat', 'AccountFinancingMode', 'AccountID', 'AccountUnits', 'CancellableOrderType',
'CandlestickGranularity', 'ClientComment', 'ClientID', 'ClientTag', 'Currency', 'DateTime', 'DecimalNumber',
Expand All @@ -26,6 +27,52 @@ class Specifier(object):
pass



class DateTime(Primitive):
"""A date and time value using either RFC3339 or UNIX time representation.
"""

def __new__(cls, value):
kwargs = {}
try:
length = len(value)
except TypeError:
pass
else:
if length == 20 or isinstance(value, int):
if isinstance(value, str):
value = value.replace('.', '')
value = int(value)
kwargs.update(tz='UTC')

return pd.Timestamp(value, **kwargs)


def _format_datetime(self, datetime_format, json=False):

if json is True and datetime_format is None:
raise ValueError(f'Must specify datetime_format when creating JSON. Either "RFC3339" or "UNIX"')

if datetime_format == 'RFC3339':
nanoseconds = str(self.nanosecond)
nanoseconds = nanoseconds + '000'[:-len(nanoseconds)]
result = self.strftime(f'%Y-%m-%dT%H:%M:%S.%f{nanoseconds}Z')
elif datetime_format == 'UNIX':
result = self.value
if json:
result = str(result)
result = f'{result[:-9]}.{result[-9:]}'

elif datetime_format is None:
result = self
else:
raise ValueError(f'{datetime_format} is not a valid value. It must be either. None, "RFC3339" or "UNIX"')
return result


pd.Timestamp.format = _format_datetime


class AccountFinancingMode(str, Primitive):
"""The financing mode of an Account
"""
Expand Down Expand Up @@ -418,38 +465,6 @@ class Currency(str, Primitive):
def __new__(cls, value):
return super().__new__(cls, value)

def _datetime_format(self, datetime_format):
if datetime_format == 'RFC3339':
nanoseconds = str(self.nanosecond)
nanoseconds = nanoseconds + '000'[:-len(nanoseconds)]
result = self.strftime(f'%Y-%m-%dT%H:%M:%S.%f{nanoseconds}Z')
elif datetime_format == 'UNIX':
value = str(self.value)
result = f'{value[:-9]}.{value[-9:]}'
else:
raise ValueError(f'{datetime_format} is not a valid value. It must be either "RFC3339" or "UNIX"')
return result

pd.Timestamp.datetime_format = _datetime_format

class DateTime(pd.Timestamp, Primitive):
"""A date and time value using either RFC3339 or UNIX time representation.
"""

def __new__(cls, value):
kwargs = {}
try:
length = len(value)
except TypeError:
pass
else:
if length == 20 or isinstance(value, int):
if isinstance(value, str):
value = value.replace('.', '')
value = int(value)
kwargs.update(tz='UTC')

return super().__new__(cls, value, **kwargs)

class DecimalNumber(float, Primitive):
"""The string representation of a decimal number.
Expand Down
2 changes: 1 addition & 1 deletion async_v20/interface/response.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ def value_to_dict(value):
# Specifiers need to be strings for JSON
result = str(value)
elif json and isinstance(value, pd.Timestamp):
result = value.datetime_format(datetime_format)
result = value.format(datetime_format, json=json)
else:
result = value
return result
Expand Down
23 changes: 23 additions & 0 deletions tests/test_definitions/test_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
from ..fixtures.server import server

from pandas import DataFrame
import pandas as pd
import numpy as np

client= client
server = server
Expand Down Expand Up @@ -174,4 +176,25 @@ async def test_array_dataframe_returns_dataframe(client, server):
assert type(df) == DataFrame


@pytest.mark.asyncio
async def test_array_dataframe_converts_datetimes_to_correct_type(client, server):
# Easier to get a real response from the fake server than to mock a response
async with client as client:
rsp = await client.get_candles('AUD_USD')
df = rsp.candles.dataframe()
assert type(df.time[0]) == pd.Timestamp

df = rsp.candles.dataframe(datetime_format='UNIX')
assert type(df.time[0]) == np.int64
assert len(str(df.time[0])) == 19

df = rsp.candles.dataframe(datetime_format='UNIX', json=True)
assert type(df.time[0]) == str
assert len(str(df.time[0])) == 20


df = rsp.candles.dataframe(datetime_format='RFC3339')
assert type(df.time[0]) == str
assert len(str(df.time[0])) == 30

assert type(df) == DataFrame
22 changes: 22 additions & 0 deletions tests/test_definitions/test_primitives.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,28 @@

import pandas as pd

def test_datetime_converts_between_different_representations():
unix_example = '1502463871.639182000'
rfc3339_example = '2017-08-11T15:04:31.639182000Z'
assert DateTime(unix_example) == DateTime(rfc3339_example)
assert DateTime(rfc3339_example).format('UNIX', json=True) == unix_example
assert DateTime(rfc3339_example).format('UNIX') == int(unix_example.replace('.', ''))
assert DateTime(DateTime(rfc3339_example).format('UNIX', json=True)) == DateTime(rfc3339_example)
assert DateTime(unix_example).format('RFC3339') == rfc3339_example

def test_datetime_format_does_not_allow_json_with_no_format_string():
unix_example = '1502463871.639182000'
with pytest.raises(TypeError):
assert DateTime(unix_example).format(json=True)

with pytest.raises(ValueError):
assert DateTime(unix_example).format(datetime_format=None, json=True)

def test_datetime_format_only_allows_valid_format_string():
unix_example = '1502463871.639182000'
with pytest.raises(ValueError):
assert DateTime(unix_example).format(datetime_format='BADVALUE')

@pytest.mark.parametrize('primitive', map(lambda x: getattr(primitives, x), primitives.__all__))
def test_get_valid_primitive_data(primitive):
"""Test the helper function can provide valid data for all primitives"""
Expand Down

0 comments on commit dc8d279

Please sign in to comment.