otsuvalidator 1.1.0.312__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- otsuvalidator-1.1.0.312/LICENSE.txt +21 -0
- otsuvalidator-1.1.0.312/PKG-INFO +118 -0
- otsuvalidator-1.1.0.312/README.md +99 -0
- otsuvalidator-1.1.0.312/otsuvalidator/__init__.py +31 -0
- otsuvalidator-1.1.0.312/otsuvalidator/bases.py +137 -0
- otsuvalidator-1.1.0.312/otsuvalidator/converters.py +282 -0
- otsuvalidator-1.1.0.312/otsuvalidator/py.typed +0 -0
- otsuvalidator-1.1.0.312/otsuvalidator/types.py +20 -0
- otsuvalidator-1.1.0.312/otsuvalidator/validators.py +490 -0
- otsuvalidator-1.1.0.312/otsuvalidator.egg-info/PKG-INFO +118 -0
- otsuvalidator-1.1.0.312/otsuvalidator.egg-info/SOURCES.txt +14 -0
- otsuvalidator-1.1.0.312/otsuvalidator.egg-info/dependency_links.txt +1 -0
- otsuvalidator-1.1.0.312/otsuvalidator.egg-info/top_level.txt +1 -0
- otsuvalidator-1.1.0.312/pyproject.toml +37 -0
- otsuvalidator-1.1.0.312/setup.cfg +4 -0
- otsuvalidator-1.1.0.312/setup.py +24 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2021 Otsuhachi
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: otsuvalidator
|
|
3
|
+
Version: 1.1.0.312
|
|
4
|
+
Summary: A useful python package named otsuvalidator.
|
|
5
|
+
Home-page: https://github.com/Otsuhachi/OtsuValidator
|
|
6
|
+
Author: Otsuhachi
|
|
7
|
+
Author-email: Otsuhachi <agequodagis.tufuiegoeris@gmail.com>
|
|
8
|
+
License: MIT License
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Requires-Python: >=3.12
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
License-File: LICENSE.txt
|
|
15
|
+
Dynamic: author
|
|
16
|
+
Dynamic: home-page
|
|
17
|
+
Dynamic: license-file
|
|
18
|
+
Dynamic: requires-python
|
|
19
|
+
|
|
20
|
+
- [概要](#概要)
|
|
21
|
+
- [インストール](#インストール)
|
|
22
|
+
- [モジュール](#モジュール)
|
|
23
|
+
- [basesモジュールのクラス](#basesモジュールのクラス)
|
|
24
|
+
- [validatorsモジュールのクラス](#validatorsモジュールのクラス)
|
|
25
|
+
- [convertersモジュールのクラス](#convertersモジュールのクラス)
|
|
26
|
+
- [継承規則](#継承規則)
|
|
27
|
+
- [Validator継承規則](#validator継承規則)
|
|
28
|
+
- [VContainer継承規則](#vcontainer継承規則)
|
|
29
|
+
- [Converter継承規則](#converter継承規則)
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
# 概要
|
|
33
|
+
|
|
34
|
+
このライブラリは、値の正常性を検証するバリデータ群と、検証前に型変換を試みるコンバータ群を提供するユーティリティです。
|
|
35
|
+
ディスクリプタとしてクラス属性に使用することで不適切な値の代入を防止できるほか、単独の関数・オブジェクトとして値を検証・変換することも可能です。
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
## インストール
|
|
39
|
+
|
|
40
|
+
**インストール**
|
|
41
|
+
```bash
|
|
42
|
+
pip install otsuvalidator
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
**アップデート**
|
|
46
|
+
```bash
|
|
47
|
+
pip install -U otsuvalidator
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
**アンインストール**
|
|
51
|
+
```bash
|
|
52
|
+
pip uninstall otsuvalidator
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## モジュール
|
|
56
|
+
本ライブラリは主に以下の3つのモジュールで構成されています。
|
|
57
|
+
|
|
58
|
+
モジュール名|概要
|
|
59
|
+
:--:|:--:
|
|
60
|
+
`bases`|バリデータ・コンバータの基底クラスを定義。自作のバリデータを作成する際に使用します。
|
|
61
|
+
`validators`|値の検証のみを行うバリデータ群が定義されています。
|
|
62
|
+
`converters`|型変換を試みた上で検証を行うコンバータ群が定義されています。
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
### basesモジュールのクラス
|
|
66
|
+
- `Validator`: すべてのバリデータおよびコンバータの基底クラス。
|
|
67
|
+
- `VContainer`: コンテナ(リストや辞書など)用バリデータの基底クラス。要素が可変なオブジェクトを検証する際に使用します。
|
|
68
|
+
- `Converter`: コンバータの基底クラス。
|
|
69
|
+
- `VChain`: バリデータ・コンバータの連結をサポートします。
|
|
70
|
+
|
|
71
|
+
### validatorsモジュールのクラス
|
|
72
|
+
※破線されたクラスは非推奨です。
|
|
73
|
+
クラス|概要|期待する型
|
|
74
|
+
:--:|:--:|:--:
|
|
75
|
+
VBool|真偽値オブジェクトか検証|bool
|
|
76
|
+
VChoice|規定の選択肢の中から1つが選択されているか検証|Any
|
|
77
|
+
VNumerical / ~~VNumber~~|適切な数値か検証|int / float
|
|
78
|
+
VFloat|適切な浮動小数点数か検証|float
|
|
79
|
+
VInt|適切な整数か検証|int
|
|
80
|
+
VPath|適切なパスか検証|pathlib.Path
|
|
81
|
+
VString|適切な文字列か検証|str
|
|
82
|
+
~~VRegex~~|適切な正規表現か検証。`VString(pattern=...)`に合併。|str
|
|
83
|
+
VDict|適切な構造の辞書か検証|dict
|
|
84
|
+
VList|適切なリストか検証|list
|
|
85
|
+
VTuple|適切なタプルか検証|tuple
|
|
86
|
+
VTimedelta|適切なtimedelta型か検証|datetime.timedelta
|
|
87
|
+
|
|
88
|
+
### convertersモジュールのクラス
|
|
89
|
+
※破線されたクラスは非推奨です。
|
|
90
|
+
クラス|概要
|
|
91
|
+
:--:|:--:
|
|
92
|
+
CBool|Yes / Noとして解釈できる値に対し`bool`型への変換を試みて検証
|
|
93
|
+
CNumerical / ~~CNumber~~|`int` / `float`型への自動変換を試みて検証
|
|
94
|
+
CFloat|`float`型への自動変換を試みて検証
|
|
95
|
+
CInt|`int`型への自動変換を試みて検証
|
|
96
|
+
CPath|`pathlib.Path`型への自動変換を試みて検証
|
|
97
|
+
CString|`str`型への返還を試みて検証
|
|
98
|
+
CTimedelta|`datetime.timedelta`型への自動変換を試みて検証
|
|
99
|
+
|
|
100
|
+
## 継承規則
|
|
101
|
+
自作のバリデータやコンバータを正しく定義するための規則です。
|
|
102
|
+
|
|
103
|
+
### Validator継承規則
|
|
104
|
+
1. 命名: クラス名は`V{検証したいクラス名}`とします(例: `VString`)。
|
|
105
|
+
2. 継承: `Validator`クラスを継承します。
|
|
106
|
+
3. 定義: `validate`メソッドを定義し、検証が成功した場合は受け取った`value`を返します。
|
|
107
|
+
4. 変換: `validate`内で`value`の型変換を行ってはいけません(変換を行う場合は`Converter`を使用)。
|
|
108
|
+
|
|
109
|
+
### VContainer継承規則
|
|
110
|
+
1. 命名: クラス名は`V{検証したいクラス名}`とします(例: `VList`)。
|
|
111
|
+
2. 継承: `VContainer`クラスを継承します。
|
|
112
|
+
3. 定義: `validate`メソッドを定義し、検証通過時は`value`を返します。中身の各要素への検証規則を設定します。
|
|
113
|
+
4. 変換: コンテナ自体の型変換は行いません。ただし要素に対してはオプション設定に応じます。
|
|
114
|
+
|
|
115
|
+
### Converter継承規則
|
|
116
|
+
1. 命名: `C{変換検証したいクラス名}`とします(例: `CInt`)。
|
|
117
|
+
2. 継承: `(対象クラスのバリデータ, Converter)`の順で多重継承します。
|
|
118
|
+
3. 定義: `validate`内で型変換を行い、`return super().validate(value)`で検証して返します。
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
- [概要](#概要)
|
|
2
|
+
- [インストール](#インストール)
|
|
3
|
+
- [モジュール](#モジュール)
|
|
4
|
+
- [basesモジュールのクラス](#basesモジュールのクラス)
|
|
5
|
+
- [validatorsモジュールのクラス](#validatorsモジュールのクラス)
|
|
6
|
+
- [convertersモジュールのクラス](#convertersモジュールのクラス)
|
|
7
|
+
- [継承規則](#継承規則)
|
|
8
|
+
- [Validator継承規則](#validator継承規則)
|
|
9
|
+
- [VContainer継承規則](#vcontainer継承規則)
|
|
10
|
+
- [Converter継承規則](#converter継承規則)
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
# 概要
|
|
14
|
+
|
|
15
|
+
このライブラリは、値の正常性を検証するバリデータ群と、検証前に型変換を試みるコンバータ群を提供するユーティリティです。
|
|
16
|
+
ディスクリプタとしてクラス属性に使用することで不適切な値の代入を防止できるほか、単独の関数・オブジェクトとして値を検証・変換することも可能です。
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
## インストール
|
|
20
|
+
|
|
21
|
+
**インストール**
|
|
22
|
+
```bash
|
|
23
|
+
pip install otsuvalidator
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
**アップデート**
|
|
27
|
+
```bash
|
|
28
|
+
pip install -U otsuvalidator
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
**アンインストール**
|
|
32
|
+
```bash
|
|
33
|
+
pip uninstall otsuvalidator
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## モジュール
|
|
37
|
+
本ライブラリは主に以下の3つのモジュールで構成されています。
|
|
38
|
+
|
|
39
|
+
モジュール名|概要
|
|
40
|
+
:--:|:--:
|
|
41
|
+
`bases`|バリデータ・コンバータの基底クラスを定義。自作のバリデータを作成する際に使用します。
|
|
42
|
+
`validators`|値の検証のみを行うバリデータ群が定義されています。
|
|
43
|
+
`converters`|型変換を試みた上で検証を行うコンバータ群が定義されています。
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
### basesモジュールのクラス
|
|
47
|
+
- `Validator`: すべてのバリデータおよびコンバータの基底クラス。
|
|
48
|
+
- `VContainer`: コンテナ(リストや辞書など)用バリデータの基底クラス。要素が可変なオブジェクトを検証する際に使用します。
|
|
49
|
+
- `Converter`: コンバータの基底クラス。
|
|
50
|
+
- `VChain`: バリデータ・コンバータの連結をサポートします。
|
|
51
|
+
|
|
52
|
+
### validatorsモジュールのクラス
|
|
53
|
+
※破線されたクラスは非推奨です。
|
|
54
|
+
クラス|概要|期待する型
|
|
55
|
+
:--:|:--:|:--:
|
|
56
|
+
VBool|真偽値オブジェクトか検証|bool
|
|
57
|
+
VChoice|規定の選択肢の中から1つが選択されているか検証|Any
|
|
58
|
+
VNumerical / ~~VNumber~~|適切な数値か検証|int / float
|
|
59
|
+
VFloat|適切な浮動小数点数か検証|float
|
|
60
|
+
VInt|適切な整数か検証|int
|
|
61
|
+
VPath|適切なパスか検証|pathlib.Path
|
|
62
|
+
VString|適切な文字列か検証|str
|
|
63
|
+
~~VRegex~~|適切な正規表現か検証。`VString(pattern=...)`に合併。|str
|
|
64
|
+
VDict|適切な構造の辞書か検証|dict
|
|
65
|
+
VList|適切なリストか検証|list
|
|
66
|
+
VTuple|適切なタプルか検証|tuple
|
|
67
|
+
VTimedelta|適切なtimedelta型か検証|datetime.timedelta
|
|
68
|
+
|
|
69
|
+
### convertersモジュールのクラス
|
|
70
|
+
※破線されたクラスは非推奨です。
|
|
71
|
+
クラス|概要
|
|
72
|
+
:--:|:--:
|
|
73
|
+
CBool|Yes / Noとして解釈できる値に対し`bool`型への変換を試みて検証
|
|
74
|
+
CNumerical / ~~CNumber~~|`int` / `float`型への自動変換を試みて検証
|
|
75
|
+
CFloat|`float`型への自動変換を試みて検証
|
|
76
|
+
CInt|`int`型への自動変換を試みて検証
|
|
77
|
+
CPath|`pathlib.Path`型への自動変換を試みて検証
|
|
78
|
+
CString|`str`型への返還を試みて検証
|
|
79
|
+
CTimedelta|`datetime.timedelta`型への自動変換を試みて検証
|
|
80
|
+
|
|
81
|
+
## 継承規則
|
|
82
|
+
自作のバリデータやコンバータを正しく定義するための規則です。
|
|
83
|
+
|
|
84
|
+
### Validator継承規則
|
|
85
|
+
1. 命名: クラス名は`V{検証したいクラス名}`とします(例: `VString`)。
|
|
86
|
+
2. 継承: `Validator`クラスを継承します。
|
|
87
|
+
3. 定義: `validate`メソッドを定義し、検証が成功した場合は受け取った`value`を返します。
|
|
88
|
+
4. 変換: `validate`内で`value`の型変換を行ってはいけません(変換を行う場合は`Converter`を使用)。
|
|
89
|
+
|
|
90
|
+
### VContainer継承規則
|
|
91
|
+
1. 命名: クラス名は`V{検証したいクラス名}`とします(例: `VList`)。
|
|
92
|
+
2. 継承: `VContainer`クラスを継承します。
|
|
93
|
+
3. 定義: `validate`メソッドを定義し、検証通過時は`value`を返します。中身の各要素への検証規則を設定します。
|
|
94
|
+
4. 変換: コンテナ自体の型変換は行いません。ただし要素に対してはオプション設定に応じます。
|
|
95
|
+
|
|
96
|
+
### Converter継承規則
|
|
97
|
+
1. 命名: `C{変換検証したいクラス名}`とします(例: `CInt`)。
|
|
98
|
+
2. 継承: `(対象クラスのバリデータ, Converter)`の順で多重継承します。
|
|
99
|
+
3. 定義: `validate`内で型変換を行い、`return super().validate(value)`で検証して返します。
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
__version__ = "1.1.0.312"
|
|
2
|
+
from .bases import VChain
|
|
3
|
+
from .converters import (
|
|
4
|
+
CBool,
|
|
5
|
+
CFloat,
|
|
6
|
+
CInt,
|
|
7
|
+
CNumber,
|
|
8
|
+
CNumerical,
|
|
9
|
+
CPath,
|
|
10
|
+
CString,
|
|
11
|
+
CTimedelta,
|
|
12
|
+
)
|
|
13
|
+
from .validators import (
|
|
14
|
+
VAny,
|
|
15
|
+
VBool,
|
|
16
|
+
VChoice,
|
|
17
|
+
VDict,
|
|
18
|
+
VFloat,
|
|
19
|
+
VInt,
|
|
20
|
+
VList,
|
|
21
|
+
VNone,
|
|
22
|
+
VNumber,
|
|
23
|
+
VPath,
|
|
24
|
+
VRange,
|
|
25
|
+
VRegex,
|
|
26
|
+
VSequence,
|
|
27
|
+
VString,
|
|
28
|
+
VTimedelta,
|
|
29
|
+
VTuple,
|
|
30
|
+
VType,
|
|
31
|
+
)
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
__all__ = (
|
|
2
|
+
"Converter",
|
|
3
|
+
"Validator",
|
|
4
|
+
"VChain",
|
|
5
|
+
"VContainer",
|
|
6
|
+
)
|
|
7
|
+
from abc import ABC, abstractmethod
|
|
8
|
+
from typing import Any, Literal, Self, overload
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Validator[OutT](ABC):
|
|
12
|
+
def __init__(self) -> None:
|
|
13
|
+
self.public_name: str = ""
|
|
14
|
+
self.private_name: str = ""
|
|
15
|
+
|
|
16
|
+
def __set_name__(self, owner: type[Any], name: str) -> None:
|
|
17
|
+
"""クラス定義時に自動的に呼び出される。
|
|
18
|
+
|
|
19
|
+
例: `age = Validator()`と書くと`name`に"age"が入る。
|
|
20
|
+
"""
|
|
21
|
+
self.public_name = name
|
|
22
|
+
self.private_name = f"_{name}"
|
|
23
|
+
|
|
24
|
+
@overload
|
|
25
|
+
def __get__(self, instance: None, owner: type[Any]) -> Self: ...
|
|
26
|
+
|
|
27
|
+
@overload
|
|
28
|
+
def __get__(self, instance: Any, owner: type[Any]) -> OutT: ...
|
|
29
|
+
def __get__(self, instance: Any | None, owner: type[Any]) -> OutT | Self:
|
|
30
|
+
# クラス属性としてアクセスされた場合
|
|
31
|
+
# 例: MyClass.age
|
|
32
|
+
if instance is None:
|
|
33
|
+
return self
|
|
34
|
+
# インスタンス属性としてアクセスされた場合
|
|
35
|
+
# myc_instance.age
|
|
36
|
+
return getattr(instance, self.private_name)
|
|
37
|
+
|
|
38
|
+
def set_name(self, name: str) -> None:
|
|
39
|
+
"""バリデータの表示名を動的に設定する。"""
|
|
40
|
+
self.public_name = name
|
|
41
|
+
self.private_name = f"_{name}"
|
|
42
|
+
|
|
43
|
+
@abstractmethod
|
|
44
|
+
def validate(self, value: Any) -> OutT:
|
|
45
|
+
"""
|
|
46
|
+
[抽象メソッド] 子クラスで必ず実装する必要がある。
|
|
47
|
+
値が正しければその値を返し、不正なら例外を投げる。
|
|
48
|
+
"""
|
|
49
|
+
raise NotImplementedError()
|
|
50
|
+
|
|
51
|
+
def __set__(self, instance: Any, value: Any) -> None:
|
|
52
|
+
"""値を代入する際に呼び出される。
|
|
53
|
+
|
|
54
|
+
`validate`メソッドでチェックを通った値のみ保持する。
|
|
55
|
+
"""
|
|
56
|
+
# 1. 値を検証(子クラスの実装を呼び出す。)
|
|
57
|
+
validated_value = self.validate(value)
|
|
58
|
+
# 2. private_nameでインスタンスに保存
|
|
59
|
+
setattr(instance, self.private_name, validated_value)
|
|
60
|
+
|
|
61
|
+
def __or__[NextOutT](self, other: "Validator[NextOutT]") -> "VChain[NextOutT]":
|
|
62
|
+
"""パイプ演算子によるバリデータの直列連結をサポートする。"""
|
|
63
|
+
return VChain(self, other)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class Converter[InT, OutT](Validator[OutT]):
|
|
67
|
+
"""値を特定の型や形式に変換するディスクリプタの基底クラス。"""
|
|
68
|
+
|
|
69
|
+
@abstractmethod
|
|
70
|
+
def validate(self, value: InT) -> OutT:
|
|
71
|
+
"""入力値を変換して返すロジックを実装する。"""
|
|
72
|
+
raise NotImplementedError()
|
|
73
|
+
|
|
74
|
+
def super_validate(self, value: Any) -> OutT:
|
|
75
|
+
"""変換を行わず、スーパークラスの検証のみを行う。"""
|
|
76
|
+
return super().validate(value) # type: ignore
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class VChain[OutT](Validator[OutT]):
|
|
80
|
+
"""複数のバリデータを直列に実行するクラス。"""
|
|
81
|
+
|
|
82
|
+
def __init__(self, *validators: Validator[Any], pass_type: Literal["all", "any"] = "any") -> None:
|
|
83
|
+
super().__init__()
|
|
84
|
+
self.validators: tuple[Validator[Any], ...] = validators
|
|
85
|
+
self.pass_type: Literal["all", "any"] = pass_type
|
|
86
|
+
|
|
87
|
+
def validate(self, value: Any) -> OutT:
|
|
88
|
+
current_value = value
|
|
89
|
+
validators = self.validators
|
|
90
|
+
if self.pass_type == "all":
|
|
91
|
+
for validator in validators:
|
|
92
|
+
current_value = validator.validate(current_value)
|
|
93
|
+
else:
|
|
94
|
+
passed = False
|
|
95
|
+
exceptions = []
|
|
96
|
+
for validator in validators:
|
|
97
|
+
try:
|
|
98
|
+
tmp = validator.validate(current_value)
|
|
99
|
+
current_value = tmp
|
|
100
|
+
passed = True
|
|
101
|
+
except Exception as ex:
|
|
102
|
+
exceptions.append(ex)
|
|
103
|
+
if not passed:
|
|
104
|
+
raise ValueError(*exceptions)
|
|
105
|
+
return current_value
|
|
106
|
+
|
|
107
|
+
def __or__[NextOutT](self, other: Validator[NextOutT]) -> "VChain[NextOutT]":
|
|
108
|
+
return VChain(*self.validators, other)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
class VContainer[OutT](Validator[OutT]):
|
|
112
|
+
"""
|
|
113
|
+
VDict, VList, VTuple などの構造を持つバリデータの基底クラス。
|
|
114
|
+
スキーマの保持や、動的な再検証フラグの管理を行う。
|
|
115
|
+
"""
|
|
116
|
+
|
|
117
|
+
def __init__(
|
|
118
|
+
self,
|
|
119
|
+
schema: Any,
|
|
120
|
+
*,
|
|
121
|
+
monitoring_overwrite: bool = False,
|
|
122
|
+
) -> None:
|
|
123
|
+
super().__init__()
|
|
124
|
+
self.schema = schema
|
|
125
|
+
self.monitoring_overwrite = monitoring_overwrite
|
|
126
|
+
|
|
127
|
+
def __get__(self, instance: Any | None, owner: type[Any]) -> OutT | Self: # type: ignore
|
|
128
|
+
"""
|
|
129
|
+
monitoring_overwrite が True の場合、
|
|
130
|
+
値を取得する際にも再検証を実行するようにオーバーライド。
|
|
131
|
+
"""
|
|
132
|
+
if instance is None:
|
|
133
|
+
return self
|
|
134
|
+
value = getattr(instance, self.private_name)
|
|
135
|
+
if self.monitoring_overwrite:
|
|
136
|
+
return self.validate(value)
|
|
137
|
+
return value
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
__all__ = (
|
|
2
|
+
"CBool",
|
|
3
|
+
"CFloat",
|
|
4
|
+
"CInt",
|
|
5
|
+
"CNumber",
|
|
6
|
+
"CNumerical",
|
|
7
|
+
"CPath",
|
|
8
|
+
"CString",
|
|
9
|
+
"CTimedelta",
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
import re
|
|
14
|
+
import warnings
|
|
15
|
+
from collections.abc import Callable
|
|
16
|
+
from datetime import timedelta
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from typing import Any, Literal, override
|
|
19
|
+
|
|
20
|
+
from .bases import Converter
|
|
21
|
+
from .types import FLOAT_INT, StrPath
|
|
22
|
+
from .validators import VBool, VFloat, VInt, VNumber, VPath, VString, VTimedelta
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class CBool(VBool, Converter[Any, bool]):
|
|
26
|
+
"""VBoolの拡張バリデータ。
|
|
27
|
+
|
|
28
|
+
一般にYes/Noとして解釈できる値に対し、bool変換を試みます。
|
|
29
|
+
bool(value)とは異なる結果を返すことがあります。
|
|
30
|
+
また、Trueにしたい値、Falseにしたい値をタプル形式で登録することでその通りに判定するようになります。
|
|
31
|
+
|
|
32
|
+
str型, float型, int型に対応しています。
|
|
33
|
+
また、str型はすべてstr.lowerされた後に判定されます。
|
|
34
|
+
|
|
35
|
+
Trueに変換される値
|
|
36
|
+
str型: 'true', 'yes', 'y', '1'
|
|
37
|
+
int型: 1
|
|
38
|
+
float型: 1.0
|
|
39
|
+
|
|
40
|
+
Falseに変換される値
|
|
41
|
+
str型: 'false', 'no', 'n', '0'
|
|
42
|
+
int型: 0
|
|
43
|
+
float型: 0.0
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
def __init__(
|
|
47
|
+
self,
|
|
48
|
+
true_data: tuple[Any, ...] | None = None,
|
|
49
|
+
false_data: tuple[Any, ...] | None = None,
|
|
50
|
+
checker: tuple[Callable[[Any], bool], ...] | None = None,
|
|
51
|
+
) -> None:
|
|
52
|
+
"""ユーザ定義のTrueにしたい値、Falseにしたい値を設定してバリデータを生成します。
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
true_data (Optional[Tuple[Any, ...]], optional): Trueにしたい値です。
|
|
56
|
+
false_data (Optional[Tuple[Any, ...]], optional): Falseにしたい値です。
|
|
57
|
+
checker (Optional[Tuple[Callable[[Any], bool], ...]], optional): 真偽判定用の関数です。
|
|
58
|
+
"""
|
|
59
|
+
self.true_data = true_data
|
|
60
|
+
self.false_data = false_data
|
|
61
|
+
self.checker = checker
|
|
62
|
+
|
|
63
|
+
def validate(self, value: Any) -> bool:
|
|
64
|
+
# ユーザ定義で真偽確認
|
|
65
|
+
tmp_value = value
|
|
66
|
+
if (checker := self.true_data) is not None and value in checker:
|
|
67
|
+
return super().validate(True)
|
|
68
|
+
if (checker := self.false_data) is not None and value in checker:
|
|
69
|
+
return super().validate(False)
|
|
70
|
+
if (checker := self.checker) is not None:
|
|
71
|
+
for c in checker:
|
|
72
|
+
try:
|
|
73
|
+
tmp = c(tmp_value)
|
|
74
|
+
if tmp is None:
|
|
75
|
+
continue
|
|
76
|
+
tmp_value = tmp # type: ignore
|
|
77
|
+
except Exception:
|
|
78
|
+
continue
|
|
79
|
+
# クラス定義で真偽を確認
|
|
80
|
+
if (tv := type(tmp_value)) is bool:
|
|
81
|
+
pass
|
|
82
|
+
elif tv is str:
|
|
83
|
+
tmp_value: str
|
|
84
|
+
tmp_value = tmp_value.lower()
|
|
85
|
+
if tmp_value in (yes := ("true", "yes", "y", "1")):
|
|
86
|
+
tmp_value = True # type: ignore
|
|
87
|
+
elif tmp_value in (no := ("false", "no", "n", "0")):
|
|
88
|
+
tmp_value = False # type: ignore
|
|
89
|
+
else:
|
|
90
|
+
msg = f"{yes}または{no}のいずれかである必要があります。\n{tmp_value=}"
|
|
91
|
+
raise ValueError(msg)
|
|
92
|
+
elif tv is int:
|
|
93
|
+
if tmp_value == 1:
|
|
94
|
+
tmp_value = True # type: ignore
|
|
95
|
+
elif tmp_value == 0:
|
|
96
|
+
tmp_value = False # type: ignore
|
|
97
|
+
else:
|
|
98
|
+
msg = f"1または0である必要があります。\n{tmp_value=}"
|
|
99
|
+
raise ValueError(msg)
|
|
100
|
+
elif tv is float:
|
|
101
|
+
if tmp_value == 1.0:
|
|
102
|
+
tmp_value = True # type: ignore
|
|
103
|
+
elif tmp_value == 0.0:
|
|
104
|
+
tmp_value = False # type: ignore
|
|
105
|
+
else:
|
|
106
|
+
msg = f"1.0または0.0である必要があります。\n{tmp_value=}"
|
|
107
|
+
raise ValueError(msg)
|
|
108
|
+
else:
|
|
109
|
+
msg = f"Yes/Noとして解釈できる必要があります。\n{tmp_value=}"
|
|
110
|
+
raise TypeError(msg)
|
|
111
|
+
return super().validate(tmp_value)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class CNumerical[OutT: FLOAT_INT](VNumber[OutT], Converter[Any, OutT]):
|
|
115
|
+
"""
|
|
116
|
+
数値を変換し、範囲を検証するコンバータの基底クラス。
|
|
117
|
+
"""
|
|
118
|
+
|
|
119
|
+
def __init__(
|
|
120
|
+
self,
|
|
121
|
+
target_type: type[OutT] | None = None,
|
|
122
|
+
min_val: OutT | None = None,
|
|
123
|
+
max_val: OutT | None = None,
|
|
124
|
+
*,
|
|
125
|
+
type_strict: bool = False,
|
|
126
|
+
) -> None:
|
|
127
|
+
super().__init__(min_val, max_val, type_strict=type_strict)
|
|
128
|
+
self.target_type = target_type
|
|
129
|
+
|
|
130
|
+
@override
|
|
131
|
+
def validate(self, value: Any) -> OutT:
|
|
132
|
+
"""入力値を target_type に変換してから、VNumber の検証を実行する。"""
|
|
133
|
+
# 1. 変換試行
|
|
134
|
+
t_type = self.target_type
|
|
135
|
+
try:
|
|
136
|
+
if isinstance(value, bool):
|
|
137
|
+
raise ValueError
|
|
138
|
+
if t_type is None:
|
|
139
|
+
# 動的判定モード
|
|
140
|
+
f_val = float(value)
|
|
141
|
+
if f_val.is_integer():
|
|
142
|
+
converted = int(f_val)
|
|
143
|
+
else:
|
|
144
|
+
converted = f_val
|
|
145
|
+
else:
|
|
146
|
+
converted = t_type(value)
|
|
147
|
+
except (TypeError, ValueError):
|
|
148
|
+
type_name = t_type.__name__ if t_type else "number"
|
|
149
|
+
msg = f"'{self.public_name}'を{type_name}に変換できませんでした: {value!r}。"
|
|
150
|
+
raise ValueError(msg) from None
|
|
151
|
+
return super().validate(converted)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
class CNumber[OutT: FLOAT_INT](CNumerical[OutT]):
|
|
155
|
+
"""
|
|
156
|
+
[非推奨]CNumberは非推奨です。将来のバージョンで削除されます。
|
|
157
|
+
代わりにCNumericalを使用してください。
|
|
158
|
+
"""
|
|
159
|
+
|
|
160
|
+
def __init__(
|
|
161
|
+
self,
|
|
162
|
+
target_type: type[OutT],
|
|
163
|
+
min_val: OutT | None = None,
|
|
164
|
+
max_val: OutT | None = None,
|
|
165
|
+
*,
|
|
166
|
+
type_strict: bool = False,
|
|
167
|
+
) -> None:
|
|
168
|
+
msg = "CNumberは非推奨です。代わりにCNumericalを使用してください。"
|
|
169
|
+
warnings.warn(msg, DeprecationWarning, stacklevel=2)
|
|
170
|
+
super().__init__(target_type, min_val, max_val, type_strict=type_strict)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
class CInt(CNumerical[int]):
|
|
174
|
+
"""
|
|
175
|
+
値を整数に変換し、範囲を検証するコンバータ。
|
|
176
|
+
"""
|
|
177
|
+
|
|
178
|
+
def __init__(
|
|
179
|
+
self,
|
|
180
|
+
min_val: int | None = None,
|
|
181
|
+
max_val: int | None = None,
|
|
182
|
+
) -> None:
|
|
183
|
+
super().__init__(target_type=int, min_val=min_val, max_val=max_val)
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
class CFloat(CNumerical[float]):
|
|
187
|
+
"""
|
|
188
|
+
値を浮動小数点数に変換し、範囲を検証するコンバータ。
|
|
189
|
+
"""
|
|
190
|
+
|
|
191
|
+
def __init__(
|
|
192
|
+
self,
|
|
193
|
+
min_val: float | None = None,
|
|
194
|
+
max_val: float | None = None,
|
|
195
|
+
) -> None:
|
|
196
|
+
super().__init__(target_type=float, min_val=min_val, max_val=max_val)
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
class CPath(VPath, Converter[StrPath, Path]):
|
|
200
|
+
"""文字列をPathオブジェクトに変換し、属性を検証するコンバータ。"""
|
|
201
|
+
|
|
202
|
+
def __init__(
|
|
203
|
+
self,
|
|
204
|
+
*extensions: str,
|
|
205
|
+
must_exist: bool = False,
|
|
206
|
+
path_type: Callable[[Path], bool] | None | Literal["dir", "file"] = None,
|
|
207
|
+
) -> None:
|
|
208
|
+
super().__init__(*extensions, must_exist=must_exist, path_type=path_type)
|
|
209
|
+
|
|
210
|
+
@override
|
|
211
|
+
def validate(self, value: Any) -> Path:
|
|
212
|
+
"""Pathオブジェクトに変換してから、VPathの検証を実行する。"""
|
|
213
|
+
try:
|
|
214
|
+
converted = Path(value)
|
|
215
|
+
except (TypeError, ValueError):
|
|
216
|
+
msg = f"'{self.public_name}'をpathlib.Pathオブジェクトに変換できませんでした: {value!r}。"
|
|
217
|
+
raise TypeError(msg) from None
|
|
218
|
+
return super().validate(converted)
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
class CString(VString, Converter[Any, str]):
|
|
222
|
+
"""VStringの拡張バリデータ。
|
|
223
|
+
|
|
224
|
+
str型に変換可能なオブジェクトは例外を投げずに変換を行います。
|
|
225
|
+
"""
|
|
226
|
+
|
|
227
|
+
def validate(self, value: Any) -> str:
|
|
228
|
+
tmp_value = value
|
|
229
|
+
if type(tmp_value) is not str:
|
|
230
|
+
try:
|
|
231
|
+
tmp_value = str(tmp_value)
|
|
232
|
+
except Exception:
|
|
233
|
+
msg = f"'{self.public_name}'はstr型に変換可能な値である必要があります: {tmp_value!r}。"
|
|
234
|
+
raise TypeError(msg) from None
|
|
235
|
+
return super().validate(tmp_value)
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
class CTimedelta(VTimedelta, Converter[Any, timedelta]):
|
|
239
|
+
"""VTimedeltaの拡張バリデータです。
|
|
240
|
+
|
|
241
|
+
timedelta型に変換可能なオブジェクトは例外を投げずに変換を行います。
|
|
242
|
+
"""
|
|
243
|
+
|
|
244
|
+
cmp_timedelta = re.compile(r"(?:(\d+) days?,? )?(\d+):(\d+):(\d+)(?:\.(\d+))?")
|
|
245
|
+
|
|
246
|
+
def validate(self, value: Any) -> timedelta:
|
|
247
|
+
f = None
|
|
248
|
+
if (tv := type(value)) is dict:
|
|
249
|
+
f = self.__try_convert_dict
|
|
250
|
+
elif tv in (list, tuple):
|
|
251
|
+
f = self.__try_convert_container
|
|
252
|
+
elif tv is str:
|
|
253
|
+
f = self.__try_convert_str
|
|
254
|
+
tmp_value = value
|
|
255
|
+
try:
|
|
256
|
+
if f is not None:
|
|
257
|
+
tmp_value = f(tmp_value)
|
|
258
|
+
except Exception:
|
|
259
|
+
msg = f"'{self.public_name}'はtimedelta型に変換可能な値である必要があります: {tmp_value!r}。"
|
|
260
|
+
raise TypeError(msg) from None
|
|
261
|
+
return super().validate(tmp_value)
|
|
262
|
+
|
|
263
|
+
def __try_convert_container(self, value: list | tuple) -> timedelta:
|
|
264
|
+
return timedelta(*value)
|
|
265
|
+
|
|
266
|
+
def __try_convert_dict(self, value: dict) -> timedelta:
|
|
267
|
+
return timedelta(**value)
|
|
268
|
+
|
|
269
|
+
def __try_convert_str(self, value: str) -> timedelta:
|
|
270
|
+
if (match := self.cmp_timedelta.fullmatch(value)) is not None:
|
|
271
|
+
keys = ("days", "hours", "minutes", "seconds", "microseconds")
|
|
272
|
+
d = {}
|
|
273
|
+
for k, v in zip(keys, match.groups(), strict=True):
|
|
274
|
+
if v is None:
|
|
275
|
+
continue
|
|
276
|
+
if k == "microseconds":
|
|
277
|
+
v = v.ljust(6, "0")[:6]
|
|
278
|
+
v = int(v)
|
|
279
|
+
d[k] = v
|
|
280
|
+
return self.__try_convert_dict(d)
|
|
281
|
+
msg = f"'{value}'をtimedelta型に変換できませんでした。"
|
|
282
|
+
raise ValueError(msg)
|
|
File without changes
|