Skip to content

Commit

Permalink
Refactor all request methods to use helper method
Browse files Browse the repository at this point in the history
  • Loading branch information
omikader committed Aug 21, 2020
1 parent f3d4455 commit cbf9d2b
Show file tree
Hide file tree
Showing 14 changed files with 327 additions and 1,039 deletions.
2 changes: 1 addition & 1 deletion aiorobinhood/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
__version__ = "2.0.0"
__version__ = "2.1.0"
__all__ = [
"RobinhoodClient",
# exceptions
Expand Down
571 changes: 177 additions & 394 deletions aiorobinhood/client.py

Large diffs are not rendered by default.

12 changes: 1 addition & 11 deletions aiorobinhood/decorators.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,7 @@
from functools import wraps
from typing import Callable

from .exceptions import ClientUnauthenticatedError, ClientUninitializedError


def check_session(func: Callable):
@wraps(func)
async def inner(self, *args, **kwargs):
if self._session is None:
raise ClientUninitializedError()
return await func(self, *args, **kwargs)

return inner
from .exceptions import ClientUnauthenticatedError


def check_tokens(func: Callable):
Expand Down
13 changes: 12 additions & 1 deletion docs/client.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ All communication with the Robinhood servers is done through the

.. autoclass:: RobinhoodClient

All :class:`~.RobinhoodClient` request methods call the :meth:`~.request` helper
method below. This method can also be used to craft custom API requests that are not
encapsulated by :class:`~.RobinhoodClient` API methods.

.. automethod:: RobinhoodClient.request

Authentication
==============

Expand Down Expand Up @@ -58,11 +64,16 @@ Placing Orders
.. warning::
Robinhood rate limits the ``/orders`` endpoint used by the following methods.

All :class:`~.RobinhoodClient` order methods call the :meth:`~.place_order` helper
method below. This method can also be used to craft custom order requests that are not
encapsulated by :class:`~.RobinhoodClient` order API methods.

.. automethod:: RobinhoodClient.place_order
.. automethod:: RobinhoodClient.place_limit_buy_order
.. automethod:: RobinhoodClient.place_limit_sell_order
.. automethod:: RobinhoodClient.place_market_buy_order
.. automethod:: RobinhoodClient.place_market_sell_order
.. automethod:: RobinhoodClient.place_stop_buy_order
.. automethod:: RobinhoodClient.place_stop_sell_order
.. automethod:: RobinhoodClient.place_stop_limit_buy_order
.. automethod:: RobinhoodClient.place_stop_limit_sell_order
.. automethod:: RobinhoodClient.place_stop_limit_sell_order
2 changes: 1 addition & 1 deletion docs/exceptions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,4 @@ Hierarchy
* :exc:`ClientUnauthenticatedError`
* :exc:`ClientRequestError`

* :exc:`ClientAPIError`
* :exc:`ClientAPIError`
2 changes: 1 addition & 1 deletion docs/misc.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@ Indices and tables

* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`
* :ref:`search`
2 changes: 1 addition & 1 deletion docs/reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@ Reference

Client <client>
Exceptions <exceptions>
Models <models>
Models <models>
129 changes: 0 additions & 129 deletions tests/test_account.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

import pytest

from aiorobinhood import ClientAPIError, ClientRequestError
from aiorobinhood.urls import POSITIONS, WATCHLISTS


Expand Down Expand Up @@ -38,39 +37,6 @@ async def test_get_positions(logged_in_client):
assert result == [{"foo": "bar"}, {"baz": "quux"}]


@pytest.mark.asyncio
async def test_get_positions_api_error(logged_in_client):
client, server = logged_in_client
task = asyncio.create_task(client.get_positions(nonzero=False))

request = await server.receive_request(timeout=pytest.TIMEOUT)
assert request.method == "GET"
assert request.headers["Authorization"] == f"Bearer {pytest.ACCESS_TOKEN}"
assert request.path == POSITIONS.path
assert request.query["nonzero"] == "false"
server.send_response(request, status=400, content_type="application/json")

with pytest.raises(ClientAPIError):
await task


@pytest.mark.asyncio
async def test_get_positions_timeout_error(logged_in_client):
client, server = logged_in_client
task = asyncio.create_task(client.get_positions())

request = await server.receive_request(timeout=pytest.TIMEOUT)
assert request.method == "GET"
assert request.headers["Authorization"] == f"Bearer {pytest.ACCESS_TOKEN}"
assert request.path == POSITIONS.path
assert request.query["nonzero"] == "true"

with pytest.raises(ClientRequestError) as exc_info:
await asyncio.sleep(pytest.TIMEOUT + 1)
await task
assert isinstance(exc_info.value.__cause__, asyncio.TimeoutError)


@pytest.mark.asyncio
async def test_get_watchlist(logged_in_client):
client, server = logged_in_client
Expand Down Expand Up @@ -100,37 +66,6 @@ async def test_get_watchlist(logged_in_client):
assert result == ["<>", "><"]


@pytest.mark.asyncio
async def test_get_watchlist_api_error(logged_in_client):
client, server = logged_in_client
task = asyncio.create_task(client.get_watchlist())

request = await server.receive_request(timeout=pytest.TIMEOUT)
assert request.method == "GET"
assert request.headers["Authorization"] == f"Bearer {pytest.ACCESS_TOKEN}"
assert request.path == (WATCHLISTS / "Default/").path
server.send_response(request, status=400, content_type="application/json")

with pytest.raises(ClientAPIError):
await task


@pytest.mark.asyncio
async def test_get_watchlist_timeout_error(logged_in_client):
client, server = logged_in_client
task = asyncio.create_task(client.get_watchlist())

request = await server.receive_request(timeout=pytest.TIMEOUT)
assert request.method == "GET"
assert request.headers["Authorization"] == f"Bearer {pytest.ACCESS_TOKEN}"
assert request.path == (WATCHLISTS / "Default/").path

with pytest.raises(ClientRequestError) as exc_info:
await asyncio.sleep(pytest.TIMEOUT + 1)
await task
assert isinstance(exc_info.value.__cause__, asyncio.TimeoutError)


@pytest.mark.asyncio
async def test_add_to_watchlist(logged_in_client):
client, server = logged_in_client
Expand All @@ -147,39 +82,6 @@ async def test_add_to_watchlist(logged_in_client):
assert result is None


@pytest.mark.asyncio
async def test_add_to_watchlist_api_error(logged_in_client):
client, server = logged_in_client
task = asyncio.create_task(client.add_to_watchlist(instrument="<>"))

request = await server.receive_request(timeout=pytest.TIMEOUT)
assert request.method == "POST"
assert request.headers["Authorization"] == f"Bearer {pytest.ACCESS_TOKEN}"
assert request.path == (WATCHLISTS / "Default/").path
assert (await request.json())["instrument"] == "<>"
server.send_response(request, status=400, content_type="application/json")

with pytest.raises(ClientAPIError):
await task


@pytest.mark.asyncio
async def test_add_to_watchlist_timeout_error(logged_in_client):
client, server = logged_in_client
task = asyncio.create_task(client.add_to_watchlist(instrument="<>"))

request = await server.receive_request(timeout=pytest.TIMEOUT)
assert request.method == "POST"
assert request.headers["Authorization"] == f"Bearer {pytest.ACCESS_TOKEN}"
assert request.path == (WATCHLISTS / "Default/").path
assert (await request.json())["instrument"] == "<>"

with pytest.raises(ClientRequestError) as exc_info:
await asyncio.sleep(pytest.TIMEOUT + 1)
await task
assert isinstance(exc_info.value.__cause__, asyncio.TimeoutError)


@pytest.mark.asyncio
async def test_remove_from_watchlist(logged_in_client):
client, server = logged_in_client
Expand All @@ -193,34 +95,3 @@ async def test_remove_from_watchlist(logged_in_client):

result = await asyncio.wait_for(task, pytest.TIMEOUT)
assert result is None


@pytest.mark.asyncio
async def test_remove_from_watchlist_api_error(logged_in_client):
client, server = logged_in_client
task = asyncio.create_task(client.remove_from_watchlist(id_="12345"))

request = await server.receive_request(timeout=pytest.TIMEOUT)
assert request.method == "DELETE"
assert request.headers["Authorization"] == f"Bearer {pytest.ACCESS_TOKEN}"
assert request.path == (WATCHLISTS / "Default" / "12345/").path
server.send_response(request, status=400, content_type="application/json")

with pytest.raises(ClientAPIError):
await task


@pytest.mark.asyncio
async def test_remove_from_watchlist_timeout_error(logged_in_client):
client, server = logged_in_client
task = asyncio.create_task(client.remove_from_watchlist(id_="12345"))

request = await server.receive_request(timeout=pytest.TIMEOUT)
assert request.method == "DELETE"
assert request.headers["Authorization"] == f"Bearer {pytest.ACCESS_TOKEN}"
assert request.path == (WATCHLISTS / "Default" / "12345/").path

with pytest.raises(ClientRequestError) as exc_info:
await asyncio.sleep(pytest.TIMEOUT + 1)
await task
assert isinstance(exc_info.value.__cause__, asyncio.TimeoutError)
34 changes: 34 additions & 0 deletions tests/test_errors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import asyncio

import pytest

from aiorobinhood import ClientAPIError, ClientRequestError


@pytest.mark.asyncio
async def test_request_api_error(logged_in_client):
client, server = logged_in_client
task = asyncio.create_task(client.request("GET", pytest.NEXT))

request = await server.receive_request(timeout=pytest.TIMEOUT)
assert request.method == "GET"
assert request.path == pytest.NEXT.path
server.send_response(request, status=400, content_type="application/json")

with pytest.raises(ClientAPIError):
await task


@pytest.mark.asyncio
async def test_request_timeout_error(logged_in_client):
client, server = logged_in_client
task = asyncio.create_task(client.request("GET", pytest.NEXT))

request = await server.receive_request(timeout=pytest.TIMEOUT)
assert request.method == "GET"
assert request.path == pytest.NEXT.path

with pytest.raises(ClientRequestError) as exc_info:
await asyncio.sleep(pytest.TIMEOUT + 1)
await task
assert isinstance(exc_info.value.__cause__, asyncio.TimeoutError)
9 changes: 8 additions & 1 deletion tests/test_magic.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
import aiohttp
import pytest

from aiorobinhood import RobinhoodClient
from aiorobinhood import ClientUninitializedError, RobinhoodClient


@pytest.mark.asyncio
async def test_async_context_manager():
async with RobinhoodClient(timeout=pytest.TIMEOUT) as client:
assert client._session is not None
assert isinstance(client._session, aiohttp.ClientSession)


@pytest.mark.asyncio
async def test_async_context_manager_client_uninitialized_error():
with pytest.raises(ClientUninitializedError):
async with RobinhoodClient(timeout=pytest.TIMEOUT) as client:
client._session = None
Loading

0 comments on commit cbf9d2b

Please sign in to comment.