diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b45d559..55384c7 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -35,8 +35,9 @@ repos: language: python entry: mypy types: [python] - additional_dependencies: ["mypy==1.4.0", "pydantic", "pytest", "types-setuptools"] - args: ["--strict", "--install-types", "--non-interactive"] + args: [".", "--strict"] + pass_filenames: false + always_run: true - repo: local hooks: diff --git a/caep/schema.py b/caep/schema.py index 9f29751..c36f5a4 100755 --- a/caep/schema.py +++ b/caep/schema.py @@ -40,14 +40,12 @@ class FieldError(Exception): class ArrayInfo(BaseModel): array_type: type split: str = DEFAULT_SPLIT - min_size: int = 0 class DictInfo(BaseModel): dict_type: type split: str = DEFAULT_SPLIT kv_split: str = DEFAULT_KV_SPLIT - min_size: int = 0 Arrays = Dict[str, ArrayInfo] @@ -96,11 +94,6 @@ def split_dict( d[key.strip()] = dict_info.dict_type(val.strip()) - if len(d.keys()) < dict_info.min_size: - raise FieldError( - f"{field} have to few elements {len(d)} < {dict_info.min_size}" - ) - return d @@ -119,9 +112,6 @@ def split_list(value: str, array: ArrayInfo, field: Optional[str] = None) -> Lis # Split by configured split value, unless it is escaped lst = [array.array_type(v.strip()) for v in escape_split(value, array.split)] - if len(lst) < array.min_size: - raise FieldError(f"{field} have to few elements {len(lst)} < {array.min_size}") - return lst @@ -268,7 +258,6 @@ def build_parser( arrays[field] = ArrayInfo( array_type=array_type, split=schema.get("split", DEFAULT_SPLIT), - min_size=schema.get("min_size", 0), ) # For arrays (lists, sets etc), we parse as str in caep and split values by @@ -287,7 +276,6 @@ def build_parser( dict_type=dict_type, split=schema.get("split", DEFAULT_SPLIT), kv_split=schema.get("kv_split", DEFAULT_KV_SPLIT), - min_size=schema.get("min_size", 0), ) else: @@ -365,7 +353,7 @@ def load( # `model_json_schema` in pydantic 2.x. if PYDANTIC_MAJOR_VERSION == "2": - fields = model.model_json_schema(alias).get("properties") # type: ignore + fields = model.model_json_schema(alias).get("properties") else: fields = model.schema(alias).get("properties") diff --git a/setup.py b/setup.py index 3e4dbf5..155f03f 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ from os import path -from setuptools import setup +from setuptools import setup # type: ignore # read the contents of your README file this_directory = path.abspath(path.dirname(__file__)) @@ -11,7 +11,7 @@ setup( name="caep", - version="1.0.0b1", + version="1.0.0", author="mnemonic AS", zip_safe=True, author_email="opensource@mnemonic.no", @@ -31,6 +31,7 @@ "dev": [ "mypy", "pytest", + "types-setuptools", ] }, classifiers=[ diff --git a/tests/test_helpers.py b/tests/test_helpers.py index 759cd07..ad3df18 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -1,10 +1,10 @@ import shlex from pathlib import Path -from typing import Any, Dict, List, Optional +from typing import List, Optional import pytest from pydantic import BaseModel # noqa: E0611 -from pydantic import Field, root_validator +from pydantic import Field, model_validator from test_schema import parse_args import caep @@ -19,13 +19,15 @@ class ExampleConfig(BaseModel): password: Optional[str] = Field(description="Password") parent_id: Optional[str] = Field(description="Parent ID") - @root_validator(skip_on_failure=True) - def check_arguments(cls, values: Dict[str, Any]) -> Dict[str, Any]: + @model_validator(mode="after") # type: ignore + def check_arguments(cls, m: "ExampleConfig") -> "ExampleConfig": """If one argument is set, they should all be set""" - caep.raise_if_some_and_not_all(values, ["username", "password", "parent_id"]) + caep.raise_if_some_and_not_all( + m.__dict__, ["username", "password", "parent_id"] + ) - return values + return m def test_config_files() -> None: diff --git a/tests/test_schema.py b/tests/test_schema.py index 35edb95..4727e64 100644 --- a/tests/test_schema.py +++ b/tests/test_schema.py @@ -34,18 +34,23 @@ class Arguments(BaseModel): float_arg: float = Field(default=0.5, description="Float with default value") # List fields will be separated by space as default - intlist: List[int] = Field(description="Space separated list of ints", split=" ") + intlist: List[int] = Field( + description="Space separated list of ints", json_schema_extra={"split": " "} + ) # Can optionally use "split" argument to use another value to split based on strlist: List[str] = Field(description="Comma separated list of strings") # Set that will be separated by space (default) - strset: Set[str] = Field(description="Space separated set of strings", split=" ") + strset: Set[str] = Field( + description="Space separated set of strings", json_schema_extra={"split": " "} + ) dict_str: Dict[str, str] = Field(description="Str Dict split by comma and colon") dict_int: Dict[str, int] = Field( - description="Int Dict split by slash and dash", split="-", kv_split="/" + description="Int Dict split by slash and dash", + json_schema_extra={"split": "-", "kv_split": "/"}, ) ipv4: Optional[ipaddress.IPv4Address] = Field(description="IPv4 Address") @@ -56,7 +61,7 @@ class Arguments(BaseModel): class ArgNs2(BaseModel): - str_arg: str = Field(value="Unset", description="String argument") + str_arg: str = Field("Unset", description="String argument") class ArgNs1(BaseModel): @@ -81,6 +86,16 @@ class MultipleFiles(BaseModel): second_file: bool = Field(description="Value set in second config file") +class MinLength(BaseModel): + # Can optionally use "split" argument to use another value to split based on + strlist: List[str] = Field( + description="Comma separated list of strings", min_length=1 + ) + dict_str: Dict[str, str] = Field( + description="Str Dict split by comma and colon", min_length=2 + ) + + class ArgCombined(Arg1, Arg2, Arg3): pass @@ -196,8 +211,6 @@ def test_schema_commandline_dict_int() -> None: assert dict_int is not None assert dict_int is not {} - print(dict_int) - assert dict_int["a"] == 1 assert dict_int["b"] == 2 @@ -329,6 +342,27 @@ def test_schema_joined_schemas() -> None: assert config.enabled is True +def test_min_length() -> None: + """Test schema that is created based on three other schemas""" + + with pytest.raises(SystemExit): + parse_args(MinLength, shlex.split("--strlist arg1,arg2")) + + with pytest.raises(SystemExit): + parse_args(MinLength, shlex.split("--dict-str a:b,c:d")) + + config = parse_args( + MinLength, + shlex.split( + "--strlist arg1 --dict-str 'header 1: x option, header 2: y option'" + ), + ) + + assert config.strlist == ["arg1"] + assert config.dict_str["header 1"] == "x option" + assert config.dict_str["header 2"] == "y option" + + def test_escape_split() -> None: assert escape_split("A\\,B\\,C,1\\,2\\,3") == ["A,B,C", "1,2,3"] assert escape_split("ABC 123", split=" ") == ["ABC", "123"] @@ -344,10 +378,6 @@ def test_split_list() -> None: # Configure split value assert split_list("a b c", ArrayInfo(array_type=str, split=" ")) - # min_size - with pytest.raises(FieldError): - assert split_list("", ArrayInfo(array_type=str, min_size=1)) - def test_split_dict() -> None: # Defaults @@ -357,9 +387,5 @@ def test_split_dict() -> None: assert d["c"] == "value X" - with pytest.raises(FieldError): - v = split_dict("", DictInfo(dict_type=str, min_size=1)) - print(v) - with pytest.raises(FieldError): split_dict("a,b", DictInfo(dict_type=str))