diff --git a/README.md b/README.md index 10cffc1..d52d215 100644 --- a/README.md +++ b/README.md @@ -130,4 +130,3 @@ Details how to manage database migrations using Alembic, including TimescaleDB-s ### 4. [Adding a New Endpoint](docs/add_new_endpoint.md) A step-by-step guide on how to add a new endpoint to the application. - diff --git a/controllers/metis_controller.py b/controllers/metis_controller.py index 57266b2..a4432c9 100644 --- a/controllers/metis_controller.py +++ b/controllers/metis_controller.py @@ -8,7 +8,7 @@ from config.database import get_db from typing import Optional from utils import page_size -from utils import validate_timebin_params, prepare_timebin_range +from utils import validate_timebin_params router = APIRouter(prefix="/metis/atlas", tags=["Metis"]) @@ -48,7 +48,7 @@ async def get_metis_atlas_deployments(
  • Limitations: At most 31 days of data can be fetched per request. For bulk downloads see: https://ihr-archive.iijlab.net/.
  • """ - timebin__gte, timebin__lte = prepare_timebin_range( + timebin__gte, timebin__lte = validate_timebin_params( timebin, timebin__gte, timebin__lte, max_days=31) deployments, total_count = MetisController.service.get_metis_atlas_deployments( @@ -108,7 +108,7 @@ async def get_metis_atlas_selections(
  • Limitations: At most 31 days of data can be fetched per request. For bulk downloads see: https://ihr-archive.iijlab.net/.
  • """ - timebin__gte, timebin__lte = prepare_timebin_range( + timebin__gte, timebin__lte = validate_timebin_params( timebin, timebin__gte, timebin__lte, max_days=31) selections, total_count = MetisController.service.get_metis_atlas_selections( diff --git a/controllers/tr_hegemony_controller.py b/controllers/tr_hegemony_controller.py index 2bafb52..4ef83b8 100644 --- a/controllers/tr_hegemony_controller.py +++ b/controllers/tr_hegemony_controller.py @@ -7,7 +7,7 @@ from typing import Optional from datetime import datetime from utils import page_size -from utils import prepare_timebin_range +from utils import validate_timebin_params router = APIRouter(prefix="/tr_hegemony", tags=["TR Hegemony"]) @@ -57,7 +57,7 @@ async def get_hegemony(
  • Limitations: At most 31 days of data can be fetched per request. For bulk downloads see: https://ihr-archive.iijlab.net/.
  • """ - timebin__gte, timebin__lte = prepare_timebin_range( + timebin__gte, timebin__lte = validate_timebin_params( timebin, timebin__gte, timebin__lte, max_days=31) hegemony_data, total_count = TRHegemonyController.service.get_tr_hegemony( diff --git a/docs/project_structure.md b/docs/project_structure.md index 8d7c317..9f83b96 100644 --- a/docs/project_structure.md +++ b/docs/project_structure.md @@ -6,14 +6,14 @@ This document provides an overview of the project's file and folder structure. E ```plaintext . -├── alembic/ # Database migration scripts -├── config/ # App configuration -├── controllers/ # API endpoints and HTTP route handlers (Controller Layer) +├── alembic/ # [Alembic] Database migration scripts +├── config/ # [API] App configuration +├── controllers/ # [API] API endpoints and HTTP route handlers (Controller Layer) ├── docs/ # Documentation files -├── dtos/ # Data Transfer Objects for request/response schemas -├── models/ # Database models and ORM classes (Model Layer) -├── repositories/ # Data access logic and database interaction (Repository Layer) -├── services/ # Business logic layer (Service Layer) +├── dtos/ # [API] Data Transfer Objects for request/response schemas +├── models/ # [API & Alembic] Database models and ORM classes (Model Layer) +├── repositories/ # [API] Data access logic and database interaction (Repository Layer) +├── services/ # [API] Business logic layer (Service Layer) ├── .env # Environment variables (e.g., database credentials) ├── .gitignore # Specifies intentionally untracked files to ignore ├── alembic.ini # Alembic configuration file diff --git a/repositories/atlas_delay_alarms_repository.py b/repositories/atlas_delay_alarms_repository.py index 782419a..1520bd3 100644 --- a/repositories/atlas_delay_alarms_repository.py +++ b/repositories/atlas_delay_alarms_repository.py @@ -4,6 +4,7 @@ from datetime import datetime from typing import List, Optional, Tuple from utils import page_size +from sqlalchemy import func class AtlasDelayAlarmsRepository: @@ -38,6 +39,12 @@ def get_alarms( .join(Startpoint, AtlasDelayAlarms.startpoint_relation)\ .join(Endpoint, AtlasDelayAlarms.endpoint_relation) + + # If no time filters specified, get rows with max timebin + if not timebin and not timebin_gte and not timebin_lte: + max_timebin = db.query(func.max(AtlasDelayAlarms.timebin)).scalar() + query = query.filter(AtlasDelayAlarms.timebin == max_timebin) + if timebin: query = query.filter(AtlasDelayAlarms.timebin == timebin) if timebin_gte: diff --git a/repositories/atlas_delay_repository.py b/repositories/atlas_delay_repository.py index 40c1ae6..f2c76a3 100644 --- a/repositories/atlas_delay_repository.py +++ b/repositories/atlas_delay_repository.py @@ -4,6 +4,7 @@ from datetime import datetime from typing import List, Optional, Tuple from utils import page_size +from sqlalchemy import func class AtlasDelayRepository: @@ -41,6 +42,12 @@ def get_delays( .join(Startpoint, AtlasDelay.startpoint_relation)\ .join(Endpoint, AtlasDelay.endpoint_relation) + + # If no time filters specified, get rows with max timebin + if not timebin and not timebin_gte and not timebin_lte: + max_timebin = db.query(func.max(AtlasDelay.timebin)).scalar() + query = query.filter(AtlasDelay.timebin == max_timebin) + # Apply timebin filters if timebin: query = query.filter(AtlasDelay.timebin == timebin) diff --git a/repositories/hegemony_alarms_repository.py b/repositories/hegemony_alarms_repository.py index e628104..b3883f1 100644 --- a/repositories/hegemony_alarms_repository.py +++ b/repositories/hegemony_alarms_repository.py @@ -3,6 +3,7 @@ from models.hegemony_alarms import HegemonyAlarms from typing import Optional, List, Tuple from utils import page_size +from sqlalchemy import func class HegemonyAlarmsRepository: @@ -21,6 +22,11 @@ def get_all( ) -> Tuple[List[HegemonyAlarms], int]: query = db.query(HegemonyAlarms) + # If no time filters specified, get rows with max timebin + if not timebin_gte and not timebin_lte: + max_timebin = db.query(func.max(HegemonyAlarms.timebin)).scalar() + query = query.filter(HegemonyAlarms.timebin == max_timebin) + # Apply filters if timebin_gte: query = query.filter(HegemonyAlarms.timebin >= timebin_gte) diff --git a/repositories/hegemony_cone_repository.py b/repositories/hegemony_cone_repository.py index 1eed074..52b8da7 100644 --- a/repositories/hegemony_cone_repository.py +++ b/repositories/hegemony_cone_repository.py @@ -3,6 +3,7 @@ from models.hegemony_cone import HegemonyCone from typing import Optional, List, Tuple from utils import page_size +from sqlalchemy import func class HegemonyConeRepository: @@ -18,6 +19,11 @@ def get_all( ) -> Tuple[List[HegemonyCone], int]: query = db.query(HegemonyCone) + # If no time filters specified, get rows with max timebin + if not timebin_gte and not timebin_lte: + max_timebin = db.query(func.max(HegemonyCone.timebin)).scalar() + query = query.filter(HegemonyCone.timebin == max_timebin) + # Apply filters if timebin_gte: query = query.filter(HegemonyCone.timebin >= timebin_gte) diff --git a/repositories/hegemony_country_repository.py b/repositories/hegemony_country_repository.py index 906cb68..691cb12 100644 --- a/repositories/hegemony_country_repository.py +++ b/repositories/hegemony_country_repository.py @@ -3,6 +3,7 @@ from models.hegemony_country import HegemonyCountry from typing import Optional, List, Tuple from utils import page_size +from sqlalchemy import func class HegemonyCountryRepository: @@ -24,6 +25,11 @@ def get_all( ) -> Tuple[List[HegemonyCountry], int]: query = db.query(HegemonyCountry) + # If no time filters specified, get rows with max timebin + if not timebin_gte and not timebin_lte: + max_timebin = db.query(func.max(HegemonyCountry.timebin)).scalar() + query = query.filter(HegemonyCountry.timebin == max_timebin) + # Apply filters if timebin_gte: query = query.filter(HegemonyCountry.timebin >= timebin_gte) diff --git a/repositories/hegemony_prefix_repository.py b/repositories/hegemony_prefix_repository.py index ee2b472..5c2a363 100644 --- a/repositories/hegemony_prefix_repository.py +++ b/repositories/hegemony_prefix_repository.py @@ -3,6 +3,7 @@ from models.hegemony_prefix import HegemonyPrefix from typing import Optional, List, Tuple from utils import page_size +from sqlalchemy import func class HegemonyPrefixRepository: @@ -29,6 +30,11 @@ def get_all( ) -> Tuple[List[HegemonyPrefix], int]: query = db.query(HegemonyPrefix) + # If no time filters specified, get rows with max timebin + if not timebin_gte and not timebin_lte: + max_timebin = db.query(func.max(HegemonyPrefix.timebin)).scalar() + query = query.filter(HegemonyPrefix.timebin == max_timebin) + # Apply filters if timebin_gte: query = query.filter(HegemonyPrefix.timebin >= timebin_gte) diff --git a/repositories/hegemony_repository.py b/repositories/hegemony_repository.py index ed7f902..2f2ca48 100644 --- a/repositories/hegemony_repository.py +++ b/repositories/hegemony_repository.py @@ -3,6 +3,7 @@ from models.hegemony import Hegemony from typing import Optional, List, Tuple from utils import page_size +from sqlalchemy import func class HegemonyRepository: @@ -22,6 +23,10 @@ def get_all( ) -> Tuple[List[Hegemony], int]: query = db.query(Hegemony) + # If no time filters specified, get rows with max timebin + if not timebin_gte and not timebin_lte: + max_timebin = db.query(func.max(Hegemony.timebin)).scalar() + query = query.filter(Hegemony.timebin == max_timebin) # Apply filters if timebin_gte: query = query.filter(Hegemony.timebin >= timebin_gte) diff --git a/repositories/metis_atlas_deployment_repository.py b/repositories/metis_atlas_deployment_repository.py index 2e4edcf..abe9967 100644 --- a/repositories/metis_atlas_deployment_repository.py +++ b/repositories/metis_atlas_deployment_repository.py @@ -3,6 +3,7 @@ from models.metis_atlas_deployment import MetisAtlasDeployment from typing import Optional, List, Tuple from utils import page_size +from sqlalchemy import func class MetisAtlasDeploymentRepository: @@ -23,6 +24,11 @@ def get_all( query = db.query(MetisAtlasDeployment).join( MetisAtlasDeployment.asn_relation) + # If no time filters specified, get rows with max timebin + if not timebin and not timebin_gte and not timebin_lte: + max_timebin = db.query(func.max(MetisAtlasDeployment.timebin)).scalar() + query = query.filter(MetisAtlasDeployment.timebin == max_timebin) + # Apply filters if timebin: query = query.filter(MetisAtlasDeployment.timebin == timebin) diff --git a/repositories/metis_atlas_selection_repository.py b/repositories/metis_atlas_selection_repository.py index 0d9d882..abd6e99 100644 --- a/repositories/metis_atlas_selection_repository.py +++ b/repositories/metis_atlas_selection_repository.py @@ -3,6 +3,7 @@ from models.metis_atlas_selection import MetisAtlasSelection from typing import Optional, List, Tuple from utils import page_size +from sqlalchemy import func class MetisAtlasSelectionRepository: @@ -23,6 +24,12 @@ def get_all( query = db.query(MetisAtlasSelection).join( MetisAtlasSelection.asn_relation) + # If no time filters specified, get rows with max timebin + if not timebin and not timebin_gte and not timebin_lte: + max_timebin = db.query( + func.max(MetisAtlasSelection.timebin)).scalar() + query = query.filter(MetisAtlasSelection.timebin == max_timebin) + # Apply filters if timebin: query = query.filter(MetisAtlasSelection.timebin == timebin) diff --git a/repositories/tr_hegemony_repository.py b/repositories/tr_hegemony_repository.py index 1a6f008..5f711c3 100644 --- a/repositories/tr_hegemony_repository.py +++ b/repositories/tr_hegemony_repository.py @@ -4,6 +4,7 @@ from datetime import datetime from typing import List, Optional, Tuple from utils import page_size +from sqlalchemy import func class TRHegemonyRepository: @@ -35,6 +36,11 @@ def get_tr_hegemony( .join(Origin, TRHegemony.origin_relation)\ .join(Dependency, TRHegemony.dependency_relation) + # If no time filters specified, get rows with max timebin + if not timebin and not timebin_gte and not timebin_lte: + max_timebin = db.query(func.max(TRHegemony.timebin)).scalar() + query = query.filter(TRHegemony.timebin == max_timebin) + if timebin: query = query.filter(TRHegemony.timebin == timebin) if timebin_gte: diff --git a/utils.py b/utils.py index f29bcde..fa53ec7 100644 --- a/utils.py +++ b/utils.py @@ -14,6 +14,7 @@ page_size = int(os.getenv("PAGE_SIZE")) + def validate_timebin_params( timebin: Optional[datetime], timebin_gte: Optional[datetime], @@ -21,15 +22,7 @@ def validate_timebin_params( max_days: int = 7 ) -> tuple[datetime, datetime]: - # Check if at least one time parameter exists - if not any([timebin, timebin_gte, timebin_lte]): - raise HTTPException( - status_code=400, - detail="No timebin parameter. Please provide a timebin value or a range of values with timebin__lte and timebin__gte." - ) - - # If timebin is not provided, both timebin_gte and timebin_lte must be provided - if not timebin and not (timebin_gte and timebin_lte): + if (not timebin_gte and timebin_lte) or (timebin_gte and not timebin_lte): raise HTTPException( status_code=400, detail="Invalid timebin range. Please provide both timebin__lte and timebin__gte." @@ -49,33 +42,4 @@ def validate_timebin_params( detail=f"The given timebin range is too large. Should be less than {max_days} days." ) - return timebin_gte, timebin_lte - - -def prepare_timebin_range( - timebin: Optional[datetime], - timebin_gte: Optional[datetime], - timebin_lte: Optional[datetime], - max_days: int = 7 -) -> Tuple[datetime, Optional[datetime]]: - - if (not timebin_gte and timebin_lte) or (timebin_gte and not timebin_lte): - raise HTTPException( - status_code=400, - detail="Invalid timebin range. Please provide both timebin__lte and timebin__gte." - ) - # If no time filters provided, default to last 6 days (including today) - if not any([timebin, timebin_gte, timebin_lte]): - today = datetime.combine(date.today(), datetime.min.time()) - timebin_gte = today - timedelta(days=6) - - # Validate range size if both are given - if timebin_gte and timebin_lte: - delta = timebin_lte - timebin_gte - if delta > timedelta(days=max_days): - raise HTTPException( - status_code=400, - detail=f"The given timebin range is too large. Should be less than {max_days} days." - ) - - return timebin_gte, timebin_lte + return timebin_gte, timebin_lte \ No newline at end of file