mentortools/libs/: init-helpers-abm-3.1.75045a0 metadata and description

Simple index Stable version available

Tools to parse args, read config, init logs and etc

author Mike Orlov
author_email m.orlov@abm-jsc.ru
classifiers
  • Programming Language :: Python :: 3
  • Programming Language :: Python :: 3.11
description_content_type text/markdown
requires_python >=3.11,<4.0
File Tox results History
init_helpers_abm-3.1.75045a0-py3-none-any.whl
Size
25 KB
Type
Python Wheel
Python
3
init_helpers_abm-3.1.75045a0.tar.gz
Size
18 KB
Type
Source

Init Helper

Tools to parse args, read config, init logs and etc

Копирование кода для тестов
cp -r $ORIGIN_DIR/init_helpers ./init_helpers

Парсинг конфигов

Создадим toml конфиг файл

printf 'some = 123\nother = "www"\nbar = "empty"' > example.toml

Проверим содержимое

cat example.toml

получим:

some = 123
other = "www"
bar = "empty"

Считаем конфиг как экземпляр датакласса

Подготовим python файл, имитирующий чтение конфига и печатающий результат

printf '
import dataclasses
import init_helpers
@dataclasses.dataclass
class Config:
    other: str
    some: int = 42
    foo: float = 1.23    
    topics: tuple[str, ...] = ("one", )    
config = init_helpers.parse_args_as_dataclass(Config)
print(config)
' > init.py

Запустим инициализацию, передав ей конфиг

python init.py example.toml

Получим

Config(other='www', some=123, foo=1.23, topics=('one',))

Запустим инициализацию с конфигом и переопределив значения из конфига

python init.py example.toml -v foo=4.2 -v some=42

Получим

Config(other='www', some=42, foo=4.2, topics=('one',))

Запустим инициализацию без конфига, передав необходимые значения через аргументы запуска

python init.py -v other=qqq -v 'topics=["two","four"]'

Получим

Config(other='qqq', some=42, foo=1.23, topics=('two', 'four'))

Создадим ini конфиг файл

printf '[main]\ntimeout = 10\nurl = http://\ntopics = ["ini"]' > example2.ini

Проверим содержимое

cat example2.ini

получим:

[main]
timeout = 10
url = http://
topics = ["ini"]

Считаем ini конфиг как экземпляр датакласса

Подготовим python файл, имитирующий чтение конфига и печатающий результат

printf '
import dataclasses
import init_helpers
@dataclasses.dataclass
class Config:
    @dataclasses.dataclass
    class MainConfig:
        url: str
        topics: tuple[str]
        timeout: float = 5
    main: MainConfig    
config = init_helpers.parse_args_as_dataclass(Config)
print(config)
' > init.py

Запустим инициализацию, передав ей ini конфиг

python init.py example2.ini

Получим

Config(main=Config.MainConfig(url='http://', topics=('ini',), timeout=10.0))

Примечание

init_helpers.parse_args_as_dataclass парсит файл на основе расширения, поэтому один и тот же код может работать и с .ini и .toml файлами. Для примера создадим toml файл и запустим с ним приложение:

printf '[main]\ntimeout = 10\nurl = "http://"\ntopics = ["toml"]' > example2.toml
python init.py example2.toml

Получим:

Config(main=Config.MainConfig(url='http://', topics=('toml',), timeout=10.0))

Conditional insert

Conditional append

Appends passed args to list passed as first argument, one by one checking condition.
Default condition is init_helpers.is_not_none. Example:

from init_helpers import conditional_append
conditional_append(target := [73, 123], 'value', None, 1, -2, 3)
print(target)
conditional_append(target := [73, 123], 'value', None, 1, -2, 3, 
                   _condition=lambda x: isinstance(x, int) and x > 0)
print(target)

will produce:

[73, 123, 'value', 1, -2, 3]
[73, 123, 1, 3]

Conditional add

Adds passed args to set passed as first argument, one by one checking condition.
Default condition is init_helpers.is_not_none. Example:

