Skip to content

Commit

Permalink
All GET API requests also accept parameters as query arguments
Browse files Browse the repository at this point in the history
Workaround for the problem described in
#576

Which was that many browser http implementations remove a request's
body if it's a GET request and as such JSON data bodies don't work for
GET requests.
  • Loading branch information
LefterisJP committed Dec 26, 2019
1 parent d3e0e13 commit 8ca16de
Show file tree
Hide file tree
Showing 8 changed files with 183 additions and 91 deletions.
32 changes: 30 additions & 2 deletions docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ Introduction
When the Rotki backend runs it exposes an HTTP Rest API that is accessed by either the electron front-end or a web browser. The endpoints accept and return JSON encoded objects. All queries have the following prefix: ``/api/<version>/`` where ``version`` is the current version. The current version at the moment is ``1``.


Request parameters
********************

All endpoints that take parameters accept a json body with said parameters. If the request is a ``GET`` request then it also accepts query parameters since for multiple implementations a JSON body will not work.

Response Format
*****************

Expand Down Expand Up @@ -515,11 +520,14 @@ Query the current fiat currencies exchange rate
Querying this endpoint with a list of strings representing FIAT currencies will return a dictionary of their current exchange rates compared to USD. If no list is given then the exchange rates of all currencies is returned. Providing an empty list is an error.

.. note::
This endpoint also accepts parameters as query arguments. List as a query argument here would be given as: ``?currencies=EUR,CNY,GBP``

**Example Request**:

.. http:example:: curl wget httpie python-requests
GET /api/1/logout HTTP/1.1
GET /api/1/fiat_exchange_rates HTTP/1.1
Host: localhost:5042

{"currencies": ["EUR", "CNY", "GBP"]}
Expand All @@ -544,7 +552,7 @@ Query the current fiat currencies exchange rate
Get a list of setup exchanges
==============================

.. http:put:: /api/(version)/exchanges
.. http:get:: /api/(version)/exchanges
Doing a GET on this endpoint will return a list of which exchanges are currently setup for the logged in user.

Expand Down Expand Up @@ -730,6 +738,9 @@ Querying the trades history of exchanges
.. note::
This endpoint can also be queried asynchronously by using ``"async_query": true``

.. note::
This endpoint also accepts parameters as query arguments.

**Example Request**:

.. http:example:: curl wget httpie python-requests
Expand Down Expand Up @@ -905,6 +916,9 @@ Querying all balances
.. note::
This endpoint can also be queried asynchronously by using ``"async_query": true``

.. note::
This endpoint also accepts parameters as query arguments.

Doing a GET on the balances endpoint will query all balances across all locations for the user. That is exchanges, blockchains and FIAT in banks. And it will return an overview of all queried balances.


Expand Down Expand Up @@ -1129,6 +1143,9 @@ Statistics for asset balance over time
.. note::
This endpoint is only available for premium users

.. note::
This endpoint also accepts parameters as query arguments.

Doing a GET on the statistics asset balance over time endpoint will return all saved balance entries for an asset. Optionally you can filter for a specific time range by providing appropriate arguments.


Expand Down Expand Up @@ -1181,6 +1198,9 @@ Statistics for value distribution
.. note::
This endpoint is only available for premium users

.. note::
This endpoint also accepts parameters as query arguments.


**Example Request**:

Expand Down Expand Up @@ -1311,6 +1331,9 @@ Dealing with trades

.. http:get:: /api/(version)/trades
.. note::
This endpoint also accepts parameters as query arguments.

Doing a GET on this endpoint will return all trades of the current user. They can be further filtered by time range and/or location.

**Example Request**:
Expand Down Expand Up @@ -1562,6 +1585,9 @@ Querying complete action history
.. note::
This endpoint can also be queried asynchronously by using ``"async_query": true``

.. note::
This endpoint also accepts parameters as query arguments.

Doing a GET on the history endpoint will trigger a query and processing of the history of all actions (trades, deposits, withdrawals, loans, eth transactions) within a specific time range.


Expand Down Expand Up @@ -1662,6 +1688,8 @@ Export action history to CSV

