diff --git a/poetry.lock b/poetry.lock index fced7e8..284ddd6 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2,33 +2,33 @@ [[package]] name = "black" -version = "23.12.1" +version = "24.10.0" description = "The uncompromising code formatter." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "black-23.12.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e0aaf6041986767a5e0ce663c7a2f0e9eaf21e6ff87a5f95cbf3675bfd4c41d2"}, - {file = "black-23.12.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c88b3711d12905b74206227109272673edce0cb29f27e1385f33b0163c414bba"}, - {file = "black-23.12.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a920b569dc6b3472513ba6ddea21f440d4b4c699494d2e972a1753cdc25df7b0"}, - {file = "black-23.12.1-cp310-cp310-win_amd64.whl", hash = "sha256:3fa4be75ef2a6b96ea8d92b1587dd8cb3a35c7e3d51f0738ced0781c3aa3a5a3"}, - {file = "black-23.12.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8d4df77958a622f9b5a4c96edb4b8c0034f8434032ab11077ec6c56ae9f384ba"}, - {file = "black-23.12.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:602cfb1196dc692424c70b6507593a2b29aac0547c1be9a1d1365f0d964c353b"}, - {file = "black-23.12.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c4352800f14be5b4864016882cdba10755bd50805c95f728011bcb47a4afd59"}, - {file = "black-23.12.1-cp311-cp311-win_amd64.whl", hash = "sha256:0808494f2b2df923ffc5723ed3c7b096bd76341f6213989759287611e9837d50"}, - {file = "black-23.12.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:25e57fd232a6d6ff3f4478a6fd0580838e47c93c83eaf1ccc92d4faf27112c4e"}, - {file = "black-23.12.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2d9e13db441c509a3763a7a3d9a49ccc1b4e974a47be4e08ade2a228876500ec"}, - {file = "black-23.12.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d1bd9c210f8b109b1762ec9fd36592fdd528485aadb3f5849b2740ef17e674e"}, - {file = "black-23.12.1-cp312-cp312-win_amd64.whl", hash = "sha256:ae76c22bde5cbb6bfd211ec343ded2163bba7883c7bc77f6b756a1049436fbb9"}, - {file = "black-23.12.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1fa88a0f74e50e4487477bc0bb900c6781dbddfdfa32691e780bf854c3b4a47f"}, - {file = "black-23.12.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a4d6a9668e45ad99d2f8ec70d5c8c04ef4f32f648ef39048d010b0689832ec6d"}, - {file = "black-23.12.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b18fb2ae6c4bb63eebe5be6bd869ba2f14fd0259bda7d18a46b764d8fb86298a"}, - {file = "black-23.12.1-cp38-cp38-win_amd64.whl", hash = "sha256:c04b6d9d20e9c13f43eee8ea87d44156b8505ca8a3c878773f68b4e4812a421e"}, - {file = "black-23.12.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3e1b38b3135fd4c025c28c55ddfc236b05af657828a8a6abe5deec419a0b7055"}, - {file = "black-23.12.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4f0031eaa7b921db76decd73636ef3a12c942ed367d8c3841a0739412b260a54"}, - {file = "black-23.12.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97e56155c6b737854e60a9ab1c598ff2533d57e7506d97af5481141671abf3ea"}, - {file = "black-23.12.1-cp39-cp39-win_amd64.whl", hash = "sha256:dd15245c8b68fe2b6bd0f32c1556509d11bb33aec9b5d0866dd8e2ed3dba09c2"}, - {file = "black-23.12.1-py3-none-any.whl", hash = "sha256:78baad24af0f033958cad29731e27363183e140962595def56423e626f4bee3e"}, - {file = "black-23.12.1.tar.gz", hash = "sha256:4ce3ef14ebe8d9509188014d96af1c456a910d5b5cbf434a09fef7e024b3d0d5"}, + {file = "black-24.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e6668650ea4b685440857138e5fe40cde4d652633b1bdffc62933d0db4ed9812"}, + {file = "black-24.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1c536fcf674217e87b8cc3657b81809d3c085d7bf3ef262ead700da345bfa6ea"}, + {file = "black-24.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:649fff99a20bd06c6f727d2a27f401331dc0cc861fb69cde910fe95b01b5928f"}, + {file = "black-24.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:fe4d6476887de70546212c99ac9bd803d90b42fc4767f058a0baa895013fbb3e"}, + {file = "black-24.10.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5a2221696a8224e335c28816a9d331a6c2ae15a2ee34ec857dcf3e45dbfa99ad"}, + {file = "black-24.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f9da3333530dbcecc1be13e69c250ed8dfa67f43c4005fb537bb426e19200d50"}, + {file = "black-24.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4007b1393d902b48b36958a216c20c4482f601569d19ed1df294a496eb366392"}, + {file = "black-24.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:394d4ddc64782e51153eadcaaca95144ac4c35e27ef9b0a42e121ae7e57a9175"}, + {file = "black-24.10.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b5e39e0fae001df40f95bd8cc36b9165c5e2ea88900167bddf258bacef9bbdc3"}, + {file = "black-24.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d37d422772111794b26757c5b55a3eade028aa3fde43121ab7b673d050949d65"}, + {file = "black-24.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:14b3502784f09ce2443830e3133dacf2c0110d45191ed470ecb04d0f5f6fcb0f"}, + {file = "black-24.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:30d2c30dc5139211dda799758559d1b049f7f14c580c409d6ad925b74a4208a8"}, + {file = "black-24.10.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1cbacacb19e922a1d75ef2b6ccaefcd6e93a2c05ede32f06a21386a04cedb981"}, + {file = "black-24.10.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1f93102e0c5bb3907451063e08b9876dbeac810e7da5a8bfb7aeb5a9ef89066b"}, + {file = "black-24.10.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ddacb691cdcdf77b96f549cf9591701d8db36b2f19519373d60d31746068dbf2"}, + {file = "black-24.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:680359d932801c76d2e9c9068d05c6b107f2584b2a5b88831c83962eb9984c1b"}, + {file = "black-24.10.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:17374989640fbca88b6a448129cd1745c5eb8d9547b464f281b251dd00155ccd"}, + {file = "black-24.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:63f626344343083322233f175aaf372d326de8436f5928c042639a4afbbf1d3f"}, + {file = "black-24.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfa1d0cb6200857f1923b602f978386a3a2758a65b52e0950299ea014be6800"}, + {file = "black-24.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:2cd9c95431d94adc56600710f8813ee27eea544dd118d45896bb734e9d7a0dc7"}, + {file = "black-24.10.0-py3-none-any.whl", hash = "sha256:3bb2b7a1f7b685f85b11fed1ef10f8a9148bceb49853e47a294a3dd963c1dd7d"}, + {file = "black-24.10.0.tar.gz", hash = "sha256:846ea64c97afe3bc677b761787993be4991810ecc7a4a937816dd6bddedc4875"}, ] [package.dependencies] @@ -42,7 +42,7 @@ typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} [package.extras] colorama = ["colorama (>=0.4.3)"] -d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"] +d = ["aiohttp (>=3.10)"] jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] uvloop = ["uvloop (>=0.15.2)"] @@ -521,4 +521,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "^3.9" -content-hash = "367611111f643b946a28255d3b78a494212ffb16ec60b02ab30ae9483d50c91d" +content-hash = "44d0f486da0bcfa47387c00e4741a05c81a67171d0132155c6a00d838580f7a3" diff --git a/pyproject.toml b/pyproject.toml index 0599af5..3611890 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,7 +19,7 @@ shapely = "^2.0.2" [tool.poetry.group.linting.dependencies] -black = "^23.12.1" +black = "^24.10" isort = "^5.13.2" pyright = "^1.1.369" flake8 = "^7.0.0" diff --git a/src/frformat/custom_format.py b/src/frformat/custom_format.py index 9d49e9b..e14e234 100644 --- a/src/frformat/custom_format.py +++ b/src/frformat/custom_format.py @@ -19,8 +19,7 @@ class CustomFormat(ABC, Generic[ValueType]): formatter: Formatter = DefaultFormatter[ValueType]() @abstractmethod - def is_valid(self, value: ValueType) -> bool: - ... + def is_valid(self, value: ValueType) -> bool: ... def format(self, value: ValueType) -> str: if not self.is_valid(value): diff --git a/src/frformat/formats/canton.py b/src/frformat/formats/canton.py index e456e6f..520466d 100644 --- a/src/frformat/formats/canton.py +++ b/src/frformat/formats/canton.py @@ -10,4 +10,4 @@ canton_versioned_data.add_version(Millesime.M2023, CANTON_COG_2023) canton_versioned_data.add_version(Millesime.M2024, CANTON_COG_2024) -Canton = set_format.new_geo("Canton", name, description, canton_versioned_data) +Canton = set_format.new("Canton", name, description, canton_versioned_data) diff --git a/src/frformat/formats/code_commune_insee.py b/src/frformat/formats/code_commune_insee.py index 036d117..4aa6858 100644 --- a/src/frformat/formats/code_commune_insee.py +++ b/src/frformat/formats/code_commune_insee.py @@ -17,6 +17,6 @@ Millesime.M2024, CODES_COMMUNES_INSEE_COG_2024 ) -CodeCommuneInsee = set_format.new_geo( +CodeCommuneInsee = set_format.new( "CodeCommuneInsee", name, description, code_commune_insee_versioned_data ) diff --git a/src/frformat/formats/code_pays.py b/src/frformat/formats/code_pays.py index 5044a07..80f3964 100644 --- a/src/frformat/formats/code_pays.py +++ b/src/frformat/formats/code_pays.py @@ -15,7 +15,7 @@ code_pays_IS02_versioned_data.add_version(Millesime.M2023, CODES_PAYS_ISO2_COG_2023) code_pays_IS02_versioned_data.add_version(Millesime.M2024, CODES_PAYS_ISO2_COG_2024) -CodePaysISO2 = set_format.new_geo( +CodePaysISO2 = set_format.new( "CodePaysISO2", name, description, code_pays_IS02_versioned_data ) @@ -27,6 +27,6 @@ code_pays_IS03_versioned_data.add_version(Millesime.M2023, CODES_PAYS_ISO3_COG_2023) code_pays_IS03_versioned_data.add_version(Millesime.M2024, CODES_PAYS_ISO3_COG_2024) -CodePaysISO3 = set_format.new_geo( +CodePaysISO3 = set_format.new( "CodePaysISO3", name, description, code_pays_IS03_versioned_data ) diff --git a/src/frformat/formats/code_region.py b/src/frformat/formats/code_region.py index 4d960ac..7f5b6f5 100644 --- a/src/frformat/formats/code_region.py +++ b/src/frformat/formats/code_region.py @@ -34,6 +34,4 @@ code_region_versioned_data.add_version(Millesime.M2023, CODES_REGIONS_COG_2023) code_region_versioned_data.add_version(Millesime.M2024, CODES_REGIONS_COG_2024) -CodeRegion = set_format.new_geo( - "CodeRegion", name, description, code_region_versioned_data -) +CodeRegion = set_format.new("CodeRegion", name, description, code_region_versioned_data) diff --git a/src/frformat/formats/commune.py b/src/frformat/formats/commune.py index f85587f..048c80a 100644 --- a/src/frformat/formats/commune.py +++ b/src/frformat/formats/commune.py @@ -13,4 +13,4 @@ commune_versioned_data.add_version(Millesime.M2023, COMMUNES_COG_2023) commune_versioned_data.add_version(Millesime.M2024, COMMUNES_COG_2024) -Commune = set_format.new_geo("Commune", name, description, commune_versioned_data) +Commune = set_format.new("Commune", name, description, commune_versioned_data) diff --git a/src/frformat/formats/departement.py b/src/frformat/formats/departement.py index c276302..697d255 100644 --- a/src/frformat/formats/departement.py +++ b/src/frformat/formats/departement.py @@ -13,6 +13,6 @@ departement_versioned_data.add_version(Millesime.M2023, DEPARTEMENTS_COG_2023) departement_versioned_data.add_version(Millesime.M2024, DEPARTEMENTS_COG_2024) -Departement = set_format.new_geo( +Departement = set_format.new( "Departement", name, description, departement_versioned_data ) diff --git a/src/frformat/formats/numero_departement.py b/src/frformat/formats/numero_departement.py index 81f2a14..c4e0381 100644 --- a/src/frformat/formats/numero_departement.py +++ b/src/frformat/formats/numero_departement.py @@ -24,6 +24,6 @@ Millesime.M2024, NUMEROS_DEPARTEMENTS_COG_2024 ) -NumeroDepartement = set_format.new_geo( +NumeroDepartement = set_format.new( "NumeroDepartement", name, description, numero_departement_versioned_data ) diff --git a/src/frformat/formats/pays.py b/src/frformat/formats/pays.py index e0e50e5..771c71f 100644 --- a/src/frformat/formats/pays.py +++ b/src/frformat/formats/pays.py @@ -11,4 +11,4 @@ pays_versioned_data = VersionedSet[Millesime]() pays_versioned_data.add_version(Millesime.M2024, PAYS_COG_2024) -Pays = set_format.new_geo("Pays", name, description, pays_versioned_data) +Pays = set_format.new("Pays", name, description, pays_versioned_data) diff --git a/src/frformat/formats/region.py b/src/frformat/formats/region.py index c6d1235..4e67deb 100644 --- a/src/frformat/formats/region.py +++ b/src/frformat/formats/region.py @@ -8,8 +8,9 @@ "Vérifie les régions françaises valides pour un Code Officiel Géographique donné" ) + region_versioned_data = VersionedSet[Millesime]() region_versioned_data.add_version(Millesime.M2023, REGIONS_COG_2023) region_versioned_data.add_version(Millesime.M2024, REGIONS_COG_2024) -Region = set_format.new_geo("Region", name, description, region_versioned_data) +Region = set_format.new("Region", name, description, region_versioned_data) diff --git a/src/frformat/formatter.py b/src/frformat/formatter.py index b98e87d..dd28666 100644 --- a/src/frformat/formatter.py +++ b/src/frformat/formatter.py @@ -7,8 +7,7 @@ class Formatter(Protocol, Generic[ValueType]): - def format(self, value: ValueType) -> str: - ... + def format(self, value: ValueType) -> str: ... class DefaultFormatter(Generic[ValueType]): diff --git a/src/frformat/set_format.py b/src/frformat/set_format.py index 277e824..97f0604 100644 --- a/src/frformat/set_format.py +++ b/src/frformat/set_format.py @@ -6,13 +6,11 @@ - GenericSetFormat creates a validator with valid data passed on the fly - `new` creates specialized versions where data is tied to the class -- `new_geo` creates an even more specialized version for geographical data - from INSEE """ from enum import Enum from functools import total_ordering -from typing import FrozenSet, Type, TypeVar, Union +from typing import FrozenSet, Generic, Type, TypeVar, Union, overload from frformat import CustomStrFormat, Metadata from frformat.common import normalize_value @@ -20,22 +18,53 @@ from frformat.versioned_set import Version, VersionedSet -class GenericSetFormat(CustomStrFormat): - """A format that checks if a value is among a set of valid values. +@total_ordering +class Millesime(Enum): + """Millesime class implements the `Version` protocol methods.""" + + M2023 = "2023" + M2024 = "2024" + LATEST = "latest" + + def __eq__(self, other) -> bool: + return self.value == other.value + + def __lt__(self, other) -> bool: + return self.value < other.value + + def get_id(self) -> str: + return self.value + + @classmethod + def is_sorted(cls) -> bool: + return True + + +class SingleSetFormat(CustomStrFormat): + """This format defines a closed list of valid values""" + + _valid_values: FrozenSet = frozenset() + """Dataset of valid values. + + Technical details: - In the generic version, valid data is passed at object initialisation. + Beware, child classes may define an instance `_valid_values` attribute, which + will always take precedence over the class attribute for the validation. """ - def __init__(self, valid_data: FrozenSet, options: Options = Options()): + def __init__(self, options: Options = Options()): self._options = options - self._data = valid_data normalized_extra_values = { normalize_value(e, self._options) for e in self._options.extra_valid_values } self._normalized_values = { - normalize_value(e, self._options) for e in self._data + normalize_value(e, self._options) + for e in self._valid_values + # in child classes, `self._valid_values` can reference an instance + # attribute, if applicable ; otherwise the class attribute will + # be used }.union(normalized_extra_values) def is_valid(self, value: str) -> bool: @@ -46,12 +75,52 @@ def is_valid(self, value: str) -> bool: V = TypeVar("V", bound="Version") +class VersionedSetFormat(SingleSetFormat, Generic[V]): + """This format defines a closed set of valid values, with different + versions to choose from. + + Specific implementation details : + + - the type will hint at which version class to use for initializing the format validator. + - a description of the format can be consulted with `MyClass.metadata.description` or at the top of `help(MyClass)` + + Technical details: + + - In the versioned set format, the `_valid_values` attribute is an instance attribute, + which takes precedence over the class attribute of the mother class. The + reason for this is that the exact valid values set is only known on instantiation. + """ + + _versioned_valid_values: VersionedSet = VersionedSet() + + def __init__(self, version: Union[V, str], options: Options = Options()): + version_id = version if isinstance(version, str) else version.get_id() + data = self._versioned_valid_values.get_data(version_id) + if data is None: + raise ValueError(f"No data available for version: {version_id}") + + self._valid_values = data + super().__init__(options) + + +@overload +def new( + class_name: str, name: str, description: str, valid_data: VersionedSet[V] +) -> Type[VersionedSetFormat[V]]: ... + + +@overload +def new( + class_name: str, name: str, description: str, valid_data: FrozenSet +) -> Type[SingleSetFormat]: ... + + def new( class_name: str, name: str, description: str, valid_data: Union[VersionedSet[V], FrozenSet[str]], -) -> Type: +) -> Union[Type[VersionedSetFormat[V]], Type[SingleSetFormat]]: """Utility function to create a specialized version of a SetFormat. The returned class is a fully featured format that once initialized @@ -62,76 +131,24 @@ def new( """ if isinstance(valid_data, VersionedSet): - class VersionedSetFormat(GenericSetFormat): - def __init__(self, version: Union[V, str], options: Options = Options()): - version_id = version if isinstance(version, str) else version.get_id() - data = valid_data.get_data(version_id) - if data is None: - raise ValueError(f"No data available for version: {version_id}") - - super().__init__(data, options) + class NewVersionedFormat(VersionedSetFormat): + _versioned_valid_values = valid_data - specialized_set_format = VersionedSetFormat + specialized_set_format = NewVersionedFormat else: - class SingleSetFormat(GenericSetFormat): - def __init__(self, options: Options = Options()): - super().__init__(valid_data, options) + class NewFormat(SingleSetFormat): + _valid_values = valid_data - specialized_set_format = SingleSetFormat + specialized_set_format = NewFormat specialized_set_format.__name__ = class_name specialized_set_format.__qualname__ = class_name + specialized_set_format.__doc__ = ( + f"{description}\n\n{specialized_set_format.__doc__}" + ) + specialized_set_format.metadata = Metadata(name, description) return specialized_set_format - - -################################ -# Insee Geo format ############# -################################ - - -@total_ordering -class Millesime(Enum): - """Millesime class implements the `Version` protocol methods.""" - - M2023 = "2023" - M2024 = "2024" - LATEST = "latest" - - def __eq__(self, other) -> bool: - return self.value == other.value - - def __lt__(self, other) -> bool: - return self.value < other.value - - def get_id(self) -> str: - return self.value - - @classmethod - def is_sorted(cls) -> bool: - return True - - -def new_geo( - class_name: str, name: str, description: str, valid_data: VersionedSet[Millesime] -) -> Type: - """A set format specialized on Insee geographical data, versioned by - the Millesime enum. - - The main difference is that the __init__ function takes a "cog" parameter for the version, - which means "Code officiel géographique" (Official Geographical Code). - - """ - VersionedSetFormat = new(class_name, name, description, valid_data) - - original_init = VersionedSetFormat.__init__ - - def new_init(self, cog: Union[Millesime, str], options=Options()): - original_init(self, cog, options) # type: ignore - - setattr(VersionedSetFormat, "__init__", new_init) - - return VersionedSetFormat diff --git a/src/frformat/versioned_set.py b/src/frformat/versioned_set.py index 34f5d18..165a84c 100644 --- a/src/frformat/versioned_set.py +++ b/src/frformat/versioned_set.py @@ -16,8 +16,7 @@ class Version(Protocol): - def get_id(self) -> str: - ... + def get_id(self) -> str: ... @classmethod def is_sorted(cls) -> bool: @@ -32,23 +31,17 @@ class _SortableVersion(Version, Protocol): """A version subclass that is sortable For type checking purposes only""" - def __lt__(self, v) -> bool: - ... + def __lt__(self, v) -> bool: ... - def __le__(self, v) -> bool: - ... + def __le__(self, v) -> bool: ... - def __gt__(self, v) -> bool: - ... + def __gt__(self, v) -> bool: ... - def __ge__(self, v) -> bool: - ... + def __ge__(self, v) -> bool: ... - def __eq__(self, v) -> bool: - ... + def __eq__(self, v) -> bool: ... - def __ne__(self, v) -> bool: - ... + def __ne__(self, v) -> bool: ... class VersionedSet(Generic[V]): diff --git a/src/tests/test_geo_data_format.py b/src/tests/test_geo_data_validation.py similarity index 96% rename from src/tests/test_geo_data_format.py rename to src/tests/test_geo_data_validation.py index d48eb28..85d13e3 100644 --- a/src/tests/test_geo_data_format.py +++ b/src/tests/test_geo_data_validation.py @@ -54,4 +54,4 @@ def test_geo_data_format(): test_format = FormatTest(tc["version"]) assert ( test_format.is_valid(tc["value_to_test"]) == tc["expected_valid"] - ), f'Error on geo data format definition with version { tc["expected_version"] } and value { tc["value_to_test"] }' + ), f'Error on geo data format definition with version { tc["version"] } and value { tc["value_to_test"] }' diff --git a/src/tests/test_geo_format.py b/src/tests/test_geo_format.py new file mode 100644 index 0000000..9423f1e --- /dev/null +++ b/src/tests/test_geo_format.py @@ -0,0 +1,310 @@ +from frformat import ( + Canton, + CodeCommuneInsee, + CodeFantoir, + CodePaysISO2, + CodePaysISO3, + CodePostal, + CodeRegion, + Commune, + Departement, + LatitudeL93, + LongitudeL93, + Millesime, + NumeroDepartement, + Pays, + Region, +) +from frformat.common import NBSP, NNBSP + + +def test_insee_geo_format(): + class ValidatorTest: + """ + This class tests all INSEE geographical formats, versioned by the Millesime enum. + + The __init__ function takes a cog parameter for the version, + which refers to the "Code officiel géographique" (Official Geographical Code). + """ + + def __init__(self, cog, validTestCases, invalidTestCases, formatClass): + self.cog = cog + self.validTestCases = validTestCases + self.invalidTestCases = invalidTestCases + self.formatClass = formatClass + + def test_valid_cases(self): + for tc in self.validTestCases: + assert self.formatClass(self.cog).is_valid( + tc + ), f"Check that {tc} is not valid when the class format is {self.formatClass} and the cog is equal to {self.cog}" + + def test_invalid_cases(self): + for tc in self.invalidTestCases: + assert not self.formatClass(self.cog).is_valid( + tc + ), f"Check that {tc} is valid when the class format is {self.formatClass} and the cog is equal to {self.cog}." + + def run_all_tests(self): + self.test_valid_cases() + self.test_invalid_cases() + + def test_all_validators_with_cog(): + test_cases = [ + { + "name": "code_region_millesime2023", + "cog": Millesime.M2023, + "formatClass": CodeRegion, + "validTestCases": ["01", "75"], + "invalidTestCases": ["AA", "00", "7 5"], + }, + { + "name": "code_region_millesime2024", + "cog": Millesime.M2024, + "formatClass": CodeRegion, + "validTestCases": ["01", "75"], + "invalidTestCases": ["AA", "00", "7 5"], + }, + { + "name": "region_millesime2023", + "cog": Millesime.M2023, + "formatClass": Region, + "validTestCases": ["Centre-Val de Loire", "La Réunion", "Corse"], + "invalidTestCases": [ + "Beleriand", + "Canyon Cosmo", + "corse", + "Centre val de Loire", + "la reunion", + ], + }, + { + "name": "region_millesime2024", + "cog": Millesime.M2024, + "formatClass": Region, + "validTestCases": ["Centre-Val de Loire", "La Réunion", "Corse"], + "invalidTestCases": [ + "Beleriand", + "Canyon Cosmo", + "corse", + "Centre val de Loire", + "la reunion", + ], + }, + { + "name": "commune_millesime2023", + "cog": Millesime.M2023, + "formatClass": Commune, + "validTestCases": [ + "La Chapelle-Achard", + "Beaumont-les-Nonains", + "La Moncelle", + "Montestrucq", + ], + "invalidTestCases": [ + "Costa del Sol", + "Val-d'Usiers", + "La Chapelle-Fleurigné", + "Oullins-Pierre-Bénite", + "la chapelle Fleurigne", + "Costa-del Sol", + ], + }, + { + "name": "commune_millesime2024", + "cog": Millesime.M2024, + "formatClass": Commune, + "validTestCases": [ + "Oullins-Pierre-Bénite", + "Bellac", + "Val-d'Usiers", + "La Chapelle-Fleurigné", + ], + "invalidTestCases": [ + "Costa del Sol", + "Montestrucq", + "La Chapelle-Achard", + "Senonville", + "montestrucq", + "La Chapelle Achard", + ], + }, + { + "name": "canton_millesime2023", + "cog": Millesime.M2023, + "formatClass": Canton, + "validTestCases": [ + "Lagnieu", + "Meximieux", + ], + "invalidTestCases": ["Paris", "Lyon", "paris"], + }, + { + "name": "canton_millesime2024", + "cog": Millesime.M2024, + "formatClass": Canton, + "validTestCases": ["Paris", "Lyon"], + "invalidTestCases": ["Saint Quentin", "saint quentin"], + }, + { + "name": "code_paysISO2_millesime2023", + "cog": Millesime.M2023, + "formatClass": CodePaysISO2, + "validTestCases": ["BV", "SJ"], + "invalidTestCases": ["RWA", "TCD", "rwa"], + }, + { + "name": "code_paysISO2_millesime2024", + "cog": Millesime.M2024, + "formatClass": CodePaysISO2, + "validTestCases": ["FR", "JP"], + "invalidTestCases": ["BV", "SJ", "bv"], + }, + { + "name": "code_paysISO3_millesime2023", + "cog": Millesime.M2023, + "formatClass": CodePaysISO3, + "validTestCases": ["BVT", "SJM"], + "invalidTestCases": ["BF", "GH", "gh"], + }, + { + "name": "code_paysISO3_millesime2024", + "cog": Millesime.M2024, + "formatClass": CodePaysISO3, + "validTestCases": ["FRA", "JPN"], + "invalidTestCases": ["BVT", "SJM", "bvt"], + }, + { + "name": "departement_millesime2023", + "cog": Millesime.M2023, + "formatClass": Departement, + "validTestCases": ["Alpes-Maritimes", "Gard", "Mayotte", "Vendée"], + "invalidTestCases": [ + "Charente-Inférieure", + "Vendee", + "Alpes maritimes", + ], + }, + { + "name": "departement_millesime2024", + "cog": Millesime.M2024, + "formatClass": Departement, + "validTestCases": ["Alpes-Maritimes", "Gard", "Mayotte", "Vendée"], + "invalidTestCases": [ + "Charente-Inférieure", + "Vendee", + "Alpes maritimes", + ], + }, + { + "name": "pays_millesime2024", + "cog": Millesime.M2024, + "formatClass": Pays, + "validTestCases": ["France", "Pays-Bas", "Bosnie-Herzégovine"], + "invalidTestCases": ["L'Eldorado", "Zubrowska", "Pays Bas", "france"], + }, + { + "name": "numero_departement_millesime2023", + "cog": Millesime.M2023, + "formatClass": NumeroDepartement, + "validTestCases": ["05", "2B", "974"], + "invalidTestCases": ["99", "051", "2b", " 97 4"], + }, + { + "name": "numero_departement_millesime2024", + "cog": Millesime.M2024, + "formatClass": NumeroDepartement, + "validTestCases": ["05", "2B", "974"], + "invalidTestCases": ["99", "051", "2b", " 97 4"], + }, + { + "name": "code_commune_insee__millesime2023", + "cog": Millesime.M2023, + "formatClass": CodeCommuneInsee, + "validTestCases": ["01015", "2B002"], + "invalidTestCases": ["77777", " 01015"], + }, + { + "name": "code_commune_insee_millesime2024", + "cog": Millesime.M2024, + "formatClass": CodeCommuneInsee, + "validTestCases": ["64300", "2A331"], + "invalidTestCases": ["64402", "64 300"], + }, + ] + + for tc in test_cases: + validatorTest = ValidatorTest( + tc["cog"], + tc["validTestCases"], + tc["invalidTestCases"], + tc["formatClass"], + ) + validatorTest.run_all_tests() + + def test_code_commune_insee_format(): + code_commune_insee_cog_2023 = CodeCommuneInsee(Millesime.M2023) + code_commune_insee_cog_2024 = CodeCommuneInsee(Millesime.M2024) + + cog_2023_value = "01015" + assert code_commune_insee_cog_2023.format(cog_2023_value) == cog_2023_value + + cog_2024_value = "64300" + + assert code_commune_insee_cog_2024.format(cog_2024_value) == cog_2024_value + + +def test_geo_format(): + """This method tests geographical formats, which does not belong to the Official Geographic Code.""" + + def test_code_fantoir(): + fantoir_valid = "ZB03A" + fantoir_invalid = ["1000", "zB03A"] + + code_fantoir = CodeFantoir() + assert code_fantoir.is_valid(fantoir_valid) + for fi in fantoir_invalid: + assert not code_fantoir.is_valid(fi) + + def test_code_postal(): + value = "05560" + code_postal = CodePostal() + assert code_postal.is_valid(value) + assert code_postal.format(value) == value + codes_postales_invalides = ["77777", "2B002"] + for cpi in codes_postales_invalides: + assert not code_postal.is_valid(cpi) + + def test_longitude_l93(): + longitudel93 = LongitudeL93() + assert longitudel93.format(224234) == "224" + NNBSP + "234" + NBSP + "m" + assert longitudel93.format(224234.0) == "224" + NNBSP + "234,00" + NBSP + "m" + + invalid_test_cases = [-435522.3, -554234, 2076524, 5436780.23] + + for tc in invalid_test_cases: + assert not longitudel93.is_valid(tc) + + valid_test_cases = [0, 1234546, 1234546.32, -123554, -234.546] + + for tc in valid_test_cases: + assert longitudel93.is_valid(tc) + + def test_latitude_l93(): + latitudel93 = LatitudeL93() + assert ( + latitudel93.format(6757121) + == "6" + NNBSP + "757" + NNBSP + "121" + NBSP + "m" + ) + assert ( + latitudel93.format(6757121.337) + == "6" + NNBSP + "757" + NNBSP + "121,34" + NBSP + "m" + ) + + assert latitudel93.is_valid(6544234.2) + assert latitudel93.is_valid(7145278) + + invalid_test_cases = [0, -6145765.9, -7234567, 7233478, 6000658.5] + + for tc in invalid_test_cases: + assert not latitudel93.is_valid(tc) diff --git a/src/tests/test_geo_fr.py b/src/tests/test_geo_fr.py deleted file mode 100644 index 7cdd0cd..0000000 --- a/src/tests/test_geo_fr.py +++ /dev/null @@ -1,57 +0,0 @@ -from frformat import CodeFantoir, CodePostal, LatitudeL93, LongitudeL93 -from frformat.common import NBSP, NNBSP - - -def test_code_fantoir(): - fantoir_valid = "ZB03A" - fantoir_invalid = ["1000", "zB03A"] - - code_fantoir = CodeFantoir() - assert code_fantoir.is_valid(fantoir_valid) - for fi in fantoir_invalid: - assert not code_fantoir.is_valid(fi) - - -def test_code_postal(): - value = "05560" - code_postal = CodePostal() - assert code_postal.is_valid(value) - assert code_postal.format(value) == value - codes_postales_invalides = ["77777", "2B002"] - for cpi in codes_postales_invalides: - assert not code_postal.is_valid(cpi) - - -def test_longitude_l93(): - longitudel93 = LongitudeL93() - assert longitudel93.format(224234) == "224" + NNBSP + "234" + NBSP + "m" - assert longitudel93.format(224234.0) == "224" + NNBSP + "234,00" + NBSP + "m" - - invalid_test_cases = [-435522.3, -554234, 2076524, 5436780.23] - - for tc in invalid_test_cases: - assert not longitudel93.is_valid(tc) - - valid_test_cases = [0, 1234546, 1234546.32, -123554, -234.546] - - for tc in valid_test_cases: - assert longitudel93.is_valid(tc) - - -def test_latitude_l93(): - latitudel93 = LatitudeL93() - assert ( - latitudel93.format(6757121) == "6" + NNBSP + "757" + NNBSP + "121" + NBSP + "m" - ) - assert ( - latitudel93.format(6757121.337) - == "6" + NNBSP + "757" + NNBSP + "121,34" + NBSP + "m" - ) - - assert latitudel93.is_valid(6544234.2) - assert latitudel93.is_valid(7145278) - - invalid_test_cases = [0, -6145765.9, -7234567, 7233478, 6000658.5] - - for tc in invalid_test_cases: - assert not latitudel93.is_valid(tc) diff --git a/src/tests/test_geo_fr_with_cog.py b/src/tests/test_geo_fr_with_cog.py deleted file mode 100644 index e3b3606..0000000 --- a/src/tests/test_geo_fr_with_cog.py +++ /dev/null @@ -1,232 +0,0 @@ -from frformat import ( - Canton, - CodeCommuneInsee, - CodePaysISO2, - CodePaysISO3, - CodeRegion, - Commune, - Departement, - Millesime, - NumeroDepartement, - Pays, - Region, -) - - -class ValidatorTest: - def __init__(self, cog, validTestCases, invalidTestCases, formatClass): - self.cog = cog - self.validTestCases = validTestCases - self.invalidTestCases = invalidTestCases - self.formatClass = formatClass - - def test_valid_cases(self): - for tc in self.validTestCases: - assert self.formatClass(self.cog).is_valid( - tc - ), f"Check that {tc} is not valid when the class format is {self.formatClass} and the cog is equal to {self.cog}" - - def test_invalid_cases(self): - for tc in self.invalidTestCases: - assert not self.formatClass(self.cog).is_valid( - tc - ), f"Check that {tc} is valid when the class format is {self.formatClass} and the cog is equal to {self.cog}." - - def run_all_tests(self): - self.test_valid_cases() - self.test_invalid_cases() - - -def test_all_validators_with_cog(): - test_cases = [ - { - "name": "code_region_millesime2023", - "cog": Millesime.M2023, - "formatClass": CodeRegion, - "validTestCases": ["01", "75"], - "invalidTestCases": ["AA", "00", "7 5"], - }, - { - "name": "code_region_millesime2024", - "cog": Millesime.M2024, - "formatClass": CodeRegion, - "validTestCases": ["01", "75"], - "invalidTestCases": ["AA", "00", "7 5"], - }, - { - "name": "region_millesime2023", - "cog": Millesime.M2023, - "formatClass": Region, - "validTestCases": ["Centre-Val de Loire", "La Réunion", "Corse"], - "invalidTestCases": [ - "Beleriand", - "Canyon Cosmo", - "corse", - "Centre val de Loire", - "la reunion", - ], - }, - { - "name": "region_millesime2024", - "cog": Millesime.M2024, - "formatClass": Region, - "validTestCases": ["Centre-Val de Loire", "La Réunion", "Corse"], - "invalidTestCases": [ - "Beleriand", - "Canyon Cosmo", - "corse", - "Centre val de Loire", - "la reunion", - ], - }, - { - "name": "commune_millesime2023", - "cog": Millesime.M2023, - "formatClass": Commune, - "validTestCases": [ - "La Chapelle-Achard", - "Beaumont-les-Nonains", - "La Moncelle", - "Montestrucq", - ], - "invalidTestCases": [ - "Costa del Sol", - "Val-d'Usiers", - "La Chapelle-Fleurigné", - "Oullins-Pierre-Bénite", - "la chapelle Fleurigne", - "Costa-del Sol", - ], - }, - { - "name": "commune_millesime2024", - "cog": Millesime.M2024, - "formatClass": Commune, - "validTestCases": [ - "Oullins-Pierre-Bénite", - "Bellac", - "Val-d'Usiers", - "La Chapelle-Fleurigné", - ], - "invalidTestCases": [ - "Costa del Sol", - "Montestrucq", - "La Chapelle-Achard", - "Senonville", - "montestrucq", - "La Chapelle Achard", - ], - }, - { - "name": "canton_millesime2023", - "cog": Millesime.M2023, - "formatClass": Canton, - "validTestCases": [ - "Lagnieu", - "Meximieux", - ], - "invalidTestCases": ["Paris", "Lyon", "paris"], - }, - { - "name": "canton_millesime2024", - "cog": Millesime.M2024, - "formatClass": Canton, - "validTestCases": ["Paris", "Lyon"], - "invalidTestCases": ["Saint Quentin", "saint quentin"], - }, - { - "name": "code_paysISO2_millesime2023", - "cog": Millesime.M2023, - "formatClass": CodePaysISO2, - "validTestCases": ["BV", "SJ"], - "invalidTestCases": ["RWA", "TCD", "rwa"], - }, - { - "name": "code_paysISO2_millesime2024", - "cog": Millesime.M2024, - "formatClass": CodePaysISO2, - "validTestCases": ["FR", "JP"], - "invalidTestCases": ["BV", "SJ", "bv"], - }, - { - "name": "code_paysISO3_millesime2023", - "cog": Millesime.M2023, - "formatClass": CodePaysISO3, - "validTestCases": ["BVT", "SJM"], - "invalidTestCases": ["BF", "GH", "gh"], - }, - { - "name": "code_paysISO3_millesime2024", - "cog": Millesime.M2024, - "formatClass": CodePaysISO3, - "validTestCases": ["FRA", "JPN"], - "invalidTestCases": ["BVT", "SJM", "bvt"], - }, - { - "name": "departement_millesime2023", - "cog": Millesime.M2023, - "formatClass": Departement, - "validTestCases": ["Alpes-Maritimes", "Gard", "Mayotte", "Vendée"], - "invalidTestCases": ["Charente-Inférieure", "Vendee", "Alpes maritimes"], - }, - { - "name": "departement_millesime2024", - "cog": Millesime.M2024, - "formatClass": Departement, - "validTestCases": ["Alpes-Maritimes", "Gard", "Mayotte", "Vendée"], - "invalidTestCases": ["Charente-Inférieure", "Vendee", "Alpes maritimes"], - }, - { - "name": "pays_millesime2024", - "cog": Millesime.M2024, - "formatClass": Pays, - "validTestCases": ["France", "Pays-Bas", "Bosnie-Herzégovine"], - "invalidTestCases": ["L'Eldorado", "Zubrowska", "Pays Bas", "france"], - }, - { - "name": "numero_departement_millesime2023", - "cog": Millesime.M2023, - "formatClass": NumeroDepartement, - "validTestCases": ["05", "2B", "974"], - "invalidTestCases": ["99", "051", "2b", " 97 4"], - }, - { - "name": "numero_departement_millesime2024", - "cog": Millesime.M2024, - "formatClass": NumeroDepartement, - "validTestCases": ["05", "2B", "974"], - "invalidTestCases": ["99", "051", "2b", " 97 4"], - }, - { - "name": "code_commune_insee__millesime2023", - "cog": Millesime.M2023, - "formatClass": CodeCommuneInsee, - "validTestCases": ["01015", "2B002"], - "invalidTestCases": ["77777", " 01015"], - }, - { - "name": "code_commune_insee_millesime2024", - "cog": Millesime.M2024, - "formatClass": CodeCommuneInsee, - "validTestCases": ["64300", "2A331"], - "invalidTestCases": ["64402", "64 300"], - }, - ] - - for tc in test_cases: - validatorTest = ValidatorTest( - tc["cog"], tc["validTestCases"], tc["invalidTestCases"], tc["formatClass"] - ) - validatorTest.run_all_tests() - - -def test_code_commune_insee_format(): - code_commune_insee_cog_2023 = CodeCommuneInsee(Millesime.M2023) - code_commune_insee_cog_2024 = CodeCommuneInsee(Millesime.M2024) - - cog_2023_value = "01015" - assert code_commune_insee_cog_2023.format(cog_2023_value) == cog_2023_value - - cog_2024_value = "64300" - - assert code_commune_insee_cog_2024.format(cog_2024_value) == cog_2024_value