diff --git a/gso/cli/imports.py b/gso/cli/imports.py index cdf01bf52cea049cbfd397db7badf64187698a34..c4dc1adf2cd9080a4401ceb34415de674d1d561e 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,18 +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): """Required fields for importing an existing :class:`gso.product.product_types.router`.""" diff --git a/gso/products/product_blocks/site.py b/gso/products/product_blocks/site.py index c6e932c4974c043953593a1b73fb499855bf540f..be7d086adc021992ede2048b6ef4a843c1793755 100644 --- a/gso/products/product_blocks/site.py +++ b/gso/products/product_blocks/site.py @@ -5,9 +5,15 @@ 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 +28,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 +67,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 f7ca91dd0428aa88a0ca79737d7842465d6dcaf0..28269a3da83dc0f745ef31a46361e9c6952e7d2f 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 5f3c328f2e088d420f5e21d568816f5117bd633c..01a0e6f6c83ca0ac73e59426dfad639d4ad11851 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 dc42bfa8204bc629915decaec19ab9578d76a976..9b2498842e0897c40f8c954e8a52bf597716cc8c 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 96107fd646f0da31232c0460cb72d868c4e1a86a..883fc4cf1245d0d30cfee5accceac4ac96bbba73 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 6f43bb10bb87d8e8b634a4249eba768d5f5af246..a968084f06e2674828b6a59df58484e1fd965851 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), ],