.. http:get:: /api/(version)/history/export
.. note::
This endpoint also accepts parameters as query arguments.

Doing a GET on the history export endpoint will export the last previously queried history to CSV files and save them in the given directory. If history has not been queried before an error is returned.

Expand Down
4 changes: 2 additions & 2 deletions rotkehlchen/api/v1/encoding.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from marshmallow import Schema, SchemaOpts, fields, post_load, validates_schema
from marshmallow.exceptions import ValidationError
from webargs import validate
from webargs import fields as webargs_fields, validate

from rotkehlchen.assets.asset import Asset, EthereumToken
from rotkehlchen.errors import DeserializationError, UnknownAsset
Expand Down Expand Up @@ -687,7 +687,7 @@ class Meta:


class FiatExchangeRatesSchema(BaseSchema):
currencies = fields.List(FiatAssetField(), missing=None)
currencies = webargs_fields.DelimitedList(FiatAssetField(), missing=None)

class Meta:
strict = True
Expand Down
14 changes: 7 additions & 7 deletions rotkehlchen/api/v1/resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ class FiatExchangeRatesResource(BaseResource):

get_schema = FiatExchangeRatesSchema()

@use_kwargs(get_schema, locations=('json',))
@use_kwargs(get_schema, locations=('json', 'query'))
def get(self, currencies: Optional[List[Asset]]) -> Response:
return self.rest_api.get_fiat_exchange_rates(currencies=currencies)

Expand All @@ -139,7 +139,7 @@ class AllBalancesResource(BaseResource):

get_schema = AllBalancesQuerySchema()

@use_kwargs(get_schema, locations=('json',))
@use_kwargs(get_schema, locations=('json', 'query'))
def get(self, save_data: bool, async_query: bool) -> Response:
return self.rest_api.query_all_balances(save_data=save_data, async_query=async_query)

Expand All @@ -163,7 +163,7 @@ class ExchangeTradesResource(BaseResource):

get_schema = ExchangeTradesQuerySchema()

