class Array(object):
    """Mixin to denote objects that are sent from OANDA in an array.
    Also used to correctly serialize objects.
    """

    def __contains__(self, item):
        """Return True if item in this array or item matches an
        objects id or instrument attribute, False otherwise.

        Note: this traverses all or part of the array, instantiating the
        objects. Using `x in array` may, therefore, have a serious impact on
        performance.

        """
        if str(item) in self._id_index or item in self._instrument_index:
            return True
        for value in self:
            if value == item:
                return True

    def __init__(self, *items):
        """Initialize a new array.

        The *items passed in are assumed to be JSON data. If an item is
        accessed, it is passed to `create_attribute` with the appropriate
        class type.

        Initially, objects are stored in self._items. When accessed, the
        objects are reified and stored in self.items. This is transparently
        handled by self.__getitem__(self, key).

        """
        object.__setattr__(self, '_items', items)
        object.__setattr__(self, 'items', [])

    def __init_subclass__(cls, **kwargs):
        """Record the type *contained in* the subclass-array.

        A subclass like:

            class array_holding_foo(Array, contains=Foo):
                pass

        will have all its inner objects instantiated using class Foo.

        """
        cls._contains = kwargs.pop('contains')

    def __repr__(self):
        return f'<{self._contains.__name__} x {len(self)}>'

    def __hash__(self):
        return hash(str(self._items))

    def __eq__(self, other):
        try:
            if str(self._items) == str(other._items):
                return True
        except AttributeError:
            pass
        return False

    def __len__(self):
        return len(self._items)

    def __iter__(self):
        """Iterate over items in array. Use integer indexing so that
        __getitem__ can handle reifying all the objects.

        """
        for index in range(len(self)):
            yield self[index]

    def __getitem__(self, key):
        if isinstance(key, slice):
            return self.__class__(*[self._items[index]
                                    for index in range(len(self._items))[key]])

        length = len(self._items)
        if key < 0:
            key += length

        if not (0 <= key < length):
            raise IndexError('Array index out of range')

        if key >= len(self.items):
            self.items.__iadd__([None] * (key - len(self.items) + 1))

        if self.items[key] is None:
            json = self._items[key]
            self.items[key] = create_attribute(self._contains, json)

        return self.items[key]

    def __getattr__(self, item):
        try:
            indexes = self._construct_indexes()
            for key, value in indexes.items():
                object.__setattr__(self, key, value)
            return indexes[item]
        except KeyError as e:
            raise AttributeError(e)

    def __add__(self, other):
        return self.__class__(*self, *other)

    __radd__ = __add__

    def __delattr__(self, item):
        raise NotImplementedError

    def __setattr__(self, key, value):
        raise NotImplementedError

    def _construct_indexes(self):
        id_index = {}
        trade_id_index = {}
        instrument_index = {}
        for index, json_model in enumerate(self._items):
            # json_model is either a json_dict. Or a Model object
            # constructed from the json_dict
            try:
                key = json_model.get('id', None)
                if key is not None:
                    id_index.update({str(key): index})


                key = json_model.get('trade_id', json_model.get('tradeID'))
                if key is not None:
                    trade_id_index.setdefault(str(key), []).append(index)

                key = json_model.get('instrument', json_model.get('name', None))
                if key is not None:
                    instrument_index.setdefault(key, []).append(index)
            except AttributeError:
                break

        return dict(
            _id_index=id_index,
            _trade_id_index=trade_id_index,
            _instrument_index=instrument_index
        )

    def get_id(self, id_, default=None):
        """Return the objects in the array where the
        `object.id` attribute matches the passed id
        else return the default"""
        try:
            return self[self._id_index[str(id_)]]
        except KeyError:
            pass
        return default

    def get_trade_id(self, id_, default=None, *, type=None):
        """Return the first object in the array where the
        `object.trade_id` attribute matches the passed id
        else return the default

        Args:
            id_: The trade_id to get.
            default: The default value to return if the id_ cannot be found
            type: The returned objects OANDA type. eg. 'STOP_LOSS'
                not to be confused the `type(instance)`
        """
        try:
            if type is not None:
                for obj in (self[idx] for idx in self._trade_id_index[str(id_)]):
                    if getattr(obj, 'type', None) == type:
                        return obj
                return default

            return self[self._trade_id_index[str(id_)][0]]
        except KeyError:
            pass
        return default

    def get_trade_ids(self, id_, default=None):
        """Return the objects in the array where the
        `object.trade_id` attribute matches the passed id
        else return the default"""
        try:
            return self.__class__(*[self[idx] for idx in self._trade_id_index[str(id_)]])
        except KeyError:
            pass
        return default


    def get_instrument(self, instrument, default=None):
        """Return the first object in the array where
        where the `object.instrument` matches the passed instrument
        else return default"""
        try:
            return self[self._instrument_index[instrument][0]]
        except KeyError:
            pass
        return default

    def get_instruments(self, instrument, default=None):
        """Return all the objects in the array where the
        `object.instrument` attribute matches the passed instrument
        else return the default"""
        try:
            return self.__class__(*[self[idx] for idx in self._instrument_index[instrument]])
        except KeyError:
            pass
        return default

    def dataframe(self, json=False, datetime_format=None):
        """Create a pandas.Dataframe

        Args:
            json: True, DataFrame columns will have the JSON representation,
                False, DataFrame columns will have the object attribute representation

            datetime_format: 'UNIX' or 'RFC3339'
        """
        return pd.DataFrame(obj.data(json=json, datetime_format=datetime_format) for obj in self) class Account(AccountSummary):
    """The full details of a client's Account. This includes full open Trade, open
    Position and pending Order representation.

    Attributes:
        id: :class:`~async_v20.AccountID`
            The Account's identifier
        alias: :class:`str`
            Client-assigned alias for the Account. Only provided
            if the Account has an alias set
        currency: :class:`~async_v20.Currency`
            The home currency of the Account
        balance: :class:`~async_v20.AccountUnits`
            The current balance of the Account. Represented in the Account's home currency.
        created_by_user_id: :class:`int`
            ID of the user that created the Account.
        created_time: :class:`~async_v20.DateTime`
            The date/time when the Account was created.
        pl: :class:`~async_v20.AccountUnits`
            The total profit/loss realized over the lifetime of
            the Account. Represented in the Account's home currency.
        resettable_pl: :class:`~async_v20.AccountUnits`
            The total realized profit/loss for the Account since it was
            last reset by the client. Represented in the Account's home currency.
        resettabled_pl_time: :class:`~async_v20.DateTime`
            The date/time that the Account's resettablePL was last reset.
        commission: :class:`~async_v20.AccountUnits`
            The total amount of commission paid over the lifetime
            of the Account. Represented in the Account's home currency.
        margin_rate: :class:`~async_v20.DecimalNumber`
            Client-provided margin rate override for the Account. The effective margin rate of the Account
            is the lesser of this value and
            the OANDA margin rate for the Account's division. This value is only provided if a margin rate override
            exists for the Account.
        margin_call_enter_time: :class:`~async_v20.DateTime`
            The date/time when the Account entered a margin call state.
            Only provided if the Account is in a margin call.
        margin_call_extension_count: :class:`int`
            The number of times that the Account's current margin call was extended.
        last_margin_call_extension_time: :class:`~async_v20.DateTime`
            The date/time of the Account's last margin call extension.
        open_trade_count: :class:`int`
            The number of Trades currently open in the Account.
        open_position_count: :class:`int`
            The number of Positions currently open in the Account.
        pending_order_count: :class:`int`
            The number of Orders currently pending in the Account.
        hedging_enabled: :class:`bool`
            Flag indicating that the Account has hedging enabled.
        unrealized_pl: :class:`~async_v20.AccountUnits`
            The total unrealized profit/loss for all Trades currently open
            in the Account. Represented in the Account's home currency.
        nav: :class:`~async_v20.AccountUnits`
            The net asset value of the Account. Equal to
            Account balance + unrealizedPL. Represented in the Account's home currency.
        margin_used: :class:`~async_v20.AccountUnits`
            Margin currently used for the Account.
            Represented in the Account's home currency.
        margin_available: :class:`~async_v20.AccountUnits`
            Margin available for Account. Represented in the Account's home currency.
        position_value: :class:`~async_v20.AccountUnits`
            The value of the Account's open
            positions represented in the Account's home currency.
        margin_closeout_unrealized_pl: :class:`~async_v20.AccountUnits`
            The Account's margin closeout unrealized PL.
        margin_closeout_nav: :class:`~async_v20.AccountUnits`
            The Account's margin closeout NAV.
        margin_closeout_margin_used: :class:`~async_v20.AccountUnits`
            The Account's margin closeout margin used.
        margin_closeout_percent: :class:`~async_v20.DecimalNumber`
            The Account's margin closeout percentage. When this value is 1.0
            or above the Account is in a margin closeout situation.
        margin_closeout_position_value: :class:`~async_v20.DecimalNumber`
            The value of the Account's open positions as used
            for margin closeout calculations represented in the Account's home currency.
        withdrawal_limit: :class:`~async_v20.AccountUnits`
            The current WithdrawalLimit for the account which will be zero or
            a positive value indicating how much can be withdrawn from the account.
        margin_call_margin_used: :class:`~async_v20.AccountUnits`
            The Account's margin call margin used.
        margin_call_percent: :class:`~async_v20.DecimalNumber`
            The Account's margin call percentage. When this value is 1.0
            or above the Account is in a margin call situation.
        last_transaction_id: :class:`~async_v20.TransactionID`
            The ID of the last Transaction created for the Account.
        trades: ( :class:`~async_v20.TradeSummary`, ...)
            The details of the Trades currently open in the Account.
        positions: ( :class:`~async_v20.Position`, ...)
            The details all Account Positions.
        orders: ( :class:`~async_v20.Order`, ...)
            The details of the Orders currently pending in the Account.
        dividend: :class:`~async_v20.DecimalNumber`
            Dividend
        dividendAdjustment: :class:`~async_v20.DecimalNumber`
            Undocumented
    """

    def __init__(self, id: AccountID = sentinel, alias: str = sentinel, currency: Currency = sentinel,
                 balance: AccountUnits = sentinel,
                 created_by_user_id: int = sentinel, created_time: DateTime = sentinel, pl: AccountUnits = sentinel,
                 resettable_pl: AccountUnits = sentinel, resettabled_pl_time: DateTime = sentinel,
                 commission: AccountUnits = sentinel, margin_rate: DecimalNumber = sentinel,
                 margin_call_enter_time: DateTime = sentinel, margin_call_extension_count: int = sentinel,
                 last_margin_call_extension_time: DateTime = sentinel, open_trade_count: int = sentinel,
                 open_position_count: int = sentinel, pending_order_count: int = sentinel,
                 hedging_enabled: bool = sentinel,
                 unrealized_pl: AccountUnits = sentinel, nav: AccountUnits = sentinel,
                 margin_used: AccountUnits = sentinel,
                 margin_available: AccountUnits = sentinel, position_value: AccountUnits = sentinel,
                 margin_closeout_unrealized_pl: AccountUnits = sentinel, margin_closeout_nav: AccountUnits = sentinel,
                 margin_closeout_margin_used: AccountUnits = sentinel,
                 margin_closeout_percent: DecimalNumber = sentinel,
                 margin_closeout_position_value: DecimalNumber = sentinel, withdrawal_limit: AccountUnits = sentinel,
                 margin_call_margin_used: AccountUnits = sentinel, margin_call_percent: DecimalNumber = sentinel,
                 last_transaction_id: TransactionID = sentinel, trades: ArrayTradeSummary = sentinel,
                 positions: ArrayPosition = sentinel, orders: ArrayOrder = sentinel,
                 financing: DecimalNumber = sentinel,
                 guaranteed_stop_loss_order_mode: GuaranteedStopLossOrderMode = sentinel,
                 resettable_pl_time: DateTime = sentinel,
                 dividend: DecimalNumber = sentinel,
                 dividend_adjustment: AccountUnits = sentinel,
                 guaranteed_execution_fees: AccountUnits = sentinel):
        Model.__init__(**locals()) class AccountSummary(Model):
    """A summary representation of a client's Account. The AccountSummary does not
    provide to full specification of pending Orders, open Trades and Positions.

    Attributes:
        id: :class:`~async_v20.AccountID`
            The Account's identifier
        alias: :class:`str`
            Client-assigned alias for the Account. Only provided
            if the Account has an alias set
        currency: :class:`~async_v20.Currency`
            The home currency of the Account
        balance: :class:`~async_v20.AccountUnits`
            The current balance of the Account. Represented in the Account's home currency.
        created_by_user_id: :class:`int`
            ID of the user that created the Account.
        created_time: :class:`~async_v20.DateTime`
            The date/time when the Account was created.
        pl: :class:`~async_v20.AccountUnits`
            The total profit/loss realized over the lifetime of
            the Account. Represented in the Account's home currency.
        resettable_pl: :class:`~async_v20.AccountUnits`
            The total realized profit/loss for the Account since it was
            last reset by the client. Represented in the Account's home currency.
        resettabled_pl_time: :class:`~async_v20.DateTime`
            The date/time that the Account's resettablePL was last reset.
        commission: :class:`~async_v20.AccountUnits`
            The total amount of commission paid over the lifetime
            of the Account. Represented in the Account's home currency.
        margin_rate: :class:`~async_v20.DecimalNumber`
            Client-provided margin rate override for the Account.
            The effective margin rate of the Account is the lesser of this
            value and the OANDA margin rate for the Account's division.
            This value is only provided if a margin rate override
            exists for the Account.
        margin_call_enter_time: :class:`~async_v20.DateTime`
            The date/time when the Account entered a margin call state.
            Only provided if the Account is in a margin call.
        margin_call_extension_count: :class:`int`
            The number of times that the Account's current margin call was extended.
        last_margin_call_extension_time: :class:`~async_v20.DateTime`
            The date/time of the Account's last margin call extension.
        open_trade_count: :class:`int`
            The number of Trades currently open in the Account.
        open_position_count: :class:`int`
            The number of Positions currently open in the Account.
        pending_order_count: :class:`int`
            The number of Orders currently pending in the Account.
        hedging_enabled: :class:`bool`
            Flag indicating that the Account has hedging enabled.
        unrealized_pl: :class:`~async_v20.AccountUnits`
            The total unrealized profit/loss for all Trades currently open
            in the Account. Represented in the Account's home currency.
        nav: :class:`~async_v20.AccountUnits`
            The net asset value of the Account. Equal to
            Account balance + unrealizedPL. Represented in the Account's home currency.
        margin_used: :class:`~async_v20.AccountUnits`
            Margin currently used for the Account.
            Represented in the Account's home currency.
        margin_available: :class:`~async_v20.AccountUnits`
            Margin available for Account. Represented in the Account's home currency.
        position_value: :class:`~async_v20.AccountUnits`
            The value of the Account's open
            positions represented in the Account's home currency.
        margin_closeout_unrealized_pl: :class:`~async_v20.AccountUnits`
            The Account's margin closeout unrealized PL.
        margin_closeout_nav: :class:`~async_v20.AccountUnits`
            The Account's margin closeout NAV.
        margin_closeout_margin_used: :class:`~async_v20.AccountUnits`
            The Account's margin closeout margin used.
        margin_closeout_percent: :class:`~async_v20.DecimalNumber`
            The Account's margin closeout percentage. When this value is 1.0
            or above the Account is in a margin closeout situation.
        margin_closeout_position_value: :class:`~async_v20.DecimalNumber`
            The value of the Account's open positions as used
            for margin closeout calculations represented in the Account's home currency.
        withdrawal_limit: :class:`~async_v20.AccountUnits`
            The current WithdrawalLimit for the account which will be zero or
            a positive value indicating how much can be withdrawn from the account.
        margin_call_margin_used: :class:`~async_v20.AccountUnits`
            The Account's margin call margin used.
        margin_call_percent: :class:`~async_v20.DecimalNumber`
            The Account's margin call percentage. When this value is 1.0
            or above the Account is in a margin call situation.
        last_transaction_id: :class:`~async_v20.TransactionID`
            The ID of the last Transaction created for the Account.
        dividend: :class:`~async_v20.DecimalNumber`
            Dividend
        dividendAdjustment: :class:`~async_v20.AccountUnits`
            Something
    """

    def __init__(self, id: AccountID = sentinel, alias: str = sentinel, currency: Currency = sentinel,
                 balance: AccountUnits = sentinel,
                 created_by_user_id: int = sentinel, created_time: DateTime = sentinel, pl: AccountUnits = sentinel,
                 resettable_pl: AccountUnits = sentinel, resettabled_pl_time: DateTime = sentinel,
                 commission: AccountUnits = sentinel, margin_rate: DecimalNumber = sentinel,
                 margin_call_enter_time: DateTime = sentinel, margin_call_extension_count: int = sentinel,
                 last_margin_call_extension_time: DateTime = sentinel, open_trade_count: int = sentinel,
                 open_position_count: int = sentinel, pending_order_count: int = sentinel,
                 hedging_enabled: bool = sentinel,
                 unrealized_pl: AccountUnits = sentinel, nav: AccountUnits = sentinel,
                 margin_used: AccountUnits = sentinel,
                 margin_available: AccountUnits = sentinel, position_value: AccountUnits = sentinel,
                 margin_closeout_unrealized_pl: AccountUnits = sentinel, margin_closeout_nav: AccountUnits = sentinel,
                 margin_closeout_margin_used: AccountUnits = sentinel,
                 margin_closeout_percent: DecimalNumber = sentinel,
                 margin_closeout_position_value: DecimalNumber = sentinel, withdrawal_limit: AccountUnits = sentinel,
                 margin_call_margin_used: AccountUnits = sentinel, margin_call_percent: DecimalNumber = sentinel,
                 last_transaction_id: TransactionID = sentinel, trades: ArrayTradeSummary = sentinel,
                 positions: ArrayPosition = sentinel, orders: ArrayOrder = sentinel,
                 financing: DecimalNumber = sentinel,
                 guaranteed_stop_loss_order_mode: GuaranteedStopLossOrderMode = sentinel,
                 resettable_pl_time: DateTime = sentinel,
                 guaranteed_execution_fees: AccountUnits = sentinel,
                 dividend: DecimalNumber = sentinel,
                 ):
        Model.__init__(**locals()) class AccountProperties(Model):
    """Properties related to an Account.

    Attributes:
        id: :class:`~async_v20.AccountID`
            The Account's identifier
        mt4account_id: :class:`~async_v20.AccountID`
            The Account's associated MT4 Account ID. This field will not
            be present if the Account is not an MT4 account.
        tags: ( :class:`str`, ...)
            The Account's tags

    """

    def __init__(self, id: AccountID = sentinel, mt4_account_id: int = sentinel, tags: ArrayStr = sentinel):
        Model.__init__(**locals()) class Position(Model):
    """The specification of a Position within an Account.

    Attributes:
        instrument: :class:`~async_v20.InstrumentName`
            The Position's Instrument.
        pl: :class:`~async_v20.AccountUnits`
            Profit/loss realized by the Position over the lifetime of the Account.
        unrealized_pl: :class:`~async_v20.AccountUnits`
            The unrealized profit/loss of all open Trades that contribute to this Position.
        resettable_pl: :class:`~async_v20.AccountUnits`
            Profit/loss realized by the Position since the
            Account's resettablePL was last reset by the client.
        commission: :class:`~async_v20.AccountUnits`
            The total amount of commission paid for this instrument over
            the lifetime of the Account. Represented in the Account's home currency.
        long: :class:`~async_v20.PositionSide`
            The details of the long side of the Position.
        short: :class:`~async_v20.PositionSide`
            The details of the short side of the Position.

    """

    def __init__(self, instrument: InstrumentName = sentinel, pl: AccountUnits = sentinel,
                 unrealized_pl: AccountUnits = sentinel,
                 resettable_pl: AccountUnits = sentinel, commission: AccountUnits = sentinel,
                 long: PositionSide = sentinel,
                 short: PositionSide = sentinel, financing: DecimalNumber = sentinel,
                 margin_used: AccountUnits = sentinel,
                 guaranteed_execution_fees: AccountUnits = sentinel):
        Model.__init__(**locals()) class GETAccountID(EndPoint):
    # the HTTP verb to use for this endpoint
    method = 'GET'

    # path to endpoint
    path = ('/v3/accounts/', AccountID)

    # description of endpoint
    description = 'Get the full details for a single Account that a client has access to. ' \
                  'Full pending Order, open Trade and open Position representations are provided.'

    # parameters required to send to endpoint
    parameters = {Authorization: (HEADER, 'Authorization'), AcceptDatetimeFormat: (HEADER, 'Accept-Datetime-Format'),
                  AccountID: (PATH, 'accountID')}
    # valid responses
    responses = {200: {'account': Account, 'lastTransactionID': TransactionID}}

    # error msgs'
    error = (400, 401, 405) class GETAccountIDSummary(EndPoint):
    # the HTTP verb to use for this endpoint
    method = 'GET'

    # path to endpoint
    path = ('/v3/accounts/', AccountID, '/summary')

    # description of endpoint
    description = 'Get a summary for a single Account that a client has access to.'

    # parameters required to send to endpoint
    parameters = {Authorization: (HEADER, 'Authorization'), AcceptDatetimeFormat: (HEADER, 'Accept-Datetime-Format'),
                  AccountID: (PATH, 'accountID')}

    # valid responses
    responses = {200: {'account': AccountSummary, 'lastTransactionID': TransactionID}}

    # error msgs'
    error = (400, 401, 405) class GETAccounts(EndPoint):
    # the HTTP verb to use for this endpoint
    method = 'GET'

    # path to endpoint
    path = ('/v3/accounts',)

    # description of endpoint
    description = 'Get a list of all Accounts authorized for the provided token.'

    # parameters required to send to endpoint
    parameters = {Authorization: (HEADER, 'Authorization')}

    # valid responses
    responses = {200: {'accounts': ArrayAccountProperties}}

    # error msgs'
    error = (401, 405) class SinceTransactionID(TransactionID):
    """The account changes to get Since LastTransactionID for account_changes() method"""
    pass class LastTransactionID(TransactionID):
    """Contains the most recent TransactionID"""
    pass class GETInstrumentsCandles(EndPoint):
    # the HTTP verb to use for this endpoint
    method = 'GET'

    # path to endpoint
    path = ('/v3/instruments/', InstrumentName, '/candles')

    # description of endpoint
    description = 'Fetch candlestick data for an instrument.'

    # parameters required to send to endpoint
    parameters = {Authorization: (HEADER, 'Authorization'), AcceptDatetimeFormat: (HEADER, 'Accept-Datetime-Format'),
                  InstrumentName: (PATH, 'instrument'), PriceComponent: (QUERY, 'price'),
                  CandlestickGranularity: (QUERY, 'granularity'), Count: (QUERY, 'count'), FromTime: (QUERY, 'from'),
                  ToTime: (QUERY, 'to'), Smooth: (QUERY, 'smooth'), IncludeFirstQuery: (QUERY, 'includeFirst'),
                  DailyAlignment: (QUERY, 'dailyAlignment'), AlignmentTimezone: (QUERY, 'alignmentTimezone'),
                  WeeklyAlignment: (QUERY, 'weeklyAlignment')}

    # valid responses
    responses = {
        200: {'instrument': InstrumentName, 'granularity': CandlestickGranularity, 'candles': ArrayCandlestick}}

    # error msgs'
    error = (400, 401, 404, 405) class ResponseTimeout(AsyncV20Exception):
    """The server took to long to respond"""
    pass class UnexpectedStatus(AsyncV20Exception):
    """The server returned an unexpected HTTP status"""
    pass def _construct_json_body_and_schema(line, schema, endpoint):
    """This helper function standardises the streaming responses"""
    try:
        typ = line['type']
        obj = schema[typ]
        key = None
        if HEARTBEAT in typ.lower():
            key = HEARTBEAT
        elif endpoint == GETPricingStream:
            key = PRICE
        elif endpoint == GETTransactionsStream:
            key = TRANSACTION
    except KeyError:
        json_body, json_schema = line, schema
    else:
        json_body, json_schema = {key: line}, {key: obj}
    return json_body, json_schema async def _create_response(json_body, endpoint, schema, status, boolean, datetime_format):
    # Here we iterate through all the json objects returned in the response
    # and construct the corresponding async_v20 type as determined by the endpoints
    # Schema
    if isinstance(schema, dict):
        data = []
        for json_object, json_field in json_body.items():
            data.append((json_object, create_attribute(schema.get(json_object), json_field)))
    else:
        obj = schema(**json_body)
        data = [(obj.__class__.__name__, obj)]
    return Response(data, status, boolean, datetime_format) def _lookup_schema(endpoint, status):
    try:
        schema = endpoint.responses[status]  # look up the template to process the data
    except KeyError:
        try:
            schema = other_responses[status]  # See if a response status is an error code
        except KeyError:
            msg = str(status)
            logger.error(msg)
            raise UnexpectedStatus(msg)
        else:
            # Returns False if the status wasn't in the endpoints expected response
            return schema, status, False
    else:
        # Returns true if the status was a valid response
        return schema, status, True async def _rest_response(self, response, endpoint, enable_rest, method_name):
    try:
        async with timeout(self.rest_timeout):
            async with response as resp:
                schema, status, boolean = _lookup_schema(endpoint, resp.status)
                # Update client headers.
                self.default_parameters.update(resp.raw_headers)
                json_body = await resp.json()

    except AsyncTimeOutError:
        msg = f'{method_name} took longer than {self.rest_timeout} seconds'
        logger.error(msg)
        raise ResponseTimeout(msg)
    else:
        response = await _create_response(json_body, endpoint, schema, status, boolean, self.datetime_format)

    if response:
        last_transaction_id = getattr(response, 'lastTransactionID', None)
        if last_transaction_id:
            self.default_parameters.update({LastTransactionID: last_transaction_id})

            # This code is to implement the RESTful nature of the v20 API
            # - Keep track of the last transaction id used to update
            # - Add / Remove / Replace changes to account
            if enable_rest:
                self.default_parameters.update({SinceTransactionID: last_transaction_id})
                update_account(self, response.changes, response.state)

        if endpoint == GETAccountID:
            # Means account_details was requested. So we catch the account state on the way through.
            # This occurs during initialization of OandaClient.
            self.default_parameters.update({SinceTransactionID: last_transaction_id})
            self._account = response.account

    return response @pytest.yield_fixture
