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.py b/hdx_hapi/endpoints/get_currency.py
new file mode 100644
index 00000000..fb2e26a2
--- /dev/null
+++ b/hdx_hapi/endpoints/get_currency.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 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.py b/hdx_hapi/endpoints/models/currency.py
new file mode 100644
index 00000000..9f14bfb8
--- /dev/null
+++ b/hdx_hapi/endpoints/models/currency.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..728c3c8b 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 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',
]