@use_kwargs(get_schema, locations=('json', 'view_args'))
@use_kwargs(get_schema, locations=('json', 'view_args', 'query'))
def get(
self,
name: Optional[str],
Expand Down Expand Up @@ -211,7 +211,7 @@ class TradesResource(BaseResource):
patch_schema = TradePatchSchema()
delete_schema = TradeDeleteSchema()

@use_kwargs(get_schema, locations=('json',))
@use_kwargs(get_schema, locations=('json', 'query'))
def get(
self,
from_timestamp: Timestamp,
Expand Down Expand Up @@ -351,7 +351,7 @@ class StatisticsAssetBalanceResource(BaseResource):

get_schema = StatisticsAssetBalanceSchema()

@use_kwargs(get_schema, locations=('json', 'view_args'))
@use_kwargs(get_schema, locations=('json', 'view_args', 'query'))
def get(
self,
asset: Asset,
Expand All @@ -369,7 +369,7 @@ class StatisticsValueDistributionResource(BaseResource):

get_schema = StatisticsValueDistributionSchema()

@use_kwargs(get_schema, locations=('json',))
@use_kwargs(get_schema, locations=('json', 'query'))
def get(self, distribution_by: str) -> Response:
return self.rest_api.query_value_distribution_data(
distribution_by=distribution_by,
Expand Down Expand Up @@ -410,7 +410,7 @@ class HistoryExportingResource(BaseResource):

get_schema = HistoryExportingSchema()

@use_kwargs(get_schema, locations=('json',))
@use_kwargs(get_schema, locations=('json', 'query'))
def get(self, directory_path: Path) -> Response:
return self.rest_api.export_processed_history_csv(directory_path=directory_path)

Expand Down
28 changes: 19 additions & 9 deletions rotkehlchen/tests/api/test_exchange_rates_query.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,29 @@ def test_qerying_fiat_exchange_rates(rotkehlchen_api_server):
status_code=HTTPStatus.BAD_REQUEST,
)

# Test with some currencies
def assert_okay(response):
"""Helper function for DRY checking below assertions"""
assert_proper_response(response)
json_data = response.json()
assert json_data['message'] == ''
result = json_data['result']
assert len(result) == 3
assert FVal(result['EUR']) > 0
assert FVal(result['USD']) > 0
assert FVal(result['KRW']) > 0

# Test with some currencies, both JSON body and query parameters
data = {'currencies': ['EUR', 'USD', 'KRW']}
response = requests.get(
api_url_for(rotkehlchen_api_server, 'fiatexchangeratesresource'), json=data,
)
assert_proper_response(response)
json_data = response.json()
assert json_data['message'] == ''
result = json_data['result']
assert len(result) == 3
assert FVal(result['EUR']) > 0
assert FVal(result['USD']) > 0
assert FVal(result['KRW']) > 0
assert_okay(response)
# The query parameters test serves as a test that a list of parametrs works with query args too
response = requests.get(
api_url_for(rotkehlchen_api_server, 'fiatexchangeratesresource') + '?currencies=' +
','.join(data['currencies']),
)
assert_okay(response)

# Test with all currencies (give no input)
response = requests.get(api_url_for(rotkehlchen_api_server, 'fiatexchangeratesresource'))
Expand Down
41 changes: 30 additions & 11 deletions rotkehlchen/tests/api/test_exchanges.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from http import HTTPStatus
from unittest.mock import patch
from urllib.parse import urlencode

import pytest
import requests
Expand Down Expand Up @@ -356,16 +357,25 @@ def test_exchange_query_trades(rotkehlchen_api_server_with_exchanges):
assert_binance_trades_result(json_data['result']['binance'])
assert_poloniex_trades_result(json_data['result']['poloniex'])

def assert_okay(response):
"""Helper function for DRY checking below assertions"""
assert_proper_response(response)
json_data = response.json()
assert json_data['message'] == ''
assert len(json_data['result']) == 2, 'only two exchanges should be registered'
assert_binance_trades_result(json_data['result']['binance'])
assert_poloniex_trades_result(json_data['result']['poloniex'], trades_to_check=(0,))

# and now query them in a specific time range excluding two of poloniex's trades
data = {'from_timestamp': 1499865548, 'to_timestamp': 1539713118}
with setup.binance_patch, setup.polo_patch:
response = requests.get(api_url_for(server, "exchangetradesresource"), json=data)
assert_proper_response(response)
json_data = response.json()
assert json_data['message'] == ''
assert len(json_data['result']) == 2, 'only two exchanges should be registered'
assert_binance_trades_result(json_data['result']['binance'])
assert_poloniex_trades_result(json_data['result']['poloniex'], trades_to_check=(0,))
assert_okay(response)
# do the same but with query args. This serves as test of from/to timestamp with query args
with setup.binance_patch, setup.polo_patch:
response = requests.get(
api_url_for(server, "exchangetradesresource") + '?' + urlencode(data))
assert_okay(response)


@pytest.mark.parametrize('added_exchanges', [('binance', 'poloniex')])
Expand All @@ -382,17 +392,26 @@ def test_exchange_query_trades_async(rotkehlchen_api_server_with_exchanges):
), json={'async_query': True})
task_id = assert_ok_async_response(response)
outcome = wait_for_async_task(rotkehlchen_api_server_with_exchanges, task_id)

assert_binance_trades_result(outcome['result'])

def assert_okay(response):
"""Helper function for DRY checking below assertions"""
task_id = assert_ok_async_response(response)
outcome = wait_for_async_task(rotkehlchen_api_server_with_exchanges, task_id)
assert_binance_trades_result(outcome['result']['binance'])
assert_poloniex_trades_result(outcome['result']['poloniex'], trades_to_check=(0,))

# query trades of all exchanges and in a specific range asynchronously
data = {'from_timestamp': 1499865548, 'to_timestamp': 1539713118, 'async_query': True}
with setup.binance_patch, setup.polo_patch:
response = requests.get(api_url_for(server, "exchangetradesresource"), json=data)
task_id = assert_ok_async_response(response)
outcome = wait_for_async_task(rotkehlchen_api_server_with_exchanges, task_id)
assert_binance_trades_result(outcome['result']['binance'])
assert_poloniex_trades_result(outcome['result']['poloniex'], trades_to_check=(0,))
assert_okay(response)
# do the same but with query args. This serves as test of async query with query args
with setup.binance_patch, setup.polo_patch:
response = requests.get(
api_url_for(server, "exchangetradesresource") + '?' + urlencode(data),
)
assert_okay(response)


@pytest.mark.parametrize('added_exchanges', [('binance', 'poloniex')])
Expand Down
79 changes: 45 additions & 34 deletions rotkehlchen/tests/api/test_history.py
Original file line number Diff line number Diff line change
Expand Up @@ -291,40 +291,7 @@ def test_query_history_errors(rotkehlchen_api_server):
)


