From 3ab094614a3e73a3e77a52f2c08d1b580c7af067 Mon Sep 17 00:00:00 2001 From: Mohammad Torkashvand <mohammad.torkashvand@geant.org> Date: Fri, 10 May 2024 12:27:25 +0200 Subject: [PATCH] make lat and long string refactor site validation --- gso/cli/imports.py | 12 ------- gso/products/product_blocks/site.py | 38 +++++++++++--------- gso/utils/helpers.py | 1 + gso/workflows/site/create_imported_site.py | 15 ++------ test/cli/test_imports.py | 8 ++--- test/fixtures.py | 4 +-- test/schemas/test_types.py | 42 +++++++++++----------- 7 files changed, 52 insertions(+), 68 deletions(-) diff --git a/gso/cli/imports.py b/gso/cli/imports.py index cdf01bf52..e829fae07 100644 --- a/gso/cli/imports.py +++ b/gso/cli/imports.py @@ -20,7 +20,6 @@ from gso.db.models import PartnerTable from gso.products import ProductType from gso.products.product_blocks.iptrunk import IptrunkType, PhysicalPortCapacity from gso.products.product_blocks.router import RouterRole -from gso.products.product_blocks.site import SiteTier from gso.services.partners import PartnerNotFoundError, get_partner_by_name from gso.services.subscriptions import ( get_active_router_subscriptions, @@ -36,17 +35,6 @@ app: typer.Typer = typer.Typer() class SiteImportModel(BaseSiteValidatorModel): """The required input for importing an existing :class:`gso.products.product_types.site`.""" - site_name: str - site_city: str - site_country: str - site_country_code: str - site_latitude: float - site_longitude: float - site_bgp_community_id: int - site_internal_id: int - site_tier: SiteTier - site_ts_address: str - partner: str class RouterImportModel(BaseModel): diff --git a/gso/products/product_blocks/site.py b/gso/products/product_blocks/site.py index c6e932c49..bdc0694de 100644 --- a/gso/products/product_blocks/site.py +++ b/gso/products/product_blocks/site.py @@ -5,9 +5,14 @@ from typing import Annotated from orchestrator.domain.base import ProductBlockModel from orchestrator.types import SubscriptionLifecycle, strEnum -from pydantic import AfterValidator, Field +from pydantic import AfterValidator from typing_extensions import Doc +MAX_LONGITUDE = 180 +MIN_LONGITUDE = -180 + +MAX_LATITUDE = 90 +MIN_LATITUDE = -90 class SiteTier(strEnum): """The tier of a site, ranging from 1 to 4. @@ -22,31 +27,36 @@ class SiteTier(strEnum): TIER4 = 4 -def validate_latitude(v: float) -> float: +def validate_latitude(v: str) -> str: """Validate a latitude coordinate.""" + msg = "Invalid latitude coordinate. Valid examples: '40.7128', '-74.0060', '90', '-90', '0'." regex = re.compile(r"^-?([1-8]?\d(\.\d+)?|90(\.0+)?)$") if not regex.match(str(v)): - msg = "Invalid latitude coordinate. Valid examples: '40.7128', '-74.0060', '90', '-90', '0'." raise ValueError(msg) + + float_v = float(v) + if float_v > MAX_LATITUDE or float_v < MIN_LATITUDE: + raise ValueError(msg) + return v -def validate_longitude(v: float) -> float: +def validate_longitude(v: str) -> str: """Validate a longitude coordinate.""" regex = re.compile(r"^-?(180(\.0+)?|((1[0-7]\d)|([1-9]?\d))(\.\d+)?)$") - if not regex.match(str(v)): - msg = "Invalid longitude coordinate. Valid examples: '40.7128', '-74.0060', '180', '-180', '0'." + msg = "Invalid longitude coordinate. Valid examples: '40.7128', '-74.0060', '180', '-180', '0'." + if not regex.match(v): + raise ValueError(msg) + + float_v = float(v) + if float_v > MAX_LONGITUDE or float_v < MIN_LONGITUDE: raise ValueError(msg) return v LatitudeCoordinate = Annotated[ - float, - Field( - ge=-90, - le=90, - ), + str, AfterValidator(validate_latitude), Doc( "A latitude coordinate, modeled as a string. " @@ -56,11 +66,7 @@ LatitudeCoordinate = Annotated[ ] LongitudeCoordinate = Annotated[ - float, - Field( - ge=-180, - le=180, - ), + str, AfterValidator(validate_longitude), Doc( "A longitude coordinate, modeled as a string. " diff --git a/gso/utils/helpers.py b/gso/utils/helpers.py index f7ca91dd0..28269a3da 100644 --- a/gso/utils/helpers.py +++ b/gso/utils/helpers.py @@ -213,6 +213,7 @@ class BaseSiteValidatorModel(BaseModel): site_country: str site_latitude: LatitudeCoordinate site_longitude: LongitudeCoordinate + partner: str @field_validator("site_ts_address") def validate_ts_address(cls, site_ts_address: str) -> str: diff --git a/gso/workflows/site/create_imported_site.py b/gso/workflows/site/create_imported_site.py index 5f3c328f2..01a0e6f6c 100644 --- a/gso/workflows/site/create_imported_site.py +++ b/gso/workflows/site/create_imported_site.py @@ -14,6 +14,7 @@ from gso.products.product_blocks.site import LatitudeCoordinate, LongitudeCoordi from gso.products.product_types.site import ImportedSiteInactive from gso.services import subscriptions from gso.services.partners import get_partner_by_name +from gso.utils.helpers import BaseSiteValidatorModel @step("Create subscription") @@ -32,21 +33,9 @@ def create_subscription(partner: str) -> State: def generate_initial_input_form() -> FormGenerator: """Generate a form that is filled in using information passed through the :term:`API` endpoint.""" - class ImportSite(FormPage): + class ImportSite(FormPage, BaseSiteValidatorModel): model_config = ConfigDict(title="Import Site") - site_name: str - site_city: str - site_country: str - site_country_code: str - site_latitude: float - site_longitude: float - site_bgp_community_id: int - site_internal_id: int - site_tier: SiteTier - site_ts_address: str - partner: str - user_input = yield ImportSite return user_input.dict() diff --git a/test/cli/test_imports.py b/test/cli/test_imports.py index dc42bfa82..9b2498842 100644 --- a/test/cli/test_imports.py +++ b/test/cli/test_imports.py @@ -103,8 +103,8 @@ def site_data(faker, temp_file): "site_city": faker.city(), "site_country": faker.country(), "site_country_code": faker.country_code(), - "site_latitude": float(faker.latitude()), - "site_longitude": float(faker.longitude()), + "site_latitude": str(faker.latitude()), + "site_longitude": str(faker.longitude()), "site_bgp_community_id": faker.pyint(), "site_internal_id": faker.pyint(), "site_tier": SiteTier.TIER1, @@ -228,12 +228,12 @@ def test_import_site_with_invalid_data(mock_start_process, site_data, capfd): assert "Validation error: 2 validation errors for SiteImportModel" in captured_output assert ( """site_latitude - Input should be a valid number [type=float_type, input_value=None, input_type=NoneType]""" + Input should be a valid string [type=string_type, input_value=None, input_type=NoneType]""" in captured_output ) assert ( """site_longitude - Input should be a valid number, unable to parse string as a number [type=float_parsing, input_value='broken',""" + Value error, Invalid longitude coordinate. Valid examples: '40.7128', '-74.0060', '180', '-180', '0'. [type=value_error, input_value='broken', input_type=str]""" in captured_output ) diff --git a/test/fixtures.py b/test/fixtures.py index 96107fd64..883fc4cf1 100644 --- a/test/fixtures.py +++ b/test/fixtures.py @@ -59,8 +59,8 @@ def site_subscription_factory(faker, geant_partner): site_city = site_city or faker.city() site_country = site_country or faker.country() site_country_code = site_country_code or faker.country_code() - site_latitude = site_latitude or float(faker.latitude()) - site_longitude = site_longitude or float(faker.longitude()) + site_latitude = site_latitude or str(faker.latitude()) + site_longitude = site_longitude or str(faker.longitude()) site_bgp_community_id = site_bgp_community_id or faker.pyint() site_internal_id = site_internal_id or faker.pyint() site_ts_address = site_ts_address or faker.ipv4() diff --git a/test/schemas/test_types.py b/test/schemas/test_types.py index 6f43bb10b..e5d39001d 100644 --- a/test/schemas/test_types.py +++ b/test/schemas/test_types.py @@ -15,18 +15,18 @@ class LongitudeModel(BaseModel): @pytest.mark.parametrize( ("input_value", "is_valid"), [ - (40.7128, True), - (-74.0060, True), - (90, True), - (-90, True), - (0, True), - (45.6, True), - (91, False), - (-91, False), - (180, False), - (-180, False), + ('40.7128', True), + ('-74.0060', True), + ('90', True), + ('-90', True), + ('0', True), + ('45.6', True), + ('91', False), + ('-91', False), + ('180', False), + ('-180', False), ("abc", False), - (90.1, False), + ('90.1', False), ], ) def test_latitude(input_value, is_valid): @@ -40,16 +40,16 @@ def test_latitude(input_value, is_valid): @pytest.mark.parametrize( ("input_value", "is_valid"), [ - (40.7128, True), - (-74.0060, True), - (180, True), - (-180, True), - (0, True), - (90.1, True), - (181, False), - (-181, False), - (200, False), - (-200, False), + ('40.7128', True), + ('-74.0060', True), + ('180', True), + ('-180', True), + ('0', True), + ('90.1', True), + ('181', False), + ('-181', False), + ('200', False), + ('-200', False), ("abc", False), ("90a", False), ], -- GitLab