from init_helpers import conditional_add
conditional_add(target := {73, 123}, None, 1, -2, 3)
print(target)
conditional_add(target := {73, 123}, 1, -2, 3, _condition=lambda x: x > 0)
print(target)

will produce:

{1, 3, 73, 123, -2}
{3, 73, 123, 1}

Conditional set

Sets passed kwargs to MutableMapping passed as first argument, one by one checking values by condition.
Default condition is init_helpers.is_not_none. Example:

from init_helpers import conditional_set
conditional_set(target := {'test': 'q'}, key='value', skip=None)
print(target)
conditional_set(target := {'test': 'q'}, key1=1, key2=2, skip1=-1, skip2=-2, _condition=lambda x: x > 0)
print(target)

will produce (sets do not keep ordering, so values are shuffled):

{'test': 'q', 'key': 'value'}
{'test': 'q', 'key1': 1, 'key2': 2}

CustomDumps

Extended version of json.dumps

Basic types serialise as in original

from init_helpers import custom_dumps
print("None:", custom_dumps(None))
print("int:", custom_dumps(42))
print("float:", custom_dumps(4.2))
print("bool:", custom_dumps(True))
print("list:", custom_dumps([42]))
print("dict:", custom_dumps({42: 4.2}))

will produce

None: null
int: 42
float: 4.2
bool: true
list: [42]
dict: {"42": 4.2}

Additional types serialise

import dataclasses
import zoneinfo
import datetime
from decimal import Decimal
from init_helpers import ReprInDumps, custom_dumps, read_json_file_by_path

print("set:", custom_dumps({42}))  # serialised as list
print("Decimal:", custom_dumps(Decimal("1.20") + Decimal("1.30")))  # serialised as string
print("Exception:", custom_dumps(ValueError("payload")))  # serialised as string by repr method
print("bytes:", custom_dumps(bytes.fromhex('ffeeaa')))  # as base64 with prefix: data:application/octet-stream;base64,
print("time:", custom_dumps(datetime.time(12, 34, 56)))  # serialised as iso string
print("date:", custom_dumps(datetime.date(2011, 11, 11)))  # serialised as iso string
print("aware datetime:", custom_dumps(datetime.datetime(
    2024, 11, 11, tzinfo=zoneinfo.ZoneInfo('Europe/Moscow'))))  # tz-aware serialised as int millisecond timestamp
try:
    custom_dumps(datetime.datetime(2024, 11, 11))  # naive datetime is disallowed
except TypeError as e:
    print("naive datetime:", e)
  

@dataclasses.dataclass
class Foo:
    a: int
    b: str = 'www'
    c: float = dataclasses.field(default=123, repr=False)

print("dataclass instance:", custom_dumps(Foo(42)))  # serialised as dict WITHOUT field with repr=False

will produce

set: [42]
Decimal: "2.50"
Exception: {"_t": "PY::Exception", "key": "ValueError", "args": ["payload"]}
bytes: "data:application/octet-stream;base64,b'/+6q'"
time: "12:34:56"
date: "2011-11-11"
aware datetime: "2024-11-11T00:00:00+03:00"
naive datetime: TypeError: datetime.datetime WITHOUT tzinfo is not JSON serializable
dataclass instance: {"a": 42, "b": "www"}

Dataclass Protocol

Can be used to use isinstance on dataclasses. Example:

Create a file with typing based on DataclassProtocol

from typing import Any
from dataclasses import dataclass
from init_helpers import DataclassProtocol

def is_dc(obj: DataclassProtocol | Any) -> bool:
    return isinstance(obj, DataclassProtocol)
    
@dataclass
class DC:
    a: int = 1

class NonDC:
    a: int
    
print(f'{is_dc(DC)=}')
print(f'{is_dc(NonDC)=}')
print(f'{is_dc(DC())=}')
print(f'{is_dc(NonDC())=}')
is_dc(DC)=True
is_dc(NonDC)=False
is_dc(DC())=True
is_dc(NonDC())=False