@pytest.mark.asyncio
async def server(event_loop):
    global status, sleep_time
    status = 200
    sleep_time = 0
    server = await event_loop.create_server(web.Server(handler), "127.0.0.1", 8080)
    yield server
    server.close()
    await asyncio.sleep(0) @pytest.yield_fixture
@pytest.mark.asyncio
async def client():
    oanda_client = OandaClient(rest_host='127.0.0.1', rest_port=8080, rest_scheme='http',
                               stream_host='127.0.0.1', stream_port=8080, stream_scheme='http',
                               health_host='127.0.0.1', health_port=8080, health_scheme='http')
    yield oanda_client
    await oanda_client.close()
    await asyncio.sleep(0) def order_dict(obj):
    if isinstance(obj, dict):
        return sorted((k, order_dict(v)) for k, v in obj.items())
    if isinstance(obj, list):
        return sorted(order_dict(x) for x in obj)
    else:
        return obj class GETPricingStream(EndPoint):
    # host to use for this endpoint
    host = 'STREAM'

    # the HTTP verb to use for this endpoint
    method = 'GET'

    # path to endpoint
    path = ('/v3/accounts/', AccountID, '/pricing/stream')

    # description of endpoint
    description = '''Get a stream of Account Prices starting from when the request is made.
    This pricing stream does not include every single price created for the Account, 
    but instead will provide at most 4 prices per second (every 250 milliseconds) 
    for each instrument being requested. If more than one price is created for an instrument 
    during the 250 millisecond window, only the price in effect at the end of the window is sent.
    This means that during periods of rapid price movement, subscribers to this stream will 
    not be sent every price. Pricing windows for different connections to the price stream 
    are not all aligned in the same way (i.e.they are not all aligned to the top of the second).
    This means that during periods of rapid price movement, different subscribers may observe 
    different prices depending on their alignment.'''

    # parameters required to send to endpoint
    parameters = {Authorization: (HEADER, 'Authorization'), AcceptDatetimeFormat: (HEADER, 'Accept-Datetime-Format'),
                  AccountID: (PATH, 'accountID'), Instruments: (QUERY, 'instruments'), Snapshot: (QUERY, 'snapshot')}

    # valid responses
    responses = {200: {'PRICE': Price, 'HEARTBEAT': PricingHeartbeat}}

    # error msgs'
    error = (400, 401, 404, 405) class GETTransactionsStream(EndPoint):
    # host to use for this endpoint
    host = 'STREAM'

    # the HTTP verb to use for this endpoint
    method = 'GET'

    # path to endpoint
    path = ('/v3/accounts/', AccountID, '/transactions/stream')

    # description of endpoint
    description = 'Get a stream of Transactions for an Account starting from when the request is made.'

    # parameters required to send to endpoint
    parameters = {Authorization: (HEADER, 'Authorization'), AccountID: (PATH, 'accountID')}

    # valid responses
    responses = {200: {'HEARTBEAT': TransactionHeartbeat,
                       'CREATE': CreateTransaction,
                       'CLOSE': CloseTransaction,
                       'REOPEN': ReopenTransaction,
                       'CLIENT_CONFIGURE': ClientConfigureTransaction,
                       'CLIENT_CONFIGURE_REJECT': ClientConfigureRejectTransaction,
                       'TRANSFER_FUNDS': TransferFundsTransaction,
                       'TRANSFER_FUNDS_REJECT': TransferFundsRejectTransaction,
                       'MARKET_ORDER': MarketOrderTransaction,
                       'MARKET_ORDER_REJECT': MarketOrderRejectTransaction,
                       'LIMIT_ORDER': LimitOrderTransaction,
                       'LIMIT_ORDER_REJECT': LimitOrderRejectTransaction,
                       'STOP_ORDER': StopOrderTransaction,
                       'STOP_ORDER_REJECT': StopOrderRejectTransaction,
                       'MARKET_IF_TOUCHED_ORDER': MarketIfTouchedOrderTransaction,
                       'MARKET_IF_TOUCHED_ORDER_REJECT': MarketIfTouchedOrderRejectTransaction,
                       'TAKE_PROFIT_ORDER': TakeProfitOrderTransaction,
                       'TAKE_PROFIT_ORDER_REJECT': TakeProfitOrderRejectTransaction,
                       'STOP_LOSS_ORDER': StopLossOrderTransaction,
                       'STOP_LOSS_ORDER_REJECT': StopOrderRejectTransaction,
                       'TRAILING_STOP_LOSS_ORDER': TrailingStopLossOrderTransaction,
                       'TRAILING_STOP_LOSS_ORDER_REJECT': TrailingStopLossOrderRejectTransaction,
                       'ORDER_FILL': OrderFillTransaction,
                       'ORDER_CANCEL': OrderCancelTransaction,
                       'ORDER_CANCEL_REJECT': OrderCancelRejectTransaction,
                       'ORDER_CLIENT_EXTENSIONS_MODIFY': OrderClientExtensionsModifyTransaction,
                       'ORDER_CLIENT_EXTENSIONS_MODIFY_REJECT': OrderClientExtensionsModifyRejectTransaction,
                       'TRADE_CLIENT_EXTENSIONS_MODIFY': TradeClientExtensionsModifyTransaction,
                       'TRADE_CLIENT_EXTENSIONS_MODIFY_REJECT': TradeClientExtensionsModifyRejectTransaction,
                       'MARGIN_CALL_ENTER': MarginCallEnterTransaction,
                       'MARGIN_CALL_EXTEND': MarginCallExtendTransaction,
                       'MARGIN_CALL_EXIT': MarginCallExitTransaction,
                       'DELAYED_TRADE_CLOSURE': DelayedTradeClosureTransaction,
                       'DAILY_FINANCING': DailyFinancingTransaction,
                       'RESET_RESETTABLE_PL': ResetResettablePLTransaction}
                 }

    # error msgs'
    error = (400, 401, 404, 405) class PricingHeartbeat(Model):
    """A PricingHeartbeat object is injected into the Pricing stream to ensure
    that the HTTP connection remains active.

    Attributes:
        type: :class:`str`
            The string "HEARTBEAT"
        time: :class:`~async_v20.DateTime`
            The date/time when the Heartbeat was created.

    """

    def __init__(self, type: str = sentinel, time: DateTime = sentinel):
        Model.__init__(**locals()) class TransactionHeartbeat(Model):
    """A TransactionHeartbeat object is injected into the Transaction stream to
    ensure that the HTTP connection remains active.

    Attributes:
        type: :class:`str`
            The string "HEARTBEAT"
        last_transaction_id: :class:`~async_v20.TransactionID`
            The ID of the most recent Transaction created for the Account
        time: :class:`~async_v20.DateTime`
            The date/time when the TransactionHeartbeat was created.

    """

    def __init__(self, type: str = sentinel, last_transaction_id: TransactionID = sentinel, time: DateTime = sentinel):
        Model.__init__(**locals()) class Price(Model):
    """The specification of an Account-specific Price.

    Attributes:
        type: :class:`str`
            The string "PRICE". Used to identify the a Price object when found in a stream.
        instrument: :class:`~async_v20.InstrumentName`
            The Price's Instrument.
        time: :class:`~async_v20.DateTime`
            The date/time when the Price was created
        status: :class:`~async_v20.PriceStatus`
            The status of the Price.
        tradeable: :class:`bool`
            Flag indicating if the Price is tradeable or not
        bids: ( :class:`~async_v20.PriceBucket`, ...),
            The list of prices and liquidity available on the Instrument's bid side. It is possible for this
            list to be empty if there is no bid liquidity currently available for the Instrument in the Account.
        asks: ( :class:`~async_v20.PriceBucket`, ...),
            The list of prices and liquidity available on the Instrument's ask side. It is possible for this
            list to be empty if there is no ask liquidity currently available for the Instrument in the Account.
        closeout_bid: :class:`~async_v20.PriceValue`
            The closeout bid Price. This Price is used when a bid is required to closeout a Position
            (margin closeout
            or manual) yet there is no bid liquidity. The closeout bid is never used to open a new position.
        closeout_ask: :class:`~async_v20.PriceValue`
            The closeout ask Price. This Price is used when a ask is required to closeout a Position
            (margin closeout
            or manual) yet there is no ask liquidity. The closeout ask is never used to open a new position.
        quote_home_conversion_factors: :class:`~async_v20.QuoteHomeConversionFactors`
            The factors used to convert quantities of this price's Instrument's
            quote currency into a quantity of the Account's home currency.
        units_available: :class:`~async_v20.UnitsAvailable`
            Representation of how many units of an Instrument are available
            to be traded by an Order depending on its postionFill option.

    """

    def __init__(self, type: str = sentinel, instrument: InstrumentName = sentinel, time: DateTime = sentinel,
                 # TODO: remove status when OANDA removes attribute
                 status: PriceStatus = sentinel, tradeable: bool = sentinel, bids: ArrayPriceBucket = sentinel,
                 asks: ArrayPriceBucket = sentinel, closeout_bid: PriceValue = sentinel,
                 closeout_ask: PriceValue = sentinel,
                 # TODO: remove quote_home_conversion_factors when OANDA removes attribute
                 quote_home_conversion_factors: QuoteHomeConversionFactors = sentinel,
                 # TODO: remove units_available when OANDA removes attribute
                 units_available: UnitsAvailable = sentinel):
        Model.__init__(**locals()) class Transaction(Model):
    """The base Transaction specification. Contains all possible attributes a transaction
    may contain.

    Attributes:
        id: :class:`~async_v20.TransactionID`
            The Transaction's Identifier.
        time: :class:`~async_v20.DateTime`
            The date/time when the Transaction was created.
        user_id: :class:`int`
            The ID of the user that initiated the creation of the Transaction.
        account_id: :class:`~async_v20.AccountID`
            The ID of the Account the Transaction was created for.
        batch_id: :class:`~async_v20.TransactionID`
            The ID of the "batch" that the Transaction belongs to.
            Transactions in the same batch are applied to the Account simultaneously.
        request_id: :class:`~async_v20.RequestID`
            The Request ID of the request which generated the transaction.

    """

    def __init__(self, id: TransactionID = sentinel, time: DateTime = sentinel, user_id: int = sentinel,
                 account_id: AccountID = sentinel, batch_id: TransactionID = sentinel, request_id: RequestID = sentinel,
                 type: TransactionType = sentinel, extension_number: int = sentinel, division_id: int = sentinel,
                 site_id: int = sentinel, account_user_id: int = sentinel, account_number: int = sentinel,
                 home_currency: Currency = sentinel, alias: str = sentinel, margin_rate: DecimalNumber = sentinel,
                 reason: Reason = sentinel, trade_ids: TradeID = sentinel, order_id: OrderID = sentinel,
                 client_order_id: ClientID = sentinel, replaced_by_order_id: OrderID = sentinel,
                 closed_trade_id: OrderID = sentinel, trade_close_transaction_id: TransactionID = sentinel,
                 client_extensions_modify: ClientExtensions = sentinel,
                 trade_client_extensions_modify: ClientExtensions = sentinel, financing: AccountUnits = sentinel,
                 account_balance: AccountUnits = sentinel, account_financing_mode: AccountFinancingMode = sentinel,
                 position_financings: ArrayPositionFinancing = sentinel, trade_id: TradeID = sentinel,
                 client_trade_id: ClientID = sentinel, price: PriceValue = sentinel,
                 time_in_force: TimeInForce = sentinel,
                 gtd_time: DateTime = sentinel, trigger_condition: OrderTriggerCondition = sentinel,
                 client_extensions: ClientExtensions = sentinel, order_fill_transaction_id: TransactionID = sentinel,
                 replaces_order_id: OrderID = sentinel, cancelling_transaction_id: TransactionID = sentinel,
                 reject_reason: TransactionRejectReason = sentinel, amount: AccountUnits = sentinel,
                 funding_reason: FundingReason = sentinel, comment: str = sentinel,
                 instrument: InstrumentName = sentinel,
                 units: DecimalNumber = sentinel, price_bound: PriceValue = sentinel,
                 position_fill: OrderPositionFill = sentinel,
                 trade_close: MarketOrderTradeClose = sentinel,
                 long_position_closeout: MarketOrderPositionCloseout = sentinel,
                 short_position_closeout: MarketOrderPositionCloseout = sentinel,
                 margin_closeout: MarketOrderMarginCloseout = sentinel,
                 delayed_trade_close: MarketOrderDelayedTradeClose = sentinel,
                 take_profit_on_fill: TakeProfitDetails = sentinel, stop_loss_on_fill: StopLossDetails = sentinel,
                 trailing_stop_loss_on_fill: TrailingStopLossDetails = sentinel,
                 trade_client_extensions: ClientExtensions = sentinel, distance: PriceValue = sentinel,
                 full_price: ClientPrice = sentinel, pl: AccountUnits = sentinel, commission: AccountUnits = sentinel,
                 trade_opened: TradeOpen = sentinel, trades_closed: ArrayTradeReduce = sentinel,
                 trade_reduced: TradeReduce = sentinel, intended_replaces_order_id: OrderID = sentinel,
                 # TODO update when OANDA ADVISES correct type. This is currently a guess.
                 gain_quote_home_conversion_factor: DecimalNumber = sentinel,
                 loss_quote_home_conversion_factor: DecimalNumber = sentinel,
                 guaranteed_execution_fee: AccountUnits = sentinel,
                 half_spread_cost: AccountUnits = sentinel,
                 partial_fill: str = sentinel,
                 guaranteed: bool = sentinel,
                 requested_units: AccountUnits = sentinel,
                 full_vwap: DecimalNumber = sentinel):
        Model.__init__(**locals()) def sort_json(x):
    return json.dumps(json.loads(x), sort_keys=True)
