Package: otsucfgmng
概要インストール使い方メソッド一覧Q&A概要このライブラリはjson形式の設定ファイルの読み書きを補助するための基底クラスです。BaseCMクラスを継承し、__defaults__,<attributes>を定義するだけで必要な操作を行えるようになります。現在違うセクションに同じキーを持つような設定ファイルには対応していません。対応しないことに決定しました。(理由はこちら)このライブラリは以下の環境で作成されています。Windows10(64bit),Python3.11.1# 違うセクションに同じキーを持つ例{'app':{'name':'Hello'},'default':{'name':'Python'}}インストールインストールpip install otsucfgmngアップデートpip install -U otsucfgmngアンインストールpip uninstall otsucfgmng使い方otuvalidatorをインポートし、必要なバリデータ、コンバータを使用できるようにする自作クラスなどを使用したい場合にはOtsuValidatorや実際のコードを参考に定義する(参考例)otsucfgmngをインポートし、BaseCMを使用できるようにするBaseCMを継承したクラスを定義する属性__defaults__に辞書形式で利用する属性名とその初期値を与える__hidden_options__で隠しオプションを設定する (必要に応じて)__defaults__で宣言した属性名に1.で用意したコンバータを与える設定ファイルのパスを与えてインスタンスを作成するインスタンスの属性を書き換えて編集を行うsave_cmを呼び出せば設定ファイルが出力される実行例-設定ファイル管理クラス作成読み込み作成-設定ファイル管理クラス実行例に戻るcfg.jsonという設定ファイルを作成していきます。# 1.fromotsuvalidatorimportCBool,CInt,CPath,CString# 2.fromotsucfgmngimportBaseCM# 3.classConfigurationManager(BaseCM):# 3.1.__defaults__={'app':{'library':'SampleLibrary.dll','scripts':'SampleScripts.scrpt','title':'Sample Program','fullscreen':False},'audio':{'bgm':100,'bgs':100,'se':100,'me':85}}# 3.3.library=CPath('dll')scripts=CPath('scrpt')title=CString(1,checker=str.istitle)fullscreen=CBool()bgm=CInt(0,100)bgs=CInt(0,100)se=CInt(0,100)me=CInt(0,100)# 4.cm=ConfigurationManager('cfg.json')# 5.cm.bgm=99cm.bgs=50# 6.cm.save_cm()上記の処理で作成されたcfg.jsonの中身は以下の通りです。{"app":{},"audio":{"bgm":99,"bgs":50}}読み込み-設定ファイル管理クラス実行例に戻る正しい形式で出力された設定ファイルをインスタンスの生成時に与えると自動で設定を読み込みます。fromotsuvalidatorimportCBool,CInt,CPath,CStringfromotsucfgmngimportBaseCMclassConfigurationManager(BaseCM):__defaults__={'app':{'library':'SampleLibrary.dll','scripts':'SampleScripts.scrpt','title':'Sample Program','fullscreen':False},'audio':{'bgm':100,'bgs':100,'se':100,'me':85}}library=CPath('dll')scripts=CPath('scrpt')title=CString(1,checker=str.istitle)fullscreen=CBool()bgm=CInt(0,100)bgs=CInt(0,100)se=CInt(0,100)me=CInt(0,100)# コンテキストマネージャを使用すると自動でsave_cmされます。withConfigurationManager('cfg.json')ascm:print(cm.user_cm())cm.fullscreen='yes'print(cm.user_cm())### 出力は以下のようになります ###{'app':{},'audio':{'bgs':50,'bgm':99}}{'app':{'fullscreen':True},'audio':{'bgs':50,'bgm':99}}上記の処理で作成されたcfg.jsonの中身は以下の通りです。{"app":{"fullscreen":true},"audio":{"bgs":50,"bgm":99}}隠しオプション先ほどのコードに隠しオプションを追加します。方法はシンプルで、__hidden_options__に対象の属性名のタプルを渡すだけです。今回はfullscreenとme属性を隠しオプションにしてみます。fromotsuvalidatorimportCBool,CInt,CPath,CStringfromotsucfgmngimportBaseCMclassConfigurationManager(BaseCM):__defaults__={'app':{'library':'SampleLibrary.dll','scripts':'SampleScripts.scrpt','title':'Sample Program','fullscreen':False},'audio':{'bgm':100,'bgs':100,'se':100,'me':85}}__hidden_options__=('fullscreen','me')# この行を追加library=CPath('dll')scripts=CPath('scrpt')title=CString(1,checker=str.istitle)fullscreen=CBool()bgm=CInt(0,100)bgs=CInt(0,100)se=CInt(0,100)me=CInt(0,100)withConfigurationManager('cfg.json')ascm:print(cm.cfg_to_str_cm(True))出力{"defaults":{"app":{"fullscreen":false,"library":"SampleLibrary.dll","scripts":"SampleScripts.scrpt","title":"Sample Program"},"audio":{"bgm":100,"bgs":100,"se":100}},"user":{"app":{"fullscreen":true},"audio":{"bgm":99,"bgs":50}}}cfg.jsonはここで作成されたものが存在する前提になります。隠しオプションに設定したmeは表示されていませんが、fullscreenは表示されていることがわかるかと思います。これはfullscreenが変更可能であることを知っている場合には隠す意味がないからです。メソッド一覧argparseやGUIなどで設定項目を編集するための補助を想定しています。showコマンドを作成してcfg_to_str_cmを制御するなど、自身のUIに合うように紐づけて使ってください。名前概要戻り値戻り値型cfg_to_str_cm設定をjson.dumpsして返すallをTrueにしている場合は標準設定も表示されるユーザが変更していない__hidden_options__は表示されないユーザに設定を見せる場合にはこのメソッドを使って出力する設定strload_cm設定ファイルを読み込む__init__から勝手に呼び出されるので、基本的に使用する必要はないNonesave_cm現在の設定を書き出すコンテキストマネージャを使用していれば勝手に呼び出されるNonereset_cm各属性を初期値に戻すNonedefaults_cm__defaults__のコピーを返す初期設定dictuser_cmユーザが変更した属性の辞書を返す変更された設定dictkey_place_cm各属性がどのセクションに属するかを記録した辞書を返すユーザにアクセスを許すと__hidden_options__が意味をなくす属性の所属先dictattributes_cm設定項目の一覧を返すユーザにアクセスを許すと__hidden_options__が意味をなくす設定項目の一覧setQ&A以下の説明でcmが登場した場合、ユーザが定義した設定ファイル管理クラスのインスタンスだと解釈してください。なぜ異なるセクションで同名キーを持てないようにしましたか？自作クラスを使用する場合に必要なバリデータとコンバータをどう用意すればいいですか？なぜ異なるセクションで同名キーを持てないようにしましたか？残念ながらライブラリ作成者の技術的な面も大きいです。後述する理由を克服、緩和する方法が追加、あるいは理解できれば異なるセクションでの同名キーを持てるようになる可能性もなくはないです。ロガーの設定ファイルなど、異なるセクションで同名キーを持ちたい状況はありますが、それに対応する場合、アクセスが複雑になりすぎる恐れがあります。例えば、現在の属性へのアクセスcm.<key>に加えて、辞書を持つキーをSectionクラスとして変換すればcm.<section>.<key>,cm.<sectionA>.<sectionB>.~.<key>というように管理することは技術的に可能になります。しかしそうなると、実行例で使用したような構造の設定ではアクセスが煩雑になるデメリットもあります。cm.fullscreenでアクセスしていたものがcm.app.fullscreenとなる等。また、動的にクラスを生成する都合上、コード入力支援が受けられず、ヒューマンエラーのリスクも上がってしまいます。自作クラスを使用する場合に必要なバリデータとコンバータをどう用意すればいいですか？otsuvalidator.basesからConverter,Validatorをインポートし、使用できるようにするその他必要なライブラリをインポート自作クラスを定義する好きなようにクラスを設計する (__eq__メソッドを定義していない場合__hidden_options__が正常に機能しない場合があります)__str__かto_jsonメソッドを定義する自作クラスに対応したValidatorを定義する(以下そのValidatorをVMyClassとする)VMyClassとConverterを継承したCMyClassを定義する使い方どおり実行例-自作クラスの使用作成読み込み作成-自作クラスの使用実行例に戻るmy_company.jsonという設定ファイルを作成していきます。ConfigurationManagerではjson.dumpできない属性はotsucfgmng.funcs.support_json_dump関数で変換を試みます(※独自に指定しない限り)この関数ではto_jsonを持つクラスをcls.to_jsonで、それ以外のクラスをstr(cls)で変換します。なので、cls.to_jsonで返る値をclsに復元できるようなConverterを定義すれば読み込み時に復元されます。# 1. & 1.1.fromtypingimportAnyfromotsuvalidatorimportCNoneablefromotsuvalidator.basesimportConverter,ValidatorfromotsucfgmngimportBaseCM# 2.classPerson:# 2.1.def__init__(self,name:str,age:int,gender:str):self.name=nameself.age=ageself.gender=genderdefshow_profile(self):print(self)# 2.2.def__str__(self)->str:data=(('名前',self.name),('年齢',self.age),('性',self.gender),)prof=[]fork,vindata:prof.append(f'{k}\t:{v}')prof='\n'.join(prof)returnprofdefto_json(self)->dict:data={'name':self.name,'age':self.age,'gender':self.gender}returndata# 3.classVPerson(Validator):defvalidate(self,value:Any)->Person:iftype(value)isnotPerson:msg=self.ERRMSG('Person型である必要があります',value)raiseTypeError(msg)returnvalue# 4.classCPerson(VPerson,Converter):defvalidate(self,value:Any)->Person:iftype(value)isnotPerson:try:ifisinstance(value,dict):value=Person(**value)else:raiseTypeErrorexcept:msg=self.ERRMSG('Person型として扱える必要があります。',value)raiseTypeError(msg)returnsuper().validate(value)defsuper_validate(self,value:Any)->Person:returnsuper().super_validate(value)# 5.classConfigurationManager(BaseCM):__defaults__={'president':Person('山田太郎',28,'男'),'employee':{'director':Person('部長花子',28,'女'),'manager':Person('課長夢',28,'女'),'chief':Person('係長次郎',28,'男')}}president=CPerson()director=CNoneable(CPerson())manager=CNoneable(CPerson())chief=CNoneable(CPerson())withConfigurationManager('my_company.json')ascm:cm.president=Person('乙八',28,'男')cm.director=Person('部長夢',28,'女')cm.manager=Nonecm.chief=None上記の処理で作成されたmy_company.jsonの中身は以下の通りです。{"employee":{"chief":null,"manager":null,"director":{"name":"部長夢","age":28,"gender":"女"}},"president":{"name":"乙八","age":28,"gender":"男"}}定義通りPersonクラスはperson.to_jsonを通して辞書形式に変換されていることがわかります。読み込み-自作クラスの使用実行例に戻る# 1. & 1.1.fromtypingimportAnyfromotsuvalidatorimportCNoneablefromotsuvalidator.basesimportConverter,ValidatorfromotsucfgmngimportBaseCM# 2.classPerson:# 2.1.def__init__(self,name:str,age:int,gender:str):self.name=nameself.age=ageself.gender=genderdefshow_profile(self):print(self)# 2.2.def__str__(self)->str:data=(('名前',self.name),('年齢',self.age),('性',self.gender),)prof=[]fork,vindata:prof.append(f'{k}\t:{v}')prof='\n'.join(prof)returnprofdefto_json(self)->dict:data={'name':self.name,'age':self.age,'gender':self.gender}returndata# 3.classVPerson(Validator):defvalidate(self,value:Any)->Person:iftype(value)isnotPerson:msg=self.ERRMSG('Person型である必要があります',value)raiseTypeError(msg)returnvalue# 4.classCPerson(VPerson,Converter):defvalidate(self,value:Any)->Person:iftype(value)isnotPerson:try:ifisinstance(value,dict):value=Person(**value)else:raiseTypeErrorexcept:msg=self.ERRMSG('Person型として扱える必要があります。',value)raiseTypeError(msg)returnsuper().validate(value)defsuper_validate(self,value:Any)->Person:returnsuper().super_validate(value)# 5.classConfigurationManager(BaseCM):__defaults__={'president':Person('山田太郎',28,'男'),'employee':{'director':Person('部長花子',28,'女'),'manager':Person('課長夢',28,'女'),'chief':Person('係長次郎',28,'男')}}president=CPerson()director=CNoneable(CPerson())manager=CNoneable(CPerson())chief=CNoneable(CPerson())withConfigurationManager('my_company.json')ascm:fori,keyinenumerate(('president','director','manager','chief')):ifi:print('-'*75)print(f'{key}\n{getattr(cm,key)}')### 出力は以下のようになります ###president名前    : 乙八年齢    : 28性      : 男---------------------------------------------------------------------------director名前    : 部長夢年齢    : 28性      : 女---------------------------------------------------------------------------managerNone---------------------------------------------------------------------------chiefNone