From 6ab8c1371d980d9c360d07a010db650082485b4d Mon Sep 17 00:00:00 2001 From: Dan Mihaila Date: Fri, 24 May 2024 15:07:07 +0300 Subject: [PATCH 1/2] HDX-9848 currency endpoint --- hdx_hapi/config/doc_snippets.py | 2 + hdx_hapi/db/dao/currency_view_dao.py | 33 ++++++++++ hdx_hapi/endpoints/get_currency_response.py | 49 +++++++++++++++ .../endpoints/models/currency_response.py | 9 +++ hdx_hapi/services/currency_logic.py | 17 ++++++ main.py | 2 + tests/conftest.py | 3 + tests/sample_data/currency.sql | 6 ++ tests/test_endpoints/endpoint_data.py | 6 ++ .../test_endpoints/test_currency_endpoint.py | 60 +++++++++++++++++++ .../test_endpoints_vs_encode_identifier.py | 1 + 11 files changed, 188 insertions(+) create mode 100644 hdx_hapi/db/dao/currency_view_dao.py create mode 100644 hdx_hapi/endpoints/get_currency_response.py create mode 100644 hdx_hapi/endpoints/models/currency_response.py create mode 100644 hdx_hapi/services/currency_logic.py create mode 100644 tests/sample_data/currency.sql create mode 100644 tests/test_endpoints/test_currency_endpoint.py diff --git a/hdx_hapi/config/doc_snippets.py b/hdx_hapi/config/doc_snippets.py index bf30b338..d016c3a2 100644 --- a/hdx_hapi/config/doc_snippets.py +++ b/hdx_hapi/config/doc_snippets.py @@ -43,3 +43,5 @@ DOC_SEE_DATASET = 'See the dataset endpoint for details.' DOC_SEE_LOC = 'See the location endpoint for details.' DOC_SEE_ORG_TYPE = 'See the org type endpoint for details.' + +DOC_CURRENCY_CODE = 'Filter the response by the currency code.' diff --git a/hdx_hapi/db/dao/currency_view_dao.py b/hdx_hapi/db/dao/currency_view_dao.py new file mode 100644 index 00000000..c8f4acf1 --- /dev/null +++ b/hdx_hapi/db/dao/currency_view_dao.py @@ -0,0 +1,33 @@ +import logging +from typing import Optional +from sqlalchemy.ext.asyncio import AsyncSession +from sqlalchemy import select + +from hdx_hapi.db.models.views.all_views import CurrencyView +from hdx_hapi.db.dao.util.util import apply_pagination, case_insensitive_filter +from hdx_hapi.endpoints.util.util import PaginationParams + +logger = logging.getLogger(__name__) + + +async def currencies_view_list( + pagination_parameters: PaginationParams, + db: AsyncSession, + code: Optional[str] = None, +): + logger.info(f'currency_view_list called with params: code={code}') + + query = select(CurrencyView) + if code: + query = case_insensitive_filter(query, CurrencyView.code, code) + + query = apply_pagination(query, pagination_parameters) + + logger.debug(f'Executing SQL query: {query}') + + result = await db.execute(query) + currencies = result.scalars().all() + + logger.info(f'Retrieved {len(currencies)} rows from the database') + + return currencies diff --git a/hdx_hapi/endpoints/get_currency_response.py b/hdx_hapi/endpoints/get_currency_response.py new file mode 100644 index 00000000..e894d9fa --- /dev/null +++ b/hdx_hapi/endpoints/get_currency_response.py @@ -0,0 +1,49 @@ +from typing import Annotated +from fastapi import Depends, Query, APIRouter + + +from sqlalchemy.ext.asyncio import AsyncSession +from hdx_hapi.config.doc_snippets import DOC_CURRENCY_CODE + +from hdx_hapi.endpoints.models.base import HapiGenericResponse +from hdx_hapi.endpoints.models.currency_response import CurrencyResponse +from hdx_hapi.endpoints.util.util import ( + CommonEndpointParams, + OutputFormat, + common_endpoint_parameters, +) +from hdx_hapi.services.csv_transform_logic import transform_result_to_csv_stream_if_requested + +from hdx_hapi.services.currency_logic import get_currencies_srv +from hdx_hapi.services.sql_alchemy_session import get_db + +router = APIRouter( + tags=['Metadata'], +) + + +@router.get( + '/api/metadata/currency', + response_model=HapiGenericResponse[CurrencyResponse], + summary='Get information about how currencies are classified', + include_in_schema=False, +) +@router.get( + '/api/v1/metadata/currency', + response_model=HapiGenericResponse[CurrencyResponse], + summary='Get information about how currencies are classified', +) +async def get_currencies( + common_parameters: Annotated[CommonEndpointParams, Depends(common_endpoint_parameters)], + db: AsyncSession = Depends(get_db), + code: Annotated[ + str, Query(max_length=32, description=f'{DOC_CURRENCY_CODE}', openapi_examples={'usd': {'value': 'usd'}}) + ] = None, + output_format: OutputFormat = OutputFormat.JSON, +): + result = await get_currencies_srv( + pagination_parameters=common_parameters, + db=db, + code=code, + ) + return transform_result_to_csv_stream_if_requested(result, output_format, CurrencyResponse) diff --git a/hdx_hapi/endpoints/models/currency_response.py b/hdx_hapi/endpoints/models/currency_response.py new file mode 100644 index 00000000..9f14bfb8 --- /dev/null +++ b/hdx_hapi/endpoints/models/currency_response.py @@ -0,0 +1,9 @@ +from pydantic import ConfigDict, Field +from hdx_hapi.endpoints.models.base import HapiBaseModel + + +class CurrencyResponse(HapiBaseModel): + code: str = Field(max_length=32) + name: str = Field(max_length=512) + + model_config = ConfigDict(from_attributes=True) diff --git a/hdx_hapi/services/currency_logic.py b/hdx_hapi/services/currency_logic.py new file mode 100644 index 00000000..5c11580f --- /dev/null +++ b/hdx_hapi/services/currency_logic.py @@ -0,0 +1,17 @@ +from typing import Optional +from sqlalchemy.ext.asyncio import AsyncSession + +from hdx_hapi.db.dao.currency_view_dao import currencies_view_list +from hdx_hapi.endpoints.util.util import PaginationParams + + +async def get_currencies_srv( + pagination_parameters: PaginationParams, + db: AsyncSession, + code: Optional[str] = None, +): + return await currencies_view_list( + pagination_parameters=pagination_parameters, + db=db, + code=code, + ) diff --git a/main.py b/main.py index c7ff27ee..89982441 100644 --- a/main.py +++ b/main.py @@ -27,6 +27,7 @@ from hdx_hapi.endpoints.get_national_risk import router as national_risk_router # noqa from hdx_hapi.endpoints.get_wfp_commodity import router as wfp_commodity_router # noqa from hdx_hapi.endpoints.get_food_security import router as food_security_router # noqa +from hdx_hapi.endpoints.get_currency_response import router as currency_router # noqa # from hdx_hapi.endpoints.delete_example import delete_dataset @@ -59,6 +60,7 @@ app.include_router(dataset_router) app.include_router(wfp_commodity_router) app.include_router(food_security_router) +app.include_router(currency_router) # add middleware diff --git a/tests/conftest.py b/tests/conftest.py index b0bfacbb..60ed9123 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -27,6 +27,7 @@ from hapi_schema.db_conflict_event import view_params_conflict_event from hapi_schema.db_poverty_rate import view_params_poverty_rate from hapi_schema.db_wfp_commodity import view_params_wfp_commodity +from hapi_schema.db_currency import view_params_currency from hdx_hapi.config.config import get_config from hdx_hapi.db.models.base import Base @@ -48,6 +49,7 @@ 'tests/sample_data/poverty_rate.sql', 'tests/sample_data/food_security.sql', 'tests/sample_data/wfp_commodity.sql', + 'tests/sample_data/currency.sql', ] VIEW_LIST = [ @@ -69,6 +71,7 @@ view_params_conflict_event, view_params_poverty_rate, view_params_wfp_commodity, + view_params_currency, ] diff --git a/tests/sample_data/currency.sql b/tests/sample_data/currency.sql new file mode 100644 index 00000000..5dd99419 --- /dev/null +++ b/tests/sample_data/currency.sql @@ -0,0 +1,6 @@ +-- dummy data +INSERT INTO currency (code, name) +VALUES +('USD', 'United states dollar'), +('RON', 'Romanian leu'), +('EUR', 'Euro'); diff --git a/tests/test_endpoints/endpoint_data.py b/tests/test_endpoints/endpoint_data.py index 28efea5b..5d9264c1 100644 --- a/tests/test_endpoints/endpoint_data.py +++ b/tests/test_endpoints/endpoint_data.py @@ -446,6 +446,12 @@ }, 'expected_fields': ['code', 'name'], }, + '/api/v1/metadata/currency': { + 'query_parameters': { + 'code': 'usD', + }, + 'expected_fields': ['code', 'name'], + }, '/api/v1/metadata/wfp_commodity': { 'query_parameters': { 'code': '001', diff --git a/tests/test_endpoints/test_currency_endpoint.py b/tests/test_endpoints/test_currency_endpoint.py new file mode 100644 index 00000000..ae07a13c --- /dev/null +++ b/tests/test_endpoints/test_currency_endpoint.py @@ -0,0 +1,60 @@ +import pytest +import logging + +from httpx import AsyncClient +from main import app +from tests.test_endpoints.endpoint_data import endpoint_data + +log = logging.getLogger(__name__) + +ENDPOINT_ROUTER = '/api/v1/metadata/currency' +endpoint_data = endpoint_data[ENDPOINT_ROUTER] +query_parameters = endpoint_data['query_parameters'] +expected_fields = endpoint_data['expected_fields'] + + +@pytest.mark.asyncio +async def test_get_currencies(event_loop, refresh_db): + log.info('started test_get_currencies') + async with AsyncClient(app=app, base_url='http://test') as ac: + response = await ac.get(ENDPOINT_ROUTER) + assert response.status_code == 200 + assert len(response.json()['data']) > 0, 'There should be at least one currency in the database' + + +@pytest.mark.asyncio +async def test_get_currency_params(event_loop, refresh_db): + log.info('started test_get_currency_params') + + for param_name, param_value in query_parameters.items(): + async with AsyncClient(app=app, base_url='http://test', params={param_name: param_value}) as ac: + response = await ac.get(ENDPOINT_ROUTER) + + assert response.status_code == 200 + assert len(response.json()['data']) > 0, ( + f'There should be at least one currency entry for parameter "{param_name}" with value "{param_value}" ' + 'in the database' + ) + + async with AsyncClient(app=app, base_url='http://test', params=query_parameters) as ac: + response = await ac.get(ENDPOINT_ROUTER) + + assert response.status_code == 200 + assert ( + len(response.json()['data']) > 0 + ), 'There should be at least one currency entry for all parameters in the database' + + +@pytest.mark.asyncio +async def test_get_currency_result(event_loop, refresh_db): + log.info('started test_get_currency_result') + + async with AsyncClient(app=app, base_url='http://test', params=query_parameters) as ac: + response = await ac.get(ENDPOINT_ROUTER) + + for field in expected_fields: + assert field in response.json()['data'][0], f'Field "{field}" not found in the response' + + assert len(response.json()['data'][0]) == len( + expected_fields + ), 'Response has a different number of fields than expected' diff --git a/tests/test_endpoints/test_endpoints_vs_encode_identifier.py b/tests/test_endpoints/test_endpoints_vs_encode_identifier.py index c21cd57e..e58bcd47 100644 --- a/tests/test_endpoints/test_endpoints_vs_encode_identifier.py +++ b/tests/test_endpoints/test_endpoints_vs_encode_identifier.py @@ -26,6 +26,7 @@ '/api/v1/coordination-context/funding', '/api/v1/coordination-context/conflict-event', '/api/v1/food/food-security', + '/api/v1/metadata/currency', ] From a65ac5db35d1ffa032333858d1ad84819f247227 Mon Sep 17 00:00:00 2001 From: Dan Mihaila Date: Fri, 24 May 2024 15:44:16 +0300 Subject: [PATCH 2/2] HDX-9848 rename currency_response files --- .../endpoints/{get_currency_response.py => get_currency.py} | 2 +- hdx_hapi/endpoints/models/{currency_response.py => currency.py} | 0 main.py | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename hdx_hapi/endpoints/{get_currency_response.py => get_currency.py} (95%) rename hdx_hapi/endpoints/models/{currency_response.py => currency.py} (100%) diff --git a/hdx_hapi/endpoints/get_currency_response.py b/hdx_hapi/endpoints/get_currency.py similarity index 95% rename from hdx_hapi/endpoints/get_currency_response.py rename to hdx_hapi/endpoints/get_currency.py index e894d9fa..fb2e26a2 100644 --- a/hdx_hapi/endpoints/get_currency_response.py +++ b/hdx_hapi/endpoints/get_currency.py @@ -6,7 +6,7 @@ from hdx_hapi.config.doc_snippets import DOC_CURRENCY_CODE from hdx_hapi.endpoints.models.base import HapiGenericResponse -from hdx_hapi.endpoints.models.currency_response import CurrencyResponse +from hdx_hapi.endpoints.models.currency import CurrencyResponse from hdx_hapi.endpoints.util.util import ( CommonEndpointParams, OutputFormat, diff --git a/hdx_hapi/endpoints/models/currency_response.py b/hdx_hapi/endpoints/models/currency.py similarity index 100% rename from hdx_hapi/endpoints/models/currency_response.py rename to hdx_hapi/endpoints/models/currency.py diff --git a/main.py b/main.py index 89982441..728c3c8b 100644 --- a/main.py +++ b/main.py @@ -27,7 +27,7 @@ from hdx_hapi.endpoints.get_national_risk import router as national_risk_router # noqa from hdx_hapi.endpoints.get_wfp_commodity import router as wfp_commodity_router # noqa from hdx_hapi.endpoints.get_food_security import router as food_security_router # noqa -from hdx_hapi.endpoints.get_currency_response import router as currency_router # noqa +from hdx_hapi.endpoints.get_currency import router as currency_router # noqa # from hdx_hapi.endpoints.delete_example import delete_dataset