From 42caee8780c6eca39b8ab59daec1caf991d84aa8 Mon Sep 17 00:00:00 2001 From: alexandru-m-g Date: Fri, 17 May 2024 12:31:37 +0300 Subject: [PATCH 1/3] HDX-9830 adpating 3w to new schema --- .../db/dao/operational_presence_view_dao.py | 79 ++++----- .../endpoints/get_operational_presence.py | 151 +++++++++--------- .../endpoints/models/operational_presence.py | 21 ++- .../services/operational_presence_logic.py | 45 +++--- main.py | 4 +- tests/conftest.py | 1 + tests/sample_data/operational_presence.sql | 8 + tests/test_endpoints/endpoint_data.py | 19 ++- .../test_operational_presence_endpoint.py | 29 ++-- 9 files changed, 187 insertions(+), 170 deletions(-) create mode 100644 tests/sample_data/operational_presence.sql diff --git a/hdx_hapi/db/dao/operational_presence_view_dao.py b/hdx_hapi/db/dao/operational_presence_view_dao.py index f849fa2a..19891e46 100644 --- a/hdx_hapi/db/dao/operational_presence_view_dao.py +++ b/hdx_hapi/db/dao/operational_presence_view_dao.py @@ -1,12 +1,11 @@ import logging -from datetime import datetime from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select -from hdx_hapi.db.models.views.db_operational_presence_view import OperationalPresenceView -from hdx_hapi.db.dao.util.util import apply_pagination, case_insensitive_filter -from hdx_hapi.endpoints.util.util import PaginationParams +from hdx_hapi.db.models.views.all_views import OperationalPresenceView +from hdx_hapi.db.dao.util.util import apply_pagination, apply_reference_period_filter, case_insensitive_filter +from hdx_hapi.endpoints.util.util import PaginationParams, ReferencePeriodParameters logger = logging.getLogger(__name__) @@ -14,73 +13,79 @@ async def operational_presences_view_list( pagination_parameters: PaginationParams, + ref_period_parameters: ReferencePeriodParameters, db: AsyncSession, sector_code: str = None, - dataset_hdx_provider_stub: str = None, - resource_update_date_min: datetime = None, - resource_update_date_max: datetime = None, - hapi_updated_date_min: datetime = None, - hapi_updated_date_max: datetime = None, - hapi_replaced_date_min: datetime = None, - hapi_replaced_date_max: datetime = None, + # dataset_hdx_provider_stub: str = None, + # resource_update_date_min: datetime = None, + # resource_update_date_max: datetime = None, + # hapi_updated_date_min: datetime = None, + # hapi_updated_date_max: datetime = None, + # hapi_replaced_date_min: datetime = None, + # hapi_replaced_date_max: datetime = None, org_acronym: str = None, org_name: str = None, sector_name: str = None, location_code: str = None, location_name: str = None, + admin1_ref: int = None, admin1_code: str = None, admin1_name: str = None, admin1_is_unspecified: bool = None, location_ref: int = None, + admin2_ref: int = None, admin2_code: str = None, admin2_name: str = None, admin2_is_unspecified: bool = None, - admin1_ref: int = None, ): logger.info( f'operational_presences_view_list called with params: sector_code={sector_code}, ' - f'dataset_hdx_provider_stub={dataset_hdx_provider_stub}, resource_update_date_min={resource_update_date_min}, ' - f'resource_update_date_max={resource_update_date_max}, org_acronym={org_acronym}, org_name={org_name}, ' + f'org_acronym={org_acronym}, org_name={org_name}, ' f'sector_name={sector_name}, location_code={location_code}, location_name={location_name}, ' f'admin1_code={admin1_code}, admin1_name={admin1_name}, admin1_is_unspecified={admin1_is_unspecified}, ' - f'admin2_code={admin2_code}, admin2_name={admin2_name}, admin2_is_unspecified={admin2_is_unspecified}' + f'admin2_code={admin2_code}, admin2_name={admin2_name}, admin2_is_unspecified={admin2_is_unspecified}, ' + f'ref_period_parameters={ref_period_parameters}' ) query = select(OperationalPresenceView) - if sector_code: - query = query.where(OperationalPresenceView.sector_code.icontains(sector_code)) - if dataset_hdx_provider_stub: - query = case_insensitive_filter( - query, OperationalPresenceView.dataset_hdx_provider_stub, dataset_hdx_provider_stub - ) - if resource_update_date_min: - query = query.where(OperationalPresenceView.resource_update_date >= resource_update_date_min) - if resource_update_date_max: - query = query.where(OperationalPresenceView.resource_update_date < resource_update_date_max) - if hapi_updated_date_min: - query = query.where(OperationalPresenceView.hapi_updated_date >= hapi_updated_date_min) - if hapi_updated_date_max: - query = query.where(OperationalPresenceView.hapi_updated_date < hapi_updated_date_max) - if hapi_replaced_date_min: - query = query.where(OperationalPresenceView.hapi_replaced_date >= hapi_replaced_date_min) - if hapi_replaced_date_max: - query = query.where(OperationalPresenceView.hapi_replaced_date < hapi_replaced_date_max) + # if dataset_hdx_provider_stub: + # query = case_insensitive_filter( + # query, OperationalPresenceView.dataset_hdx_provider_stub, dataset_hdx_provider_stub + # ) + # if resource_update_date_min: + # query = query.where(OperationalPresenceView.resource_update_date >= resource_update_date_min) + # if resource_update_date_max: + # query = query.where(OperationalPresenceView.resource_update_date < resource_update_date_max) + # if hapi_updated_date_min: + # query = query.where(OperationalPresenceView.hapi_updated_date >= hapi_updated_date_min) + # if hapi_updated_date_max: + # query = query.where(OperationalPresenceView.hapi_updated_date < hapi_updated_date_max) + # if hapi_replaced_date_min: + # query = query.where(OperationalPresenceView.hapi_replaced_date >= hapi_replaced_date_min) + # if hapi_replaced_date_max: + # query = query.where(OperationalPresenceView.hapi_replaced_date < hapi_replaced_date_max) if org_acronym: query = case_insensitive_filter(query, OperationalPresenceView.org_acronym, org_acronym) if org_name: query = query.where(OperationalPresenceView.org_name.icontains(org_name)) + if sector_code: + query = query.where(OperationalPresenceView.sector_code.icontains(sector_code)) if sector_name: query = query.where(OperationalPresenceView.sector_name.icontains(sector_name)) + if location_ref: + query = query.where(OperationalPresenceView.location_ref == location_ref) if location_code: query = case_insensitive_filter(query, OperationalPresenceView.location_code, location_code) if location_name: query = query.where(OperationalPresenceView.location_name.icontains(location_name)) + if admin1_ref: + query = query.where(OperationalPresenceView.admin1_ref == admin1_ref) if admin1_code: query = case_insensitive_filter(query, OperationalPresenceView.admin1_code, admin1_code) if admin1_name: query = query.where(OperationalPresenceView.admin1_name.icontains(admin1_name)) - if location_ref: - query = query.where(OperationalPresenceView.location_ref == location_ref) + if admin2_ref: + query = query.where(OperationalPresenceView.admin2_ref == admin2_ref) if admin2_code: query = case_insensitive_filter(query, OperationalPresenceView.admin2_code, admin2_code) if admin2_name: @@ -89,8 +94,8 @@ async def operational_presences_view_list( query = query.where(OperationalPresenceView.admin1_is_unspecified == admin1_is_unspecified) if admin2_is_unspecified is not None: query = query.where(OperationalPresenceView.admin2_is_unspecified == admin2_is_unspecified) - if admin1_ref: - query = query.where(OperationalPresenceView.admin1_ref == admin1_ref) + + query = apply_reference_period_filter(query, ref_period_parameters, OperationalPresenceView) query = apply_pagination(query, pagination_parameters) diff --git a/hdx_hapi/endpoints/get_operational_presence.py b/hdx_hapi/endpoints/get_operational_presence.py index 25892820..229feb4c 100644 --- a/hdx_hapi/endpoints/get_operational_presence.py +++ b/hdx_hapi/endpoints/get_operational_presence.py @@ -1,7 +1,5 @@ -from datetime import date from typing import Annotated from fastapi import Depends, Query, APIRouter -from pydantic import NaiveDatetime from sqlalchemy.ext.asyncio import AsyncSession from hdx_hapi.config.doc_snippets import ( @@ -14,15 +12,22 @@ DOC_SEE_ADMIN1, DOC_SEE_ADMIN2, DOC_SEE_LOC, - DOC_HAPI_UPDATED_DATE_MIN, - DOC_HAPI_UPDATED_DATE_MAX, - DOC_HAPI_REPLACED_DATE_MIN, - DOC_HAPI_REPLACED_DATE_MAX, + # DOC_HAPI_UPDATED_DATE_MIN, + # DOC_HAPI_UPDATED_DATE_MAX, + # DOC_HAPI_REPLACED_DATE_MIN, + # DOC_HAPI_REPLACED_DATE_MAX, ) from hdx_hapi.endpoints.models.base import HapiGenericResponse from hdx_hapi.endpoints.models.operational_presence import OperationalPresenceResponse -from hdx_hapi.endpoints.util.util import AdminLevel, CommonEndpointParams, OutputFormat, common_endpoint_parameters +from hdx_hapi.endpoints.util.util import ( + AdminLevel, + CommonEndpointParams, + OutputFormat, + ReferencePeriodParameters, + common_endpoint_parameters, + reference_period_parameters, +) from hdx_hapi.services.csv_transform_logic import transform_result_to_csv_stream_if_requested from hdx_hapi.services.operational_presence_logic import get_operational_presences_srv from hdx_hapi.services.sql_alchemy_session import get_db @@ -38,29 +43,18 @@ @router.get( - '/api/themes/3w', + '/api/coordination-context/operational-presence', response_model=HapiGenericResponse[OperationalPresenceResponse], summary=SUMMARY_TEXT, include_in_schema=False, ) @router.get( - '/api/themes/3W', - response_model=HapiGenericResponse[OperationalPresenceResponse], - summary=SUMMARY_TEXT, - include_in_schema=False, -) -@router.get( - '/api/v1/themes/3w', - response_model=HapiGenericResponse[OperationalPresenceResponse], - summary=SUMMARY_TEXT, -) -@router.get( - '/api/v1/themes/3W', + '/api/v1/coordination-context/operational-presence', response_model=HapiGenericResponse[OperationalPresenceResponse], summary=SUMMARY_TEXT, - include_in_schema=False, ) async def get_operational_presences( + ref_period_parameters: Annotated[ReferencePeriodParameters, Depends(reference_period_parameters)], common_parameters: Annotated[CommonEndpointParams, Depends(common_endpoint_parameters)], db: AsyncSession = Depends(get_db), sector_code: Annotated[ @@ -111,64 +105,65 @@ async def get_operational_presences( ), ), ] = None, + location_ref: Annotated[int, Query(description='Location reference')] = None, location_code: Annotated[str, Query(max_length=128, description=f'{DOC_LOCATION_CODE} {DOC_SEE_LOC}')] = None, location_name: Annotated[str, Query(max_length=512, description=f'{DOC_LOCATION_NAME} {DOC_SEE_LOC}')] = None, + admin1_ref: Annotated[int, Query(description='Admin1 reference')] = None, admin1_code: Annotated[str, Query(max_length=128, description=f'{DOC_ADMIN1_CODE} {DOC_SEE_ADMIN1}')] = None, admin1_name: Annotated[str, Query(max_length=512, description=f'{DOC_ADMIN1_NAME} {DOC_SEE_ADMIN1}')] = None, - location_ref: Annotated[int, Query(description='Location reference')] = None, # admin1_is_unspecified: Annotated[bool, Query(description='Location Adm1 is not specified')] = None, + admin2_ref: Annotated[int, Query(description='Admin2 reference')] = None, admin2_code: Annotated[str, Query(max_length=128, description=f'{DOC_ADMIN2_CODE} {DOC_SEE_ADMIN2}')] = None, admin2_name: Annotated[str, Query(max_length=512, description=f'{DOC_ADMIN2_NAME} {DOC_SEE_ADMIN2}')] = None, - admin1_ref: Annotated[int, Query(description='Admin1 reference')] = None, admin_level: Annotated[AdminLevel, Query(description='Filter the response by admin level')] = None, # admin2_is_unspecified: Annotated[bool, Query(description='Location Adm2 is not specified')] = None, - resource_update_date_min: Annotated[ - NaiveDatetime | date, - Query( - description=( - 'Filter the response to data updated on or after this date. ' - 'For example 2020-01-01 or 2020-01-01T00:00:00' - ), - openapi_examples={'2020-01-01': {'value': '2020-01-01'}}, - ), - ] = None, - resource_update_date_max: Annotated[ - NaiveDatetime | date, - Query( - description=( - 'Filter the response to data updated on or before this date. ' - 'For example 2024-12-31 or 2024-12-31T23:59:59' - ), - openapi_examples={'2024-12-31': {'value': '2024-12-31'}}, - ), - ] = None, - hapi_updated_date_min: Annotated[ - NaiveDatetime | date, - Query(description=f'{DOC_HAPI_UPDATED_DATE_MIN}'), - ] = None, - hapi_updated_date_max: Annotated[ - NaiveDatetime | date, - Query(description=f'{DOC_HAPI_UPDATED_DATE_MAX}'), - ] = None, - hapi_replaced_date_min: Annotated[ - NaiveDatetime | date, - Query(description=f'{DOC_HAPI_REPLACED_DATE_MIN}'), - ] = None, - hapi_replaced_date_max: Annotated[ - NaiveDatetime | date, - Query(description=f'{DOC_HAPI_REPLACED_DATE_MAX}'), - ] = None, - dataset_hdx_provider_stub: Annotated[ - str, - Query( - max_length=128, - description=( - 'Filter the query by the organizations contributing the source data to HDX. ' - 'If you want to filter by the organization mentioned in the operational presence record, ' - 'see the org_name and org_acronym parameters below.' - ), - ), - ] = None, + # resource_update_date_min: Annotated[ + # NaiveDatetime | date, + # Query( + # description=( + # 'Filter the response to data updated on or after this date. ' + # 'For example 2020-01-01 or 2020-01-01T00:00:00' + # ), + # openapi_examples={'2020-01-01': {'value': '2020-01-01'}}, + # ), + # ] = None, + # resource_update_date_max: Annotated[ + # NaiveDatetime | date, + # Query( + # description=( + # 'Filter the response to data updated on or before this date. ' + # 'For example 2024-12-31 or 2024-12-31T23:59:59' + # ), + # openapi_examples={'2024-12-31': {'value': '2024-12-31'}}, + # ), + # ] = None, + # hapi_updated_date_min: Annotated[ + # NaiveDatetime | date, + # Query(description=f'{DOC_HAPI_UPDATED_DATE_MIN}'), + # ] = None, + # hapi_updated_date_max: Annotated[ + # NaiveDatetime | date, + # Query(description=f'{DOC_HAPI_UPDATED_DATE_MAX}'), + # ] = None, + # hapi_replaced_date_min: Annotated[ + # NaiveDatetime | date, + # Query(description=f'{DOC_HAPI_REPLACED_DATE_MIN}'), + # ] = None, + # hapi_replaced_date_max: Annotated[ + # NaiveDatetime | date, + # Query(description=f'{DOC_HAPI_REPLACED_DATE_MAX}'), + # ] = None, + # dataset_hdx_provider_stub: Annotated[ + # str, + # Query( + # max_length=128, + # description=( + # 'Filter the query by the organizations contributing the source data to HDX. ' + # 'If you want to filter by the organization mentioned in the operational presence record, ' + # 'see the org_name and org_acronym parameters below.' + # ), + # ), + # ] = None, # org_ref: Annotated[int, Query(ge=1, description='Organization reference')] = None, # dataset_hdx_id: Annotated[str, Query(max_length=36, description='HDX Dataset ID')] = None, # dataset_hdx_stub: Annotated[str, Query(max_length=128, description='HDX Dataset Name')] = None, @@ -188,15 +183,16 @@ async def get_operational_presences( """ result = await get_operational_presences_srv( pagination_parameters=common_parameters, + ref_period_parameters=ref_period_parameters, db=db, sector_code=sector_code, - dataset_hdx_provider_stub=dataset_hdx_provider_stub, - resource_update_date_min=resource_update_date_min, - resource_update_date_max=resource_update_date_max, - hapi_updated_date_min=hapi_updated_date_min, - hapi_updated_date_max=hapi_updated_date_max, - hapi_replaced_date_min=hapi_replaced_date_min, - hapi_replaced_date_max=hapi_replaced_date_max, + # dataset_hdx_provider_stub=dataset_hdx_provider_stub, + # resource_update_date_min=resource_update_date_min, + # resource_update_date_max=resource_update_date_max, + # hapi_updated_date_min=hapi_updated_date_min, + # hapi_updated_date_max=hapi_updated_date_max, + # hapi_replaced_date_min=hapi_replaced_date_min, + # hapi_replaced_date_max=hapi_replaced_date_max, org_acronym=org_acronym, org_name=org_name, sector_name=sector_name, @@ -206,6 +202,7 @@ async def get_operational_presences( admin1_name=admin1_name, location_ref=location_ref, # admin1_is_unspecified=admin1_is_unspecified, + admin2_ref=admin2_ref, admin2_code=admin2_code, admin2_name=admin2_name, admin1_ref=admin1_ref, diff --git a/hdx_hapi/endpoints/models/operational_presence.py b/hdx_hapi/endpoints/models/operational_presence.py index 617ff65d..ac8a0dd0 100644 --- a/hdx_hapi/endpoints/models/operational_presence.py +++ b/hdx_hapi/endpoints/models/operational_presence.py @@ -1,4 +1,3 @@ -from datetime import datetime from pydantic import ConfigDict, Field, model_validator, NaiveDatetime from typing import Optional @@ -6,31 +5,31 @@ class OperationalPresenceResponse(HapiBaseModel): - - sector_code: str = Field(max_length=32) - dataset_hdx_stub: str = Field(max_length=128) + # dataset_hdx_stub: str = Field(max_length=128) resource_hdx_id: str = Field(max_length=36) org_acronym: str = Field(max_length=32) org_name: str = Field(max_length=512) + sector_code: str = Field(max_length=32) sector_name: str = Field(max_length=512) + location_ref: int location_code: str = Field(max_length=128) location_name: str = Field(max_length=512) - reference_period_start: Optional[NaiveDatetime] + reference_period_start: NaiveDatetime reference_period_end: Optional[NaiveDatetime] - hapi_updated_date: datetime - hapi_replaced_date: Optional[datetime] + # hapi_updated_date: datetime + # hapi_replaced_date: Optional[datetime] admin1_is_unspecified: bool = Field(exclude=True) admin2_is_unspecified: bool = Field(exclude=True) + admin1_ref: int admin1_code: Optional[str] = Field(max_length=128) admin1_name: Optional[str] = Field(max_length=512) - location_ref: int = None + admin2_ref: int admin2_code: Optional[str] = Field(max_length=128) admin2_name: Optional[str] = Field(max_length=512) - admin1_ref: int = None # resource_update_date: datetime # org_ref: int = None, # dataset_hdx_id: str = Field(max_length=36), @@ -38,12 +37,12 @@ class OperationalPresenceResponse(HapiBaseModel): # dataset_hdx_provider_stub: str = Field(max_length=128), # dataset_hdx_provider_name: str = Field(max_length=512), # resource_name: str = Field(max_length=256), - # org_type_code: str = Field(max_length=32), + org_type_code: str = Field(max_length=32) # org_type_description: str = Field(max_length=512), model_config = ConfigDict(from_attributes=True) - @model_validator(mode='after') + @model_validator(mode='after') # type: ignore def set_admin1_admin2_null(self) -> 'OperationalPresenceResponse': admin1_is_unspecified = self.admin1_is_unspecified admin2_is_unspecified = self.admin2_is_unspecified diff --git a/hdx_hapi/services/operational_presence_logic.py b/hdx_hapi/services/operational_presence_logic.py index c2182ed2..abd20ad2 100644 --- a/hdx_hapi/services/operational_presence_logic.py +++ b/hdx_hapi/services/operational_presence_logic.py @@ -1,63 +1,64 @@ -from datetime import datetime - from sqlalchemy.ext.asyncio import AsyncSession from hdx_hapi.db.dao.operational_presence_view_dao import operational_presences_view_list -from hdx_hapi.endpoints.util.util import AdminLevel, PaginationParams +from hdx_hapi.endpoints.util.util import AdminLevel, PaginationParams, ReferencePeriodParameters from hdx_hapi.services.admin_level_logic import compute_unspecified_values async def get_operational_presences_srv( pagination_parameters: PaginationParams, + ref_period_parameters: ReferencePeriodParameters, db: AsyncSession, sector_code: str = None, - dataset_hdx_provider_stub: str = None, - resource_update_date_min=None, - resource_update_date_max=None, - hapi_updated_date_min: datetime = None, - hapi_updated_date_max: datetime = None, - hapi_replaced_date_min: datetime = None, - hapi_replaced_date_max: datetime = None, + # dataset_hdx_provider_stub: str = None, + # resource_update_date_min=None, + # resource_update_date_max=None, + # hapi_updated_date_min: datetime = None, + # hapi_updated_date_max: datetime = None, + # hapi_replaced_date_min: datetime = None, + # hapi_replaced_date_max: datetime = None, org_acronym: str = None, org_name: str = None, sector_name: str = None, + location_ref: int = None, location_code: str = None, location_name: str = None, + admin1_ref: int = None, admin1_code: str = None, admin1_name: str = None, - location_ref: int = None, # admin1_is_unspecified=None, + admin2_ref: int = None, admin2_code: str = None, admin2_name: str = None, - admin1_ref: int = None, admin_level: AdminLevel = None, # admin2_is_unspecified=None, ): - admin1_is_unspecified, admin2_is_unspecified = compute_unspecified_values(admin_level) return await operational_presences_view_list( pagination_parameters=pagination_parameters, + ref_period_parameters=ref_period_parameters, db=db, sector_code=sector_code, - dataset_hdx_provider_stub=dataset_hdx_provider_stub, - resource_update_date_min=resource_update_date_min, - resource_update_date_max=resource_update_date_max, - hapi_updated_date_min=hapi_updated_date_min, - hapi_updated_date_max=hapi_updated_date_max, - hapi_replaced_date_min=hapi_replaced_date_min, - hapi_replaced_date_max=hapi_replaced_date_max, + # dataset_hdx_provider_stub=dataset_hdx_provider_stub, + # resource_update_date_min=resource_update_date_min, + # resource_update_date_max=resource_update_date_max, + # hapi_updated_date_min=hapi_updated_date_min, + # hapi_updated_date_max=hapi_updated_date_max, + # hapi_replaced_date_min=hapi_replaced_date_min, + # hapi_replaced_date_max=hapi_replaced_date_max, org_acronym=org_acronym, org_name=org_name, sector_name=sector_name, + location_ref=location_ref, location_code=location_code, location_name=location_name, + admin1_ref=admin1_ref, admin1_code=admin1_code, admin1_name=admin1_name, admin1_is_unspecified=admin1_is_unspecified, - location_ref=location_ref, + admin2_ref=admin2_ref, admin2_code=admin2_code, admin2_name=admin2_name, admin2_is_unspecified=admin2_is_unspecified, - admin1_ref=admin1_ref, ) diff --git a/main.py b/main.py index df91ebb3..8f21f771 100644 --- a/main.py +++ b/main.py @@ -16,7 +16,7 @@ # from hdx_hapi.endpoints.favicon import router as favicon_router # noqa # from hdx_hapi.endpoints.get_population import router as population_router # noqa -# from hdx_hapi.endpoints.get_operational_presence import router as operational_presence_router # noqa +from hdx_hapi.endpoints.get_operational_presence import router as operational_presence_router # noqa from hdx_hapi.endpoints.get_admin_level import router as admin_level_router # noqa from hdx_hapi.endpoints.get_hdx_metadata import router as dataset_router # noqa from hdx_hapi.endpoints.get_humanitarian_response import router as humanitarian_response_router # noqa @@ -46,7 +46,7 @@ app.include_router(encoded_identifier_router) # app.include_router(favicon_router) -# app.include_router(operational_presence_router) +app.include_router(operational_presence_router) # app.include_router(population_router) # app.include_router(food_security_router) # app.include_router(national_risk_router) diff --git a/tests/conftest.py b/tests/conftest.py index 1767d698..b69ecc38 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -35,6 +35,7 @@ 'tests/sample_data/sector.sql', 'tests/sample_data/org_type.sql', 'tests/sample_data/org.sql', + 'tests/sample_data/operational_presence.sql', ] VIEW_LIST = [ diff --git a/tests/sample_data/operational_presence.sql b/tests/sample_data/operational_presence.sql new file mode 100644 index 00000000..b645123f --- /dev/null +++ b/tests/sample_data/operational_presence.sql @@ -0,0 +1,8 @@ +-- 3W +INSERT INTO operational_presence (resource_hdx_id, org_acronym, org_name, sector_code, admin2_ref, reference_period_start, reference_period_end) +VALUES +('17acb541-9431-409a-80a8-50eda7e8ebab', 'ORG01', 'Organisation 1', 'SHL', 2, '2023-01-01 00:00:00', NULL), +('17acb541-9431-409a-80a8-50eda7e8ebab', 'ORG02', 'Organisation 2', 'FSC', 4, '2023-01-01 00:00:00', NULL), +('17acb541-9431-409a-80a8-50eda7e8ebab', 'ORG03', 'Organisation 3', 'WSH', 4, '2023-01-01 00:00:00', NULL), +('17acb541-9431-409a-80a8-50eda7e8ebab', 'ORG03', 'Organisation 3', 'HEA', 6, '2023-01-01 00:00:00', NULL), +('17acb541-9431-409a-80a8-50eda7e8ebab', 'ORG02', 'Organisation 2', 'WSH', 1, '2023-01-01 00:00:00', NULL); diff --git a/tests/test_endpoints/endpoint_data.py b/tests/test_endpoints/endpoint_data.py index 79353959..83f167ce 100644 --- a/tests/test_endpoints/endpoint_data.py +++ b/tests/test_endpoints/endpoint_data.py @@ -82,42 +82,47 @@ 'reference_period_end', ], }, - '/api/themes/3W': { + '/api/v1/coordination-context/operational-presence': { 'query_parameters': { - 'sector_code': 'Shl', 'dataset_hdx_provider_stub': 'PROVIDER01', 'resource_update_date_min': date(2023, 6, 1), 'resource_update_date_max': date(2023, 6, 2), 'org_acronym': 'oRG01', 'org_name': 'Organisation 1', + 'sector_code': 'Shl', 'sector_name': 'Emergency Shelter and NFI', + 'location_ref': 1, 'location_code': 'foo', 'location_name': 'Foolandia', + 'admin1_ref': 2, 'admin1_code': 'foo-001', + 'admin1_name': 'province', 'admin1_is_unspecified': False, + 'admin2_ref': 2, 'admin2_code': 'foo-001-xxx', 'admin2_name': 'Unspecified', 'admin2_is_unspecified': True, + 'reference_period_start_min': '2020-01-01T00:00:00', + 'reference_period_start_max': '2024-01-01T00:00:00', }, 'expected_fields': [ 'sector_code', - 'dataset_hdx_stub', 'resource_hdx_id', - 'hapi_updated_date', - 'hapi_replaced_date', 'org_acronym', 'org_name', + 'org_type_code', 'sector_name', + 'location_ref', 'location_code', 'location_name', 'reference_period_start', 'reference_period_end', + 'admin1_ref', 'admin1_code', 'admin1_name', - 'location_ref', + 'admin2_ref', 'admin2_code', 'admin2_name', - 'admin1_ref', ], }, '/api/v1/metadata/org': { diff --git a/tests/test_endpoints/test_operational_presence_endpoint.py b/tests/test_endpoints/test_operational_presence_endpoint.py index a72d91e8..9ad47539 100644 --- a/tests/test_endpoints/test_operational_presence_endpoint.py +++ b/tests/test_endpoints/test_operational_presence_endpoint.py @@ -1,3 +1,4 @@ +from datetime import datetime import pytest import logging @@ -8,7 +9,7 @@ log = logging.getLogger(__name__) -ENDPOINT_ROUTER = '/api/themes/3W' +ENDPOINT_ROUTER = '/api/v1/coordination-context/operational-presence' endpoint_data = endpoint_data[ENDPOINT_ROUTER] query_parameters = endpoint_data['query_parameters'] expected_fields = endpoint_data['expected_fields'] @@ -67,24 +68,24 @@ async def test_get_operational_presence_adm_fields(event_loop, refresh_db): operational_presence_view_adm_specified = OperationalPresenceResponse( sector_code='ABC', - age_range_code='0-1', - dataset_hdx_stub='test-dataset1', resource_hdx_id='test-resource1', - hapi_updated_date='2023-01-01 00:00:00', - hapi_replaced_date=None, org_acronym='ORG01', org_name='Organisation 1', + org_type_code='unimportant', sector_name='Sector Name', + location_ref=1, location_code='Foolandia', location_name='FOO-XXX', + admin1_ref=1, admin1_is_unspecified=False, admin1_code='FOO-XXX', admin1_name='Province 01', + admin2_ref=1, admin2_is_unspecified=False, admin2_code='FOO-XXX-XXX', admin2_name='District A', - reference_period_start='2023-01-01 00:00:00', - reference_period_end='2023-03-31 23:59:59', + reference_period_start=datetime.strptime('2023-01-01 00:00:00', '%Y-%m-%d %H:%M:%S'), + reference_period_end=datetime.strptime('2023-03-31 23:59:59', '%Y-%m-%d %H:%M:%S'), ) assert ( @@ -102,24 +103,24 @@ async def test_get_operational_presence_adm_fields(event_loop, refresh_db): operational_presence_view_adm_unspecified = OperationalPresenceResponse( sector_code='ABC', - age_range_code='0-1', - dataset_hdx_stub='test-dataset1', resource_hdx_id='test-resource1', - hapi_updated_date='2023-01-01 00:00:00', - hapi_replaced_date=None, org_acronym='ORG01', org_name='Organisation 1', + org_type_code='unimportant', sector_name='Sector Name', + location_ref=1, location_code='Foolandia', location_name='FOO-XXX', admin1_is_unspecified=True, + admin1_ref=1, admin1_code='FOO-XXX', admin1_name='Unpecified', + admin2_ref=1, admin2_is_unspecified=True, - admin2_code='FOO-XXX', + admin2_code='FOO-XXX-XXX', admin2_name='Unspecified', - reference_period_start='2023-01-01 00:00:00', - reference_period_end='2023-03-31 23:59:59', + reference_period_start=datetime.strptime('2023-01-01 00:00:00', '%Y-%m-%d %H:%M:%S'), + reference_period_end=datetime.strptime('2023-03-31 23:59:59', '%Y-%m-%d %H:%M:%S'), ) assert ( From a9f7ac8280af5357fcec906b4adc8a2b83a1a698 Mon Sep 17 00:00:00 2001 From: alexandru-m-g Date: Sat, 18 May 2024 10:51:13 +0000 Subject: [PATCH 2/3] HDX-9830 new funding endpoint and logic --- hdx_hapi/db/dao/funding_view_dao.py | 57 +++++++++++++++++ hdx_hapi/endpoints/get_funding.py | 60 ++++++++++++++++++ .../endpoints/get_operational_presence.py | 5 +- hdx_hapi/endpoints/models/funding.py | 25 ++++++++ hdx_hapi/services/csv_transform_logic.py | 6 +- hdx_hapi/services/funding_logic.py | 34 +++++++++++ main.py | 7 ++- tests/conftest.py | 1 + tests/sample_data/funding.sql | 4 ++ tests/test_endpoints/endpoint_data.py | 24 ++++++++ tests/test_endpoints/test_funding_endpoint.py | 61 +++++++++++++++++++ 11 files changed, 275 insertions(+), 9 deletions(-) create mode 100644 hdx_hapi/db/dao/funding_view_dao.py create mode 100644 hdx_hapi/endpoints/get_funding.py create mode 100644 hdx_hapi/endpoints/models/funding.py create mode 100644 hdx_hapi/services/funding_logic.py create mode 100644 tests/sample_data/funding.sql create mode 100644 tests/test_endpoints/test_funding_endpoint.py diff --git a/hdx_hapi/db/dao/funding_view_dao.py b/hdx_hapi/db/dao/funding_view_dao.py new file mode 100644 index 00000000..c0805614 --- /dev/null +++ b/hdx_hapi/db/dao/funding_view_dao.py @@ -0,0 +1,57 @@ +import logging +from typing import Optional, Sequence + +from sqlalchemy.ext.asyncio import AsyncSession +from sqlalchemy import select + +from hdx_hapi.db.models.views.all_views import FundingView +from hdx_hapi.db.dao.util.util import apply_pagination, apply_reference_period_filter, case_insensitive_filter +from hdx_hapi.endpoints.util.util import PaginationParams, ReferencePeriodParameters + + +logger = logging.getLogger(__name__) + + +async def funding_view_list( + pagination_parameters: PaginationParams, + ref_period_parameters: ReferencePeriodParameters, + db: AsyncSession, + appeal_code: Optional[str] = None, + appeal_type: Optional[str] = None, + org_acronym: Optional[str] = None, + org_name: Optional[str] = None, + sector_name: Optional[str] = None, + # location_ref: Optional[int] = None, + location_code: Optional[str] = None, + location_name: Optional[str] = None, +) -> Sequence[FundingView]: + query = select(FundingView) + if org_acronym: + query = case_insensitive_filter(query, FundingView.org_acronym, org_acronym) + if org_name: + query = query.where(FundingView.org_name.icontains(org_name)) + if sector_name: + query = query.where(FundingView.sector_name.icontains(sector_name)) + # if location_ref: + # query = query.where(FundingView.location_ref == location_ref) + if location_code: + query = case_insensitive_filter(query, FundingView.location_code, location_code) + if location_name: + query = query.where(FundingView.location_name.icontains(location_name)) + if appeal_code: + query = case_insensitive_filter(query, FundingView.appeal_code, appeal_code) + if appeal_type: + query = case_insensitive_filter(query, FundingView.appeal_type, appeal_type) + + query = apply_reference_period_filter(query, ref_period_parameters, FundingView) + + query = apply_pagination(query, pagination_parameters) + + logger.debug(f'Executing SQL query: {query}') + + result = await db.execute(query) + funding = result.scalars().all() + + logger.info(f'Retrieved {len(funding)} rows from the database') + + return funding diff --git a/hdx_hapi/endpoints/get_funding.py b/hdx_hapi/endpoints/get_funding.py new file mode 100644 index 00000000..e8b13541 --- /dev/null +++ b/hdx_hapi/endpoints/get_funding.py @@ -0,0 +1,60 @@ +from typing import Annotated, Optional +from fastapi import APIRouter, Depends, Query + +from sqlalchemy.ext.asyncio import AsyncSession + +from hdx_hapi.config.doc_snippets import DOC_LOCATION_CODE, DOC_LOCATION_NAME, DOC_SEE_LOC +from hdx_hapi.endpoints.models.base import HapiGenericResponse +from hdx_hapi.endpoints.models.funding import FundingResponse +from hdx_hapi.endpoints.util.util import ( + CommonEndpointParams, + OutputFormat, + ReferencePeriodParameters, + common_endpoint_parameters, + reference_period_parameters, +) +from hdx_hapi.services.csv_transform_logic import transform_result_to_csv_stream_if_requested +from hdx_hapi.services.funding_logic import get_funding_srv +from hdx_hapi.services.sql_alchemy_session import get_db + + +router = APIRouter( + tags=['Funding'], +) + + +@router.get( + '/api/coordination-context/funding', + response_model=HapiGenericResponse[FundingResponse], + summary='Funding endpoint', + include_in_schema=False, +) +@router.get( + '/api/v1/coordination-context/funding', + response_model=HapiGenericResponse[FundingResponse], + summary='Funding endpoint', +) +async def get_fundings( + ref_period_parameters: Annotated[ReferencePeriodParameters, Depends(reference_period_parameters)], + common_parameters: Annotated[CommonEndpointParams, Depends(common_endpoint_parameters)], + db: AsyncSession = Depends(get_db), + appeal_code: Annotated[Optional[str], Query(max_length=32, description='Appeal code')] = None, + appeal_type: Annotated[Optional[str], Query(max_length=32, description='Appeal type')] = None, + location_code: Annotated[ + Optional[str], Query(max_length=128, description=f'{DOC_LOCATION_CODE} {DOC_SEE_LOC}') + ] = None, + location_name: Annotated[ + Optional[str], Query(max_length=512, description=f'{DOC_LOCATION_NAME} {DOC_SEE_LOC}') + ] = None, + output_format: OutputFormat = OutputFormat.JSON, +): + result = await get_funding_srv( + pagination_parameters=common_parameters, + ref_period_parameters=ref_period_parameters, + db=db, + appeal_code=appeal_code, + appeal_type=appeal_type, + location_code=location_code, + location_name=location_name, + ) + return transform_result_to_csv_stream_if_requested(result, output_format, FundingResponse) diff --git a/hdx_hapi/endpoints/get_operational_presence.py b/hdx_hapi/endpoints/get_operational_presence.py index 229feb4c..c743e704 100644 --- a/hdx_hapi/endpoints/get_operational_presence.py +++ b/hdx_hapi/endpoints/get_operational_presence.py @@ -36,10 +36,7 @@ tags=['3W Operational Presence'], ) -SUMMARY_TEXT = ( - 'Get the list of organizations present and in which humanitarian sectors they are working. ' - "There are two versions of this endpoint to support the uppercase and lowercase 'w'" -) +SUMMARY_TEXT = 'Get the list of organizations present and in which humanitarian sectors they are working.' @router.get( diff --git a/hdx_hapi/endpoints/models/funding.py b/hdx_hapi/endpoints/models/funding.py new file mode 100644 index 00000000..0a611cb0 --- /dev/null +++ b/hdx_hapi/endpoints/models/funding.py @@ -0,0 +1,25 @@ +from pydantic import ConfigDict, Field, NaiveDatetime +from typing import Optional + +from hdx_hapi.endpoints.models.base import HapiBaseModel + + +class FundingResponse(HapiBaseModel): + resource_hdx_id: str = Field(max_length=36) + + appeal_code: str = Field(max_length=32) + appeal_name: str = Field(max_length=256) + appeal_type: str = Field(max_length=32) + + requirements_usd: float = Field(ge=0.0) + funding_usd: float = Field(ge=0.0) + funding_pct: float = Field(ge=0.0) + + location_ref: int + location_code: str = Field(max_length=128) + location_name: str = Field(max_length=512) + + reference_period_start: NaiveDatetime + reference_period_end: Optional[NaiveDatetime] + + model_config = ConfigDict(from_attributes=True) diff --git a/hdx_hapi/services/csv_transform_logic.py b/hdx_hapi/services/csv_transform_logic.py index 72efd5f7..85113458 100644 --- a/hdx_hapi/services/csv_transform_logic.py +++ b/hdx_hapi/services/csv_transform_logic.py @@ -1,7 +1,7 @@ import csv import io -from typing import Dict, List, Type +from typing import Dict, Sequence, Type from fastapi.responses import StreamingResponse from hdx_hapi.endpoints.models.base import HapiBaseModel @@ -13,8 +13,8 @@ def transform_result_to_csv_stream_if_requested( - result: List[Dict], output_format: OutputFormat, pydantic_class: Type[HapiBaseModel] -) -> List[Dict] | StreamingResponse: + result: Sequence[Dict], output_format: OutputFormat, pydantic_class: Type[HapiBaseModel] +) -> Dict[str, Sequence] | StreamingResponse: """ Transforms the result to a CSV stream if requested. Otherwise, returns the result as is """ diff --git a/hdx_hapi/services/funding_logic.py b/hdx_hapi/services/funding_logic.py new file mode 100644 index 00000000..b0db1c9b --- /dev/null +++ b/hdx_hapi/services/funding_logic.py @@ -0,0 +1,34 @@ +from typing import Optional, Sequence +from sqlalchemy.ext.asyncio import AsyncSession + +from hdx_hapi.db.dao.funding_view_dao import funding_view_list +from hdx_hapi.db.models.views.all_views import FundingView +from hdx_hapi.endpoints.util.util import PaginationParams, ReferencePeriodParameters + + +async def get_funding_srv( + pagination_parameters: PaginationParams, + ref_period_parameters: ReferencePeriodParameters, + db: AsyncSession, + appeal_code: Optional[str] = None, + appeal_type: Optional[str] = None, + org_acronym: Optional[str] = None, + org_name: Optional[str] = None, + sector_name: Optional[str] = None, + # location_ref: Optional[int] = None, + location_code: Optional[str] = None, + location_name: Optional[str] = None, +) -> Sequence[FundingView]: + + return await funding_view_list( + pagination_parameters=pagination_parameters, + ref_period_parameters=ref_period_parameters, + db=db, + appeal_code=appeal_code, + appeal_type=appeal_type, + org_acronym=org_acronym, + org_name=org_name, + sector_name=sector_name, + location_code=location_code, + location_name=location_name, + ) diff --git a/main.py b/main.py index 8f21f771..4aaa312a 100644 --- a/main.py +++ b/main.py @@ -14,9 +14,11 @@ from hdx_hapi.endpoints.get_encoded_identifier import router as encoded_identifier_router # noqa -# from hdx_hapi.endpoints.favicon import router as favicon_router # noqa +from hdx_hapi.endpoints.favicon import router as favicon_router # noqa + # from hdx_hapi.endpoints.get_population import router as population_router # noqa from hdx_hapi.endpoints.get_operational_presence import router as operational_presence_router # noqa +from hdx_hapi.endpoints.get_funding import router as funding_router # noqa from hdx_hapi.endpoints.get_admin_level import router as admin_level_router # noqa from hdx_hapi.endpoints.get_hdx_metadata import router as dataset_router # noqa from hdx_hapi.endpoints.get_humanitarian_response import router as humanitarian_response_router # noqa @@ -45,8 +47,9 @@ ) app.include_router(encoded_identifier_router) -# app.include_router(favicon_router) +app.include_router(favicon_router) app.include_router(operational_presence_router) +app.include_router(funding_router) # app.include_router(population_router) # app.include_router(food_security_router) # app.include_router(national_risk_router) diff --git a/tests/conftest.py b/tests/conftest.py index b69ecc38..d0b30c88 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -36,6 +36,7 @@ 'tests/sample_data/org_type.sql', 'tests/sample_data/org.sql', 'tests/sample_data/operational_presence.sql', + 'tests/sample_data/funding.sql', ] VIEW_LIST = [ diff --git a/tests/sample_data/funding.sql b/tests/sample_data/funding.sql new file mode 100644 index 00000000..65da4cb3 --- /dev/null +++ b/tests/sample_data/funding.sql @@ -0,0 +1,4 @@ +-- Funding +INSERT INTO funding (resource_hdx_id, appeal_code, appeal_name, appeal_type, location_ref, requirements_usd, funding_usd, funding_pct, reference_period_start, reference_period_end) +VALUES +('17acb541-9431-409a-80a8-50eda7e8ebab', 'HFOO24', 'Foolandia HRP 2024', 'HRP', 1, 100000.3, 50000.7, 50, '2023-01-01 00:00:00', NULL); \ No newline at end of file diff --git a/tests/test_endpoints/endpoint_data.py b/tests/test_endpoints/endpoint_data.py index 83f167ce..6a0c058c 100644 --- a/tests/test_endpoints/endpoint_data.py +++ b/tests/test_endpoints/endpoint_data.py @@ -82,6 +82,30 @@ 'reference_period_end', ], }, + '/api/v1/coordination-context/funding': { + 'query_parameters': { + 'appeal_code': 'hfoo24', + 'appeal_type': 'hRp', + 'location_code': 'foo', + 'location_name': 'Foolandia', + 'reference_period_start_min': '2020-01-01T00:00:00', + 'reference_period_start_max': '2024-01-01T00:00:00', + }, + 'expected_fields': [ + 'resource_hdx_id', + 'appeal_code', + 'appeal_name', + 'appeal_type', + 'requirements_usd', + 'funding_usd', + 'funding_pct', + 'location_ref', + 'location_code', + 'location_name', + 'reference_period_start', + 'reference_period_end', + ], + }, '/api/v1/coordination-context/operational-presence': { 'query_parameters': { 'dataset_hdx_provider_stub': 'PROVIDER01', diff --git a/tests/test_endpoints/test_funding_endpoint.py b/tests/test_endpoints/test_funding_endpoint.py new file mode 100644 index 00000000..8b6d407e --- /dev/null +++ b/tests/test_endpoints/test_funding_endpoint.py @@ -0,0 +1,61 @@ +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/coordination-context/funding' +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_fundings(event_loop, refresh_db): + log.info('started test_get_fundings') + 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 funding in the database' + + +@pytest.mark.asyncio +async def test_get_funding_params(event_loop, refresh_db): + log.info('started test_get_funding_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, ( + 'There should be at least one funding entry for parameter ' + f'"{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 funding entry for all parameters in the database' + + +@pytest.mark.asyncio +async def test_get_funding_result(event_loop, refresh_db): + log.info('started test_get_funding_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' + From 200b8a13fa467a3775d577229e897f43e86647b9 Mon Sep 17 00:00:00 2001 From: alexandru-m-g Date: Sat, 18 May 2024 11:14:16 +0000 Subject: [PATCH 3/3] HDX-9818 refactoring pydantic models with admins --- hdx_hapi/endpoints/models/base.py | 38 ++++++++++++++++++- hdx_hapi/endpoints/models/food_security.py | 36 ++---------------- .../endpoints/models/humanitarian_needs.py | 36 ++---------------- .../endpoints/models/operational_presence.py | 35 ++--------------- hdx_hapi/endpoints/models/population.py | 35 ++--------------- 5 files changed, 48 insertions(+), 132 deletions(-) diff --git a/hdx_hapi/endpoints/models/base.py b/hdx_hapi/endpoints/models/base.py index d9689ffd..677cc5e4 100644 --- a/hdx_hapi/endpoints/models/base.py +++ b/hdx_hapi/endpoints/models/base.py @@ -1,5 +1,6 @@ -from typing import Generic, List, TypeVar -from pydantic import BaseModel, ConfigDict +from typing import Generic, List, Optional, TypeVar +from typing_extensions import Self +from pydantic import BaseModel, ConfigDict, Field, model_validator class HapiBaseModel(BaseModel): @@ -7,6 +8,39 @@ def list_of_fields(self) -> List[str]: return list(self.model_fields.keys()) +class HapiModelWithAdmins(BaseModel): + location_ref: int + location_code: str = Field(max_length=128) + location_name: str = Field(max_length=512) + + admin1_is_unspecified: bool = Field(exclude=True) + admin2_is_unspecified: bool = Field(exclude=True) + + admin1_ref: int + admin1_code: Optional[str] = Field(max_length=128) + admin1_name: Optional[str] = Field(max_length=512) + admin2_ref: int + admin2_code: Optional[str] = Field(max_length=128) + admin2_name: Optional[str] = Field(max_length=512) + + @model_validator(mode='after') # type: ignore + def set_admin1_admin2_null(self) -> Self: + admin1_is_unspecified = self.admin1_is_unspecified + admin2_is_unspecified = self.admin2_is_unspecified + + # If 'admin1_is_unspecified' is True, set 'admin1_code' and 'admin1_name' to None + if admin1_is_unspecified: + self.admin1_code = None + self.admin1_name = None + + # If 'admin2_is_unspecified' is True, set 'admin2_code' and 'admin2_name' to None + if admin2_is_unspecified: + self.admin2_code = None + self.admin2_name = None + + return self + + DataT = TypeVar('DataT') diff --git a/hdx_hapi/endpoints/models/food_security.py b/hdx_hapi/endpoints/models/food_security.py index 3c5aaccc..ec784511 100644 --- a/hdx_hapi/endpoints/models/food_security.py +++ b/hdx_hapi/endpoints/models/food_security.py @@ -1,11 +1,11 @@ from datetime import datetime -from pydantic import ConfigDict, Field, model_validator, NaiveDatetime +from pydantic import ConfigDict, Field, NaiveDatetime from typing import Optional -from hdx_hapi.endpoints.models.base import HapiBaseModel +from hdx_hapi.endpoints.models.base import HapiBaseModel, HapiModelWithAdmins -class FoodSecurityResponse(HapiBaseModel): +class FoodSecurityResponse(HapiBaseModel, HapiModelWithAdmins): population_in_phase: int population_fraction_in_phase: float @@ -22,34 +22,4 @@ class FoodSecurityResponse(HapiBaseModel): hapi_updated_date: datetime hapi_replaced_date: Optional[datetime] - location_code: str = Field(max_length=128) - location_name: str = Field(max_length=512) - - admin1_is_unspecified: bool = Field(exclude=True) - admin2_is_unspecified: bool = Field(exclude=True) - - admin1_code: Optional[str] = Field(max_length=128) - admin1_name: Optional[str] = Field(max_length=512) - location_ref: int = None - admin2_code: Optional[str] = Field(max_length=128) - admin2_name: Optional[str] = Field(max_length=512) - admin1_ref: int = None - model_config = ConfigDict(from_attributes=True) - - @model_validator(mode='after') - def set_admin1_admin2_null(self) -> 'FoodSecurityResponse': - admin1_is_unspecified = self.admin1_is_unspecified - admin2_is_unspecified = self.admin2_is_unspecified - - # If 'admin1_is_unspecified' is True, set 'admin1_code' and 'admin1_name' to None - if admin1_is_unspecified: - self.admin1_code = None - self.admin1_name = None - - # If 'admin2_is_unspecified' is True, set 'admin2_code' and 'admin2_name' to None - if admin2_is_unspecified: - self.admin2_code = None - self.admin2_name = None - - return self diff --git a/hdx_hapi/endpoints/models/humanitarian_needs.py b/hdx_hapi/endpoints/models/humanitarian_needs.py index 07d20f91..68f7499b 100644 --- a/hdx_hapi/endpoints/models/humanitarian_needs.py +++ b/hdx_hapi/endpoints/models/humanitarian_needs.py @@ -1,11 +1,11 @@ from datetime import datetime -from pydantic import ConfigDict, Field, model_validator, NaiveDatetime +from pydantic import ConfigDict, Field, NaiveDatetime from typing import Optional -from hdx_hapi.endpoints.models.base import HapiBaseModel +from hdx_hapi.endpoints.models.base import HapiBaseModel, HapiModelWithAdmins -class HumanitarianNeedsResponse(HapiBaseModel): +class HumanitarianNeedsResponse(HapiBaseModel, HapiModelWithAdmins): gender_code: Optional[str] = Field(max_length=1) age_range_code: Optional[str] = Field(max_length=32) disabled_marker: Optional[bool] = None @@ -25,34 +25,4 @@ class HumanitarianNeedsResponse(HapiBaseModel): hapi_updated_date: datetime hapi_replaced_date: Optional[datetime] - location_code: str = Field(max_length=128) - location_name: str = Field(max_length=512) - - admin1_is_unspecified: bool = Field(exclude=True) - admin2_is_unspecified: bool = Field(exclude=True) - - admin1_code: Optional[str] = Field(max_length=128) - admin1_name: Optional[str] = Field(max_length=512) - location_ref: int = None - admin2_code: Optional[str] = Field(max_length=128) - admin2_name: Optional[str] = Field(max_length=512) - admin1_ref: int = None - model_config = ConfigDict(from_attributes=True) - - @model_validator(mode='after') - def set_admin1_admin2_null(self) -> 'HumanitarianNeedsResponse': - admin1_is_unspecified = self.admin1_is_unspecified - admin2_is_unspecified = self.admin2_is_unspecified - - # If 'admin1_is_unspecified' is True, set 'admin1_code' and 'admin1_name' to None - if admin1_is_unspecified: - self.admin1_code = None - self.admin1_name = None - - # If 'admin2_is_unspecified' is True, set 'admin2_code' and 'admin2_name' to None - if admin2_is_unspecified: - self.admin2_code = None - self.admin2_name = None - - return self diff --git a/hdx_hapi/endpoints/models/operational_presence.py b/hdx_hapi/endpoints/models/operational_presence.py index ac8a0dd0..191dbee2 100644 --- a/hdx_hapi/endpoints/models/operational_presence.py +++ b/hdx_hapi/endpoints/models/operational_presence.py @@ -1,19 +1,16 @@ -from pydantic import ConfigDict, Field, model_validator, NaiveDatetime +from pydantic import ConfigDict, Field, NaiveDatetime from typing import Optional -from hdx_hapi.endpoints.models.base import HapiBaseModel +from hdx_hapi.endpoints.models.base import HapiBaseModel, HapiModelWithAdmins -class OperationalPresenceResponse(HapiBaseModel): +class OperationalPresenceResponse(HapiBaseModel, HapiModelWithAdmins): # dataset_hdx_stub: str = Field(max_length=128) resource_hdx_id: str = Field(max_length=36) org_acronym: str = Field(max_length=32) org_name: str = Field(max_length=512) sector_code: str = Field(max_length=32) sector_name: str = Field(max_length=512) - location_ref: int - location_code: str = Field(max_length=128) - location_name: str = Field(max_length=512) reference_period_start: NaiveDatetime reference_period_end: Optional[NaiveDatetime] @@ -21,15 +18,6 @@ class OperationalPresenceResponse(HapiBaseModel): # hapi_updated_date: datetime # hapi_replaced_date: Optional[datetime] - admin1_is_unspecified: bool = Field(exclude=True) - admin2_is_unspecified: bool = Field(exclude=True) - - admin1_ref: int - admin1_code: Optional[str] = Field(max_length=128) - admin1_name: Optional[str] = Field(max_length=512) - admin2_ref: int - admin2_code: Optional[str] = Field(max_length=128) - admin2_name: Optional[str] = Field(max_length=512) # resource_update_date: datetime # org_ref: int = None, # dataset_hdx_id: str = Field(max_length=36), @@ -41,20 +29,3 @@ class OperationalPresenceResponse(HapiBaseModel): # org_type_description: str = Field(max_length=512), model_config = ConfigDict(from_attributes=True) - - @model_validator(mode='after') # type: ignore - def set_admin1_admin2_null(self) -> 'OperationalPresenceResponse': - admin1_is_unspecified = self.admin1_is_unspecified - admin2_is_unspecified = self.admin2_is_unspecified - - # If 'admin1_is_unspecified' is True, set 'admin1_code' and 'admin1_name' to None - if admin1_is_unspecified: - self.admin1_code = None - self.admin1_name = None - - # If 'admin2_is_unspecified' is True, set 'admin2_code' and 'admin2_name' to None - if admin2_is_unspecified: - self.admin2_code = None - self.admin2_name = None - - return self diff --git a/hdx_hapi/endpoints/models/population.py b/hdx_hapi/endpoints/models/population.py index 54ff257c..b84fd012 100644 --- a/hdx_hapi/endpoints/models/population.py +++ b/hdx_hapi/endpoints/models/population.py @@ -1,11 +1,11 @@ from datetime import datetime -from pydantic import ConfigDict, Field, model_validator, NaiveDatetime +from pydantic import ConfigDict, Field, NaiveDatetime from typing import Optional -from hdx_hapi.endpoints.models.base import HapiBaseModel +from hdx_hapi.endpoints.models.base import HapiBaseModel, HapiModelWithAdmins -class PopulationResponse(HapiBaseModel): +class PopulationResponse(HapiBaseModel, HapiModelWithAdmins): gender_code: Optional[str] = Field(max_length=1) age_range_code: Optional[str] = Field(max_length=32) population: int @@ -17,34 +17,5 @@ class PopulationResponse(HapiBaseModel): resource_hdx_id: str = Field(max_length=36) hapi_updated_date: datetime hapi_replaced_date: Optional[datetime] - location_code: str = Field(max_length=128) - location_name: str = Field(max_length=512) - - admin1_is_unspecified: bool = Field(exclude=True) - admin2_is_unspecified: bool = Field(exclude=True) - - admin1_code: Optional[str] = Field(max_length=128) - admin1_name: Optional[str] = Field(max_length=512) - location_ref: int = None - admin2_code: Optional[str] = Field(max_length=128) - admin2_name: Optional[str] = Field(max_length=512) - admin1_ref: int = None model_config = ConfigDict(from_attributes=True) - - @model_validator(mode='after') - def set_admin1_admin2_null(self) -> 'PopulationResponse': - admin1_is_unspecified = self.admin1_is_unspecified - admin2_is_unspecified = self.admin2_is_unspecified - - # If 'admin1_is_unspecified' is True, set 'admin1_code' and 'admin1_name' to None - if admin1_is_unspecified: - self.admin1_code = None - self.admin1_name = None - - # If 'admin2_is_unspecified' is True, set 'admin2_code' and 'admin2_name' to None - if admin2_is_unspecified: - self.admin2_code = None - self.admin2_name = None - - return self