Package: otsuvalidator
概要インストールモジュール継承規則実行例-バリデータコンバータの変換実行例-コンバータ概要このライブラリは値の正常性を検証するバリデータ群と、検証前に型変換を試みるコンバータ群です。ディスクリプタとして使用することで属性に不正な値が代入されるのを防止します。また、単独で使用することも可能です。このライブラリは以下の環境で作成されています。Windows10(64bit),Python3.8.10インストールインストールpip install otsuvalidatorアップデートpip install -U otsuvalidatorアンインストールpip uninstall otsuvalidatorモジュールモジュールは以下の3つが存在します。モジュール名概要basesバリデータ、コンバータの基底クラスが定義されいる自作のバリデータを定義するときに使用できるvalidatorsバリデータが定義されているconvertersコンバータが定義されているomit in tocbasesモジュールのクラスValidatorと表記されている部分に関しては、バリデータ、コンバータ両方を指します。クラス概要Validatorすべてのバリデータ、コンバータの基底クラスVContainerコンテナ用のバリデータの基底クラス中身が可変なクラスのバリデータを定義するときに使用するConverterコンバータの基底クラスセキュアさが重視される場面では使用しないCNoneableValidator既定のバリデーションに加え、Noneを許可する変換の可否は以下の2点に依存する-渡したValidatorがコンバータか否か-所属するVContainerのallow_convertオプションCNumerical数値型用コンバータの基底クラスvalueに対し、int変換、float変換を試みるメソッドが定義されているcomplexは想定されていないvalidatorsモジュールのクラススーパークラスの表記がないものはValidatorを継承しています。クラススーパークラス概要期待する型VBool真偽値オブジェクトかboolVChoice選択肢の中から1つが選択されているかAnyVNumber適切な数値かint,flaotVFloatVNumber適切な浮動小数点数かflaotVIntVNumber適切な整数かintVPath適切なパスかpathlib.PathVString適切な文字列かstrVRegexVString正規表現にマッチする適切な文字列かstrVDictVContainer適切な辞書かdictVListVContainer適切なリストかlistVTupleVContaner適切なタプルかtupleVTimedelta適切な経過時間型かdatetime.timedeltaconvertersモジュールのクラススーパークラスにコンバータが記載されていないクラスはConverterを継承しています。クラススーパークラス概要CBoolVBool,Converter一般にYes/Noとして解釈できる値に対し、bool変換を試み、検証を行うbool(value)ではTrueになるものがFalseになったり例外が発生したりするCNumberVNumber, CNumericalint,float型への変換を試み、検証を行うCFloatVFloat, CNumericalfloat型への変換を試み、検証を行うCIntVInt, CNumericalint型への変換を試み、検証を行うCPathVPath, ConverterPath型への変換を試み、検証を行うCStringVString, Converterstr型への変換を試み、検証を行うCTimedeltaVTimedelta, Converterdatetime.timedelta型への変換を試み、検証を行う継承規則きちんと動作するバリデータ、コンバータを定義するための規則です。CNoneableは継承しないでください。Validator継承規則VContainer継承規則Converter継承規則Validator継承規則規則概要理由命名クラス名はV{検証したいクラス名}とする管理のしやすさ継承Validatorを継承する定義validateメソッドを定義し、検証が通った場合にはvalueを返す拡張してコンバータを定義するときに必要変換valueの型を変換しない変換と検証を行う場合はコンバータを使用するVContainer継承規則規則概要理由命名クラス名はV<検証したいクラス名>とする管理のしやすさ本質的にはValidatorと変わらないので規則もそのまま適用継承VContainerを継承する定義validateメソッドを定義し、検証が通った場合にはvalueを返す変換を許可する場合、TEMPLATEがValidator以外の場合など細かな違いを設定する必要があるコンテナそのものの検証と中身の検証が必要変換valueの型を変換しないvalueの各要素vに対してはオプション次第TEMPLATEにコンバータを渡している場合、禁止されていない限り変換を行うのが自然なためConverter継承規則CNumericalについてもここに従ってください。規則概要理由命名クラス名はC<変換検証したいクラス名>とする一目で変換を行うクラスと認識するため継承(検証したいクラスのバリデータ,コンバータ)を継承する検証したいクラスのバリデータ.validateメソッドをvalidateメソッド内で呼び出すため定義validateメソッドを定義し、変換検証が通った場合には変換されたvalueを返すsuper_validateメソッドを定義し、検証したいクラス.validateメソッドを行えるようにするVContainerなど、変換を許可したくない状況ではsuper_validateを使用するため変換validateメソッド内で変換を試みるsuper_validateメソッドでは変換しない定義で書いた通り無変換が必要になる場面もあるため実行例-バリデータバリデータの実行例バリデータをディスクリプタとして使用しているStudentクラスを試しに使用します。バリデータの実行例目次前提コードnameの操作ageの操作genderの操作gradesの操作hobbyの操作addressの操作成功バリデータ実行例-前提コード目次に戻る説明は以下の条件を満たした環境で実行されることを想定しています。Python3.8以上がインストールされたWindows本ライブラリがインストールされている以下のtest.pyファイルを生成し、py -i test.py、または対話モードで以下のコードが入力されている# test.pyfromotsuvalidatorimport(CNoneable,VChoice,VDict,VInt,VList,VRegex,VString)classStudent:name=VString(1,50,checker=str.istitle)# 1文字以上50文字以下, str.istitleがTrueになる文字列age=VInt(0,150)# 0以上150以下の整数gender=VChoice('male','female','others')# (male, female, others)のいずれかgrades=VDict(# 以下の構造を持つ辞書, キー欠落不可, アクセス時に再検証を行わない{'Japanese':VInt(0,100),# 0以上100以下の整数'Social Studies':VInt(0,100),'Math':VDict(# 以下のキーを持つ辞書, キー欠落可, アクセス時に再検証を行う{'Math1':VInt(0,100),'Math2':VInt(0,100)},allow_missing_key=True,monitoring_overwrite=True,)},allow_missing_key=False,monitoring_overwrite=False,)# 1文字以上の文字列だけのリスト Noneで無回答可 要素数は無制限hobby=CNoneable(VList(VString(1)))# [郵便番号, 都道府県, 市町村群]のリスト Noneで無回答可address=CNoneable(VList([VRegex('^\\d{3}-?\\d{4}$'),VRegex('(?!.*\\d.*)'),VRegex('(?!.*\\d.*)')]))defshow_profile(self):name=self.nameage=self.agegender=self.gendergrades=self.gradesjapanese=grades['Japanese']social=grades['Social Studies']math=grades['Math']hobby=self.hobbyaddress=self.addressprofiles=('名前','年齢','性別','国語','社会','数学','趣味','住所')profile_values=(name,age,gender,japanese,social,math,hobby,address)fortitle,valueinzip(profiles,profile_values):print(f'{title}:{value}')otsuhachi=Student()バリデータ実行例-nameの操作目次に戻るotsuhachi.nameを操作します。Studentのname属性はVString(1, checker=str.istitle)によって検証されます。# 失敗 (型が異なる)>>>otsuhachi.name=28Traceback(mostrecentcalllast):...TypeError:属性'name'はstr型である必要があります。(28:int)# 失敗 (最低文字数を満たしていない)>>>otsuhachi.name=''Traceback(mostrecentcalllast):...ValueError:属性'name'は1文字以上である必要があります。('':str)# 失敗 (最大文字数を超過している)>>>otsuhachi.name='A'+('a'*100)Traceback(mostrecentcalllast):...ValueError:属性'name'は50文字以下である必要があります。('Aaaaaaaaa...aaaaaaaaa':str)# 失敗 (checkerがTrueを返さない)>>>otsuhachi.name='otsuhachi'Traceback(mostrecentcalllast):...ValueError:属性'name'は指定した形式に対応している必要があります。<method'istitle'of'str'objects>。('otsuhachi':str)# 成功>>>otsuhachi.name='Otsuhachi'>>>otsuhachi.name'Otsuhachi'バリデータ実行例-ageの操作目次に戻るotsuhachi.ageを操作します。Studentのage属性はVInt(0)によって検証されます。#失敗 (型が異なる)>>>otsuhachi.age=28.8Traceback(mostrecentcalllast):...TypeError:属性'age'はint型である必要があります。(28.8:float)# 失敗 (最小値未満)>>>otsuhachi.age=-1...ValueError:属性'age'は0より小さい値を設定することはできません。(-1:int)# 失敗 (最大値超過)>>>otsuhachi.age=280Traceback(mostrecentcalllast):...ValueError:属性'age'は150より大きい値を設定することはできません。(280:int)# 成功>>>otsuhachi.age=28>>>otsuhachi.age28バリデータ実行例-genderの操作目次に戻るotsuhachi.genderを操作します。Studentのgender属性はVChoice('male', 'female', 'others')によって検証されます。# 失敗 (選択肢にない値)>>>otsuhachi.gender=NoneTraceback(mostrecentcalllast):...ValueError:属性'gender'は{'male','others','female'}のいずれかである必要があります。(None:NoneType)# 失敗 (選択肢にない値)>>>otsuhachi.gender='mal'Traceback(mostrecentcalllast):...ValueError:属性'gender'は{'male','others','female'}のいずれかである必要があります。('mal':str)# 成功>>>otsuhachi.gender='others'>>>otsuhachi.gender'others'>>>otsuhachi.gender='female'>>>otsuhachi.gender'female'>>>otsuhachi.gender='male'>>>otsuhachi.gender'male'バリデータ実行例-gradesの操作目次に戻るgradesの概要gradesの基本的な失敗と成功の例gradesで起こりえる不正gradesで起こりえる不正の防止otsuhachi.garadesを操作します。Studentのgradesは以下のように定義されたバリデータによって検証されます。VDict({'Japanese':VInt(0,100),'Social Studies':VInt(0,100),'Math':VDict({'Math1':VInt(0,100),'Math2':VInt(0,100)},allow_missing_key=True,monitoring_overwrite=True,)},allow_missing_key=False,monitoring_overwrite=False,)gradesの概要分解して考えてみます。gradesが持つべきキーは(Japanese,Social Studies,Math)の3つJapaneseとSocial Studiesは0～100の整数値Mathは(Math1,Math2)のキーを持つ辞書Math1とMath2は0～100の整数値allow_missing_keyがTrueなのでキーを持たない辞書でも可monitoring_overwriteはTrueでも実質無関係allow_missing_keyがFalseなので、キーすべてが含まれている必要があるmonitoring_overwriteがFalseなのでotsuhachi.gradesをしても再検証が行われない以上のような設定のバリデータになっています。gradesの基本的な失敗と成功の例# 失敗 (型が異なる)>>>otsuhachi.grades=['Japanese','Social Studies','Math']Traceback(mostrecentcalllast):...TypeError:属性'grades'はdict型である必要があります。(['Japanese','Social Studies','Math']:list)# 失敗 (必須キーの欠落)>>>otsuhachi.grades={'Japanese':68}Traceback(mostrecentcalllast):...ValueError:属性'grades'は以下のキーを設定する必要があります。({'Math','Social Studies'})。({'Japanese':68}:dict)# 失敗 (不正な値)>>>otsuhachi.grades={'Japanese':68,'Social Studies':28,'Math':66}Traceback(mostrecentcalllast):...TypeError:dict型である必要があります。(66:int)Duringhandlingoftheaboveexception,anotherexceptionoccurred:Traceback(mostrecentcalllast):...TypeError:キー'Math'は不正な値です。(66:int)# 失敗 (不正な値: 入れ子構造)>>>otsuhachi.grades={'Japanese':68,'Social Studies':28,'Math':{'Math1':2.8}}Traceback(mostrecentcalllast):...TypeError:int型である必要があります。(2.8:float)Duringhandlingoftheaboveexception,anotherexceptionoccurred:Traceback(mostrecentcalllast):...TypeError:キー'Math1'は不正な値です。(2.8:float)Duringhandlingoftheaboveexception,anotherexceptionoccurred:Traceback(mostrecentcalllast):...TypeError:キー'Math'は不正な値です。({'Math1':2.8}:dict)# 失敗 (未定義のキー)>>>otsuhachi.grades={'Japanese':68,'Social Studies':28,'Math':{'Math1':66},'Science':70}Traceback(mostrecentcalllast):...ValueError:属性'grades'は以下のキーを設定することはできません。({'Science'})。({'Japanese...ence':70}:dict)# 成功>>>otsuhachi.grades={'Japanese':68,'Social Studies':28,'Math':{'Math1':66,'Math2':56}}>>>otsuhachi.grades{'Japanese':68,'Social Studies':28,'Math':{'Math1':66,'Math2':56}}# Math内はキー欠落可>>>otsuhachi.grades={'Japanese':68,'Social Studies':28,'Math':{'Math1':66}}>>>otsuhachi.grades{'Japanese':68,'Social Studies':28,'Math':{'Math1':66}}gradesで起こりえる不正この設定では書き換えに対して無力です。otsuhachi.gradesが呼び出されたとき限定で検証が行われるので、以下のような操作では不正が行えます。# 正常な形式でgradesをセット>>>otsuhachi.grades={'Japanese':68,'Social Studies':28,'Math':{'Math1':66,'Math2':56}}>>>otsuhachi.grades{'Japanese':68,'Social Studies':28,'Math':{'Math1':66,'Math2':56}}# grades['Math']を66にする>>>otsuhachi.grades['Math']=66>>>otsuhachi.grades{'Japanese':68,'Social Studies':28,'Math':66}gradesで起こりえる不正の防止不正の防止には主に2つの手段があります。バリデータをクラス外で定義し、必要に応じて検証を行うmonitoring_overwriteをTrueにする1.の方法では手間が掛かりますが、不要な時に検証されることがないので比較的高速な動作が期待されます。またmonitoring_overwriteはFalseでなければ2の方法と変わりありません。2.の方法ではotsuhachi.gradesが呼ばれるたびに検証されるので手軽です。どちらも書き換えは許してしまいますが、最終的に値を利用するタイミングでは検証が行われます。# 1の方法GRADES_VALIDATOR=VDict({'Japanese':VInt(0,100),'Social Studies':VInt(0,100),'Math':VDict({'Math1':VInt(0,100),'Math2':VInt(0,100)},allow_missing_key=True,monitoring_overwrite=True,)},allow_missing_key=False,monitoring_overwrite=False,)classStudent:# ...部分は前提コード通りです。...grades=GRADES_VALIDATOR...# 値のセットotsuhachi=Student()otsuhachi.grades={'Japanese':68,'Social Studies':28,'Math':{'Math1':66,'Math2':56}}>>>otsuhachi.grades{'Japanese':68,'Social Studies':28,'Math':{'Math1':66,'Math2':56}}# 不正な書き換え>>>otsuhachi.grades['Math']=66>>>otsuhachi.grades{'Japanese':68,'Social Studies':28,'Math':66}# 不正が困る場面>>>GRADES_VALIDATOR.validate(otsuhachi.grades)Traceback(mostrecentcalllast):...TypeError:dict型である必要があります。(66:int)Duringhandlingoftheaboveexception,anotherexceptionoccurred:Traceback(mostrecentcalllast):...TypeError:キー'Math'は不正な値です。(66:int)# 2の方法classStudent:# ...部分は前提コード通りです。...grades=VDict(...monitoring_overwrite=True,)...# 値のセットotsuhachi=Student()>>>otsuhachi.grades={'Japanese':68,'Social Studies':28,'Math':{'Math1':66,'Math2':56}}>>>otsuhachi.grades{'Japanese':68,'Social Studies':28,'Math':{'Math1':66,'Math2':56}}# 不正な書き換え>>>otsuhachi.grades['Math']=66>>>otsuhachi.gradesTraceback(mostrecentcalllast):...TypeError:dict型である必要があります。(66:int)Duringhandlingoftheaboveexception,anotherexceptionoccurred:Traceback(mostrecentcalllast):...TypeError:キー'Math'は不正な値です。(66:int)バリデータ実行例-hobbyの操作目次に戻るotsuhachi.hobbyを操作します。Studentのhobby属性はCNoneable(VList(VString(1)))によって検証されます。CNoneableはバリデータにNoneを許可するクラスです。今回はotsuhachi.hobbyがNoneまたはVList(VString(1))の条件を満たす時に検証を通過します。# 失敗 (CNoneableはNoneを許可するだけで、初期値をNoneにはしない)>>>otsuhachi.hobbyTraceback(mostrecentcalllast):...AttributeError:'Student'objecthasnoattribute'_hobby'# 失敗 (不正な値)>>>otsuhachi.hobby=1Traceback(mostrecentcalllast):...TypeError:list型である必要があります。(1:int)# 失敗 (リスト内の値が不正)>>>otsuhachi.hobby=[1]Traceback(mostrecentcalllast):...TypeError:str型である必要があります。(1:int)Duringhandlingoftheaboveexception,anotherexceptionoccurred:Traceback(mostrecentcalllast):...TypeError:インデックス0は不正な値です。(1:int)# 成功>>>otsuhachi.hobby=None>>>print(otsuhachi.hobby)None# 成功>>>otsuhachi.hobby=['PC','game']>>>otsuhachi.hobby['PC','game']# 失敗 (不正な値を追加後に参照)>>>otsuhachi.hobby.append(1)>>>otsuhachi.hobbyTraceback(mostrecentcalllast):...TypeError:str型である必要があります。(1:int)Duringhandlingoftheaboveexception,anotherexceptionoccurred:Traceback(mostrecentcalllast):...TypeError:インデックス2は不正な値です。(1:int)バリデータ実行例-addressの操作目次に戻るotsuhachi.addressを操作します。Studentのaddress属性はCNoneable(VList([VRegex('^\\d{3}-?\\d{4}$'), VRegex('(?!.*\\d.*)')によって検証されます。基本的な失敗例、成功例はhobbyを参照してください。address属性の特殊な点はVListのTEMPLATEがlist型である点です。value[i]がTEMPLATE[i]でそれぞれ検証されます。# 失敗 (要素数が足りていない)>>>otsuhachi.address=[]Traceback(mostrecentcalllast):...ValueError:あと3個設定する必要があります。([]:list)# 失敗 (要素数が多い)>>>otsuhachi.address=['','','','']Traceback(mostrecentcalllast):...ValueError:あと1個減らす必要があります。(['','','','']:list)# 失敗 (不正な値)>>>otsuhachi.address=['0000000000','Otsu Prefecture2','OtsuCity']Traceback(mostrecentcalllast):...ValueError:正規表現'^\\d{3}-?\\d{4}$'に対応している必要があります。('0000000000':str)Duringhandlingoftheaboveexception,anotherexceptionoccurred:Traceback(mostrecentcalllast):...ValueError:インデックス0は不正な値です。('0000000000':str)# 失敗 (不正な値)>>>otsuhachi.address=['282-2828','Otsu Prefecture2','OtsuCity']Traceback(mostrecentcalllast):...ValueError:正規表現'(?!.*\\d.*)'に対応している必要があります。('Otsu Prefecture2':str)Duringhandlingoftheaboveexception,anotherexceptionoccurred:Traceback(mostrecentcalllast):...ValueError:インデックス1は不正な値です。('Otsu Prefecture2':str)# 成功>>>otsuhachi.address=['282-2828','Otsu Prefecture','OtsuCity']>>>otsuhachi.address['282-2828','Otsu Prefecture','OtsuCity']バリデータ実行例-成功目次に戻るすべてのバリデータでの検証が終われば、設計通りにクラスが動作します。>>>otsuhachi.name='Otsuhachi'>>>otsuhachi.age=28>>>otsuhachi.gender='male'>>>otsuhachi.grades={'Japanese':68,'Social Studies':28,'Math':{'Math1':66,'Math2':56}}>>>otsuhachi.hobby=['PC','game']>>>otsuhachi.address=['282-2828','Otsu Prefecture','OtsuCity']>>>otsuhachi.show_profile()名前:Otsuhachi年齢:28性別:male国語:68社会:28数学:{'Math1':66,'Math2':56}趣味:['PC','game']住所:['282-2828','Otsu Prefecture','OtsuCity']コンバータの変換基本的にコンバータはC<対象の型名>で、<対象の型>(value)で変換できるかどうかを試すのが基本になります。たとえばCStringならばstr(value)を試みてから検証を行います。
しかし、コンバータによってはその基本に従わないものがあります。CIntは、int(value)できなかった場合にint(float(value))を試します(CFloatはその逆の動作です)。これはまだ理解しやすい変換ですが、以下の2つのコンバータは若干特殊な挙動の変換を行います。これはjsonファイルや標準入力などで受け取った場合の変換処理を容易に行うためです。CTimedeltaこのコンバータはstr,dict,list,tupleいずれかの型である場合にTimedelta型に変換を試みます。変換に必要な形式は以下の通りです。型形式str(<日>( )day(s, ))<時>:<分>:<秒>(.<ミリ秒>)()で囲まれた部分の有無は任意要はstr(<timedeltaインスタンス>)で変換された後の形式(厳密には日と時間の間の空白を問わないなど若干異なる)dicttimedelta(**value)でインスタンスを生成できる形式list, tupletimedelta(*value)でインスタンスを生成できる形式CBoolこのコンバータは以下の標準定義の項目をboolに変換します。また、自分でTrueになる値、Falseになる値を設定することも可能です。さらに、f(value)が真偽値を返す関数fのタプルを渡して判定することも可能です。以下が標準定義の真偽値対応表です。str型はvalue.lower()されたあとで判定されるので、大文字小文字を問いません。型TrueFalseboolTrueFalsestr'true', 'yes', 'y', '1''false', 'no', 'n', '0'int10float1.00.0実行例-コンバータfromdatetimeimporttimedeltafromtypingimportcastfromotsuvalidatorimportCBool,CTimedeltaclassSampleClass:bool_dflt:bool=cast(bool,CBool())bool_user:bool=cast(bool,CBool(true_data=('はい',),false_data=('いいえ',)))td_timedelta:timedelta=cast(timedelta,CTimedelta())td_str:timedelta=cast(timedelta,CTimedelta())td_tuple:timedelta=cast(timedelta,CTimedelta())td_dict:timedelta=cast(timedelta,CTimedelta())defshow(self):keys=('bool_dflt','bool_user','td_timedelta','td_str','td_tuple','td_dict',)forkinkeys:v=getattr(self,k)print(f'{k}:{v}({type(v).__name__})')s=SampleClass()td=timedelta(days=1,seconds=2,microseconds=3,milliseconds=4,minutes=5,hours=6,weeks=7)# s.bool_dflt = 'はい'  # Error# 一般にYes/Noとして解釈されるものはboolに変換s.bool_dflt='yes'# ユーザ定義のTrueなのでTrueになるs.bool_user='はい'# 無変換でtimedeltas.td_timedelta=td# 特定形式の文字列をtimedeltaに変換s.td_str='50 days, 0:0:1'# 特定形式のタプル、リストをtimedeltaに変換s.td_tuple=(1,2,3,4,5,7)# 特定形式の辞書をtimedeltaに変換s.td_dict={'seconds':2,'microseconds':3,'milliseconds':4,'minutes':5,'hours':6}# 属性名: str(属性)(属性の型)を出力s.show()bool_dflt: True(bool)bool_user: True(bool)td_timedelta: 50 days, 6:05:02.004003(timedelta)td_str: 50 days, 0:00:01(timedelta)td_tuple: 1 day, 7:05:02.004003(timedelta)td_dict: 6:05:02.004003(timedelta)