Skip to main content

設定ファイルを扱うクラスを生成するライブラリ

Project description

概要

このライブラリは設定ファイルの読み書きを補助するための基底クラスです。
BaseCMクラスを継承し、__defaults__, <attributes>を定義するだけで必要な操作を行えるようになります。

現在違うセクションに同じキーを持つような設定ファイルには対応していません。 対応しないことに決定しました。

# 違うセクションに同じキーを持つ例
{
    'app': {'name': 'Hello'},
    'default': {'name': 'Python'}
}

インストール

インストール

pip install otsucfgmng

アップデート

pip install -U otsucfgmng

アンインストール

pip uninstall otsucfgmng

使い方

  1. otuvalidatorをインポートし、必要なバリデータ、コンバータを使用できるようにする
    • 自作クラスなどを使用したい場合にはOtsuValidatorや実際のコードを参考に定義する(参考例)
  2. otsucfgmngをインポートし、BaseCMを使用できるようにする
  3. BaseCMを継承したクラスを定義する
    1. 属性__defaults__に辞書形式で利用する属性名とその初期値を与える
    2. __defaults__で宣言した属性名に1.で用意したコンバータを与える
  4. 設定ファイルのパスを与えてインスタンスを作成する
  5. インスタンスの属性を書き換えて編集を行う
  6. save_cmを呼び出せば設定ファイルが出力される

実行例-設定ファイル管理クラス

作成-設定ファイル管理クラス

実行例に戻る

cfg.jsonという設定ファイルを作成していきます。

# 1.
from otsuvalidator import CBool, CInt, CPath, CString

# 2.
from otsucfgmng import BaseCM


# 3.
class ConfigurationManager(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.2.
    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 = 99
cm.bgs = 50

# 6.
cm.save_cm()

上記の処理で作成されたcfg.jsonの中身は以下の通りです。
実行するたびにキーの並びは異なります。

{
    "app": {},
    "audio": {
        "bgm": 99,
        "bgs": 50
    }
}

読み込み-設定ファイル管理クラス

実行例に戻る

正しい形式で出力された設定ファイルをインスタンスの生成時に与えると自動で設定を読み込みます。

from otsuvalidator import CBool, CInt, CPath, CString

from otsucfgmng import BaseCM


class ConfigurationManager(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されます。
with ConfigurationManager('cfg.json') as cm:
    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
    }
}

Q&A

以下の説明でcmが登場した場合、ユーザが定義した設定ファイル管理クラスのインスタンスだと解釈してください。

なぜ異なるセクションで同名キーを持てないようにしましたか?

  1. 残念ながらライブラリ作成者の技術的な面も大きいです。
  2. 後述する理由を克服、緩和する方法が追加、あるいは理解できれば異なるセクションでの同名キーを持てるようになる可能性もなくはないです

ロガーの設定ファイルなど、異なるセクションで同名キーを持ちたい状況はありますが、それに対応する場合、アクセスが複雑になりすぎる恐れがあります。

例えば、現在の属性へのアクセスcm.<key>に加えて、辞書を持つキーをSectionクラスとして変換すればcm.<section>.<key>, cm.<sectionA>.<sectionB>.~.<key>というように管理することは技術的に可能になります。

しかしそうなると、実行例で使用したような構造の設定ではアクセスが煩雑になるデメリットもあります。
cm.fullscreenでアクセスしていたものがcm.app.fullscreenとなる等。
また、動的にクラスを生成する都合上、コード入力支援が受けられず、ヒューマンエラーのリスクも上がってしまいます。

自作クラスを使用する場合に必要なバリデータとコンバータをどう用意すればいいですか?

  1. otsuvalidator.basesからConverter, Validatorをインポートし、使用できるようにする
    1. その他必要なライブラリをインポート
  2. 自作クラスを定義する
    1. 好きなようにクラスを設計する
    2. __str__to_jsonメソッドを定義する
  3. 自作クラスに対応したValidatorを定義する(以下そのValidatorをVMyClassとする)
  4. VMyClassConverterを継承したCMyClassを定義する
  5. 使い方どおり