@pytest.mark.parametrize(
'added_exchanges',
[('binance', 'poloniex', 'bittrex', 'bitmex', 'kraken')],
)
@pytest.mark.parametrize('mocked_price_queries', [prices])
def test_history_export_csv(
rotkehlchen_api_server_with_exchanges,
tmpdir_factory,
):
"""Test that the csv export REST API endpoint works correctly"""
rotki = rotkehlchen_api_server_with_exchanges.rest_api.rotkehlchen
profit_currency = rotki.data.db.get_main_currency()
setup = prepare_rotki_for_history_processing_test(
rotki,
should_mock_history_processing=False,
)
csv_dir = str(tmpdir_factory.mktemp('test_csv_dir'))

# First, query history processing to have data for exporting
with ExitStack() as stack:
for manager in setup:
if manager is None:
continue
stack.enter_context(manager)
response = requests.get(
api_url_for(rotkehlchen_api_server_with_exchanges, "historyprocessingresource"),
)
assert_proper_response(response)

# now query the export endpoint
response = requests.get(
api_url_for(rotkehlchen_api_server_with_exchanges, "historyexportingresource"),
json={'directory_path': csv_dir},
)
def assert_csv_export_response(response, profit_currency, csv_dir):
assert_proper_response(response)
data = response.json()
assert data['message'] == ''
Expand Down Expand Up @@ -467,6 +434,50 @@ def test_history_export_csv(
)


@pytest.mark.parametrize(
'added_exchanges',
[('binance', 'poloniex', 'bittrex', 'bitmex', 'kraken')],
)
@pytest.mark.parametrize('mocked_price_queries', [prices])
def test_history_export_csv(
rotkehlchen_api_server_with_exchanges,
tmpdir_factory,
):
"""Test that the csv export REST API endpoint works correctly"""
rotki = rotkehlchen_api_server_with_exchanges.rest_api.rotkehlchen
profit_currency = rotki.data.db.get_main_currency()
setup = prepare_rotki_for_history_processing_test(
rotki,
should_mock_history_processing=False,
)
csv_dir = str(tmpdir_factory.mktemp('test_csv_dir'))
csv_dir2 = str(tmpdir_factory.mktemp('test_csv_dir2'))

# First, query history processing to have data for exporting
with ExitStack() as stack:
for manager in setup:
if manager is None:
continue
stack.enter_context(manager)
response = requests.get(
api_url_for(rotkehlchen_api_server_with_exchanges, "historyprocessingresource"),
)
assert_proper_response(response)

# now query the export endpoint with json body
response = requests.get(
api_url_for(rotkehlchen_api_server_with_exchanges, "historyexportingresource"),
json={'directory_path': csv_dir},
)
assert_csv_export_response(response, profit_currency, csv_dir)
# now query the export endpoint with query params
response = requests.get(
api_url_for(rotkehlchen_api_server_with_exchanges, "historyexportingresource") +
f'?directory_path={csv_dir2}',
)
assert_csv_export_response(response, profit_currency, csv_dir2)


@pytest.mark.parametrize(
'added_exchanges',
[('binance', 'poloniex', 'bittrex', 'bitmex', 'kraken')],
Expand Down

0 comments on commit 8ca16de

Please sign in to comment.