import async_timeout
import pytest
import logging
from async_v20.definitions.base import Array
from async_v20.definitions.types import Account, AccountSummary, AccountProperties
from async_v20.definitions.types import Position
from async_v20.endpoints.account import GETAccountID, GETAccountIDSummary, GETAccounts
from async_v20.endpoints.annotations import SinceTransactionID, LastTransactionID
from async_v20.endpoints.instrument import GETInstrumentsCandles
from async_v20.exceptions import ResponseTimeout, UnexpectedStatus
from async_v20.interface.parser import _construct_json_body_and_schema
from async_v20.interface.parser import _create_response
from async_v20.interface.parser import _lookup_schema
from async_v20.interface.parser import _rest_response
from tests.data.json_data import GETAccountIDSummary_response
from tests.data.json_data import GETAccountID_response
from tests.data.json_data import GETAccounts_response
from tests.data.json_data import GETInstrumentsCandles_response
from tests.data.json_data import stream_price
from tests.data.json_data import stream_price_heartbeat
from tests.data.json_data import stream_transaction
from tests.data.json_data import stream_transaction_heartbeat
from tests.fixtures import server as server_module
from tests.fixtures.client import client
from tests.fixtures.static import account_changes_response
from tests.test_interface.helpers import order_dict
from async_v20.endpoints.pricing import GETPricingStream
from async_v20.endpoints.transaction import GETTransactionsStream
from async_v20.definitions.types import PricingHeartbeat
from async_v20.definitions.types import TransactionHeartbeat
from async_v20.definitions.types import Price
from async_v20.definitions.types import Transaction
from .helpers import sort_json


logger = logging.getLogger('async_v20')
logger.disabled = True

client = client
server = server_module.server


@pytest.fixture()
def rest_response():
    """FIXTURE to simulate an `aiohttp` rest response"""

    class RestResponse(object):
        raw_headers = {}

        def __init__(self, data, status=200):
            self.data = data
            self.status = status

        async def json(self):
            return self.data

        async def __aenter__(self):
            return self

        async def __aexit__(self, exc_type, exc_val, exc_tb):
            pass

    yield RestResponse
    del RestResponse


@pytest.mark.asyncio
async def test_rest_response_builds_account_object_from_json_response(client, rest_response):
    result = await _rest_response(client, rest_response(GETAccountID_response), GETAccountID, enable_rest=False,
                                  method_name='test_method')
    # Ensure the result contains an 'account'
    assert 'account' in result
    # Ensure that 'account' is indeed an Account

    assert type(result['account']) == Account