実行例-自作クラスの使用

作成-自作クラスの使用

実行例に戻る

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.
from typing import Any

from otsuvalidator import CNoneable
from otsuvalidator.bases import Converter, Validator

from otsucfgmng import BaseCM


# 2.
class Person:
    # 2.1.
    def __init__(self, name: str, age: int, gender: str):
        self.name = name
        self.age = age
        self.gender = gender

    def show_profile(self):
        print(self)

    # 2.2.
    def __str__(self) -> str:
        data = (
            ('名前', self.name),
            ('年齢', self.age),
            ('性', self.gender),
        )
        prof = []
        for k, v in data:
            prof.append(f'{k}\t: {v}')
        prof = '\n'.join(prof)
        return prof

    def to_json(self) -> dict:
        data = {'name': self.name, 'age': self.age, 'gender': self.gender}
        return data


# 3.
class VPerson(Validator):
    def validate(self, value: Any) -> Person:
        if type(value) is not Person:
            msg = self.ERRMSG('Person型である必要があります', value)
            raise TypeError(msg)
        return value


# 4.
class CPerson(VPerson, Converter):
    def validate(self, value: Any) -> Person:
        if type(value) is not Person:
            try:
                if isinstance(value, dict):
                    value = Person(**value)
                else:
                    raise TypeError
            except:
                msg = self.ERRMSG('Person型として扱える必要があります。', value)
                raise TypeError(msg)
        return super().validate(value)

    def super_validate(self, value: Any) -> Person:
        return super().super_validate(value)


# 5.
class ConfigurationManager(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())


with ConfigurationManager('my_company.json') as cm:
    cm.president = Person('乙八', 28, '男')
    cm.director = Person('部長夢', 28, '女')
    cm.manager = None
    cm.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.
from typing import Any

from otsuvalidator import CNoneable
from otsuvalidator.bases import Converter, Validator

from otsucfgmng import BaseCM


# 2.
class Person:
    # 2.1.
    def __init__(self, name: str, age: int, gender: str):
        self.name = name
        self.age = age
        self.gender = gender

    def show_profile(self):
        print(self)

    # 2.2.
    def __str__(self) -> str:
        data = (
            ('名前', self.name),
            ('年齢', self.age),
            ('性', self.gender),
        )
        prof = []
        for k, v in data:
            prof.append(f'{k}\t: {v}')
        prof = '\n'.join(prof)
        return prof

    def to_json(self) -> dict:
        data = {'name': self.name, 'age': self.age, 'gender': self.gender}
        return data


# 3.
class VPerson(Validator):
    def validate(self, value: Any) -> Person:
        if type(value) is not Person:
            msg = self.ERRMSG('Person型である必要があります', value)
            raise TypeError(msg)
        return value


# 4.
class CPerson(VPerson, Converter):
    def validate(self, value: Any) -> Person:
        if type(value) is not Person:
            try:
                if isinstance(value, dict):
                    value = Person(**value)
                else:
                    raise TypeError
            except:
                msg = self.ERRMSG('Person型として扱える必要があります。', value)
                raise TypeError(msg)
        return super().validate(value)

    def super_validate(self, value: Any) -> Person:
        return super().super_validate(value)


# 5.
class ConfigurationManager(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())


with ConfigurationManager('my_company.json') as cm:
    for i, key in enumerate(('president', 'director', 'manager', 'chief')):
        if i:
            print('-' * 75)
        print(f'{key}\n{getattr(cm,key)}')
### 出力は以下のようになります (実行するたびにキーの並びは異なります) ###
president
名前    : 乙八
年齢    : 28
性      : 男
---------------------------------------------------------------------------
director
名前    : 部長夢
年齢    : 28
性      : 女
---------------------------------------------------------------------------
manager
None
---------------------------------------------------------------------------
chief
None

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distributions

No source distribution files available for this release.See tutorial on generating distribution archives.

Built Distribution

otsucfgmng-1.1.0-py3-none-any.whl (10.3 kB view hashes)

Uploaded Python 3

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page