potato-util 0.1.0__py3-none-any.whl → 0.2.1__py3-none-any.whl
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.
- potato_util/__version__.py +1 -1
- potato_util/constants/_enum.py +8 -0
- potato_util/io/_async.py +270 -1
- potato_util/io/_sync.py +261 -1
- {potato_util-0.1.0.dist-info → potato_util-0.2.1.dist-info}/METADATA +5 -3
- {potato_util-0.1.0.dist-info → potato_util-0.2.1.dist-info}/RECORD +9 -9
- {potato_util-0.1.0.dist-info → potato_util-0.2.1.dist-info}/WHEEL +1 -1
- {potato_util-0.1.0.dist-info → potato_util-0.2.1.dist-info}/licenses/LICENSE.txt +0 -0
- {potato_util-0.1.0.dist-info → potato_util-0.2.1.dist-info}/top_level.txt +0 -0
potato_util/__version__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "0.1
|
|
1
|
+
__version__ = "0.2.1"
|
potato_util/constants/_enum.py
CHANGED
|
@@ -24,8 +24,16 @@ class HashAlgoEnum(str, Enum):
|
|
|
24
24
|
sha512 = "sha512"
|
|
25
25
|
|
|
26
26
|
|
|
27
|
+
class ConfigFileFormatEnum(str, Enum):
|
|
28
|
+
YAML = "YAML"
|
|
29
|
+
JSON = "JSON"
|
|
30
|
+
TOML = "TOML"
|
|
31
|
+
INI = "INI"
|
|
32
|
+
|
|
33
|
+
|
|
27
34
|
__all__ = [
|
|
28
35
|
"WarnEnum",
|
|
29
36
|
"TSUnitEnum",
|
|
30
37
|
"HashAlgoEnum",
|
|
38
|
+
"ConfigFileFormatEnum",
|
|
31
39
|
]
|
potato_util/io/_async.py
CHANGED
|
@@ -1,12 +1,29 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
import json
|
|
4
|
+
import glob
|
|
1
5
|
import errno
|
|
2
6
|
import hashlib
|
|
3
7
|
import logging
|
|
8
|
+
import configparser
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Any
|
|
4
11
|
|
|
12
|
+
_binary_toml = False
|
|
13
|
+
if sys.version_info >= (3, 11):
|
|
14
|
+
import tomllib # type: ignore
|
|
15
|
+
|
|
16
|
+
_binary_toml = True
|
|
17
|
+
else:
|
|
18
|
+
import toml as tomllib # type: ignore
|
|
19
|
+
|
|
20
|
+
import yaml
|
|
5
21
|
import aioshutil
|
|
6
22
|
import aiofiles.os
|
|
7
23
|
from pydantic import validate_call
|
|
8
24
|
|
|
9
|
-
from ..
|
|
25
|
+
from .._base import deep_merge
|
|
26
|
+
from ..constants import WarnEnum, HashAlgoEnum, ConfigFileFormatEnum, MAX_PATH_LENGTH
|
|
10
27
|
|
|
11
28
|
|
|
12
29
|
logger = logging.getLogger(__name__)
|
|
@@ -276,6 +293,252 @@ async def async_get_file_checksum(
|
|
|
276
293
|
return _file_checksum
|
|
277
294
|
|
|
278
295
|
|
|
296
|
+
@validate_call
|
|
297
|
+
async def async_read_yaml_file(file_path: str | Path) -> dict[str, Any]:
|
|
298
|
+
"""Asynchronous read YAML file.
|
|
299
|
+
|
|
300
|
+
Args:
|
|
301
|
+
file_path (str | Path, required): YAML file path.
|
|
302
|
+
|
|
303
|
+
Raises:
|
|
304
|
+
FileNotFoundError: If YAML file is not found.
|
|
305
|
+
Exception : If failed to read YAML file.
|
|
306
|
+
|
|
307
|
+
Returns:
|
|
308
|
+
dict[str, Any]: YAML file data as dictionary.
|
|
309
|
+
"""
|
|
310
|
+
|
|
311
|
+
_data: dict[str, Any] = {}
|
|
312
|
+
|
|
313
|
+
if isinstance(file_path, str):
|
|
314
|
+
file_path = Path(file_path)
|
|
315
|
+
|
|
316
|
+
if not await aiofiles.os.path.isfile(file_path):
|
|
317
|
+
raise FileNotFoundError(f"Not found '{file_path}' YAML file!")
|
|
318
|
+
|
|
319
|
+
try:
|
|
320
|
+
async with aiofiles.open(file_path, "r", encoding="utf-8") as _file:
|
|
321
|
+
_content = await _file.read()
|
|
322
|
+
_data = yaml.safe_load(_content) or {}
|
|
323
|
+
except Exception:
|
|
324
|
+
logger.error(f"Failed to read '{file_path}' YAML file!")
|
|
325
|
+
raise
|
|
326
|
+
|
|
327
|
+
return _data
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
@validate_call
|
|
331
|
+
async def async_read_json_file(file_path: str | Path) -> dict[str, Any]:
|
|
332
|
+
"""Asynchronous read JSON file.
|
|
333
|
+
|
|
334
|
+
Args:
|
|
335
|
+
file_path (str | Path, required): JSON file path.
|
|
336
|
+
|
|
337
|
+
Raises:
|
|
338
|
+
FileNotFoundError: If JSON file is not found.
|
|
339
|
+
Exception : If failed to read JSON file.
|
|
340
|
+
|
|
341
|
+
Returns:
|
|
342
|
+
dict[str, Any]: JSON file data as dictionary.
|
|
343
|
+
"""
|
|
344
|
+
|
|
345
|
+
_data: dict[str, Any] = {}
|
|
346
|
+
|
|
347
|
+
if isinstance(file_path, str):
|
|
348
|
+
file_path = Path(file_path)
|
|
349
|
+
|
|
350
|
+
if not await aiofiles.os.path.isfile(file_path):
|
|
351
|
+
raise FileNotFoundError(f"Not found '{file_path}' JSON file!")
|
|
352
|
+
|
|
353
|
+
try:
|
|
354
|
+
async with aiofiles.open(file_path, "r", encoding="utf-8") as _file:
|
|
355
|
+
_content = await _file.read()
|
|
356
|
+
_data = json.loads(_content) or {}
|
|
357
|
+
except Exception:
|
|
358
|
+
logger.error(f"Failed to read '{file_path}' JSON file!")
|
|
359
|
+
raise
|
|
360
|
+
|
|
361
|
+
return _data
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
@validate_call
|
|
365
|
+
async def async_read_toml_file(file_path: str | Path) -> dict[str, Any]:
|
|
366
|
+
"""Asynchronous read TOML file.
|
|
367
|
+
|
|
368
|
+
Args:
|
|
369
|
+
file_path (str | Path, required): TOML file path.
|
|
370
|
+
|
|
371
|
+
Raises:
|
|
372
|
+
FileNotFoundError: If TOML file is not found.
|
|
373
|
+
Exception : If failed to read TOML file.
|
|
374
|
+
|
|
375
|
+
Returns:
|
|
376
|
+
dict[str, Any]: TOML file data as dictionary.
|
|
377
|
+
"""
|
|
378
|
+
|
|
379
|
+
_data: dict[str, Any] = {}
|
|
380
|
+
|
|
381
|
+
if isinstance(file_path, str):
|
|
382
|
+
file_path = Path(file_path)
|
|
383
|
+
|
|
384
|
+
if not await aiofiles.os.path.isfile(file_path):
|
|
385
|
+
raise FileNotFoundError(f"Not found '{file_path}' TOML file!")
|
|
386
|
+
|
|
387
|
+
try:
|
|
388
|
+
_content: str = ""
|
|
389
|
+
if _binary_toml:
|
|
390
|
+
async with aiofiles.open(file_path, "rb") as _file:
|
|
391
|
+
_content = await _file.read() # type: ignore
|
|
392
|
+
_data = tomllib.load(_content) or {}
|
|
393
|
+
else:
|
|
394
|
+
async with aiofiles.open(file_path, "r", encoding="utf-8") as _file:
|
|
395
|
+
_content = await _file.read() # type: ignore
|
|
396
|
+
_data = tomllib.loads(_content) or {}
|
|
397
|
+
|
|
398
|
+
except Exception:
|
|
399
|
+
logger.error(f"Failed to read '{file_path}' TOML file!")
|
|
400
|
+
raise
|
|
401
|
+
|
|
402
|
+
return _data
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
@validate_call
|
|
406
|
+
async def async_read_ini_file(file_path: str | Path) -> dict[str, Any]:
|
|
407
|
+
"""Asynchronous read INI config file.
|
|
408
|
+
|
|
409
|
+
Args:
|
|
410
|
+
file_path (str | Path, required): INI config file path.
|
|
411
|
+
|
|
412
|
+
Raises:
|
|
413
|
+
FileNotFoundError: If INI config file is not found.
|
|
414
|
+
Exception : If failed to read INI config file.
|
|
415
|
+
|
|
416
|
+
Returns:
|
|
417
|
+
dict[str, Any]: INI config file data as dictionary.
|
|
418
|
+
"""
|
|
419
|
+
|
|
420
|
+
_config: dict[str, Any] = {}
|
|
421
|
+
|
|
422
|
+
if isinstance(file_path, str):
|
|
423
|
+
file_path = Path(file_path)
|
|
424
|
+
|
|
425
|
+
if not await aiofiles.os.path.isfile(file_path):
|
|
426
|
+
raise FileNotFoundError(f"Not found '{file_path}' INI config file!")
|
|
427
|
+
|
|
428
|
+
try:
|
|
429
|
+
_content: str = ""
|
|
430
|
+
async with aiofiles.open(file_path, "r", encoding="utf-8") as _file:
|
|
431
|
+
_content = await _file.read()
|
|
432
|
+
|
|
433
|
+
_config_parser = configparser.ConfigParser()
|
|
434
|
+
_config_parser.read_string(_content)
|
|
435
|
+
for _section in _config_parser.sections():
|
|
436
|
+
_config[_section] = dict(_config_parser.items(_section))
|
|
437
|
+
|
|
438
|
+
except Exception:
|
|
439
|
+
logger.error(f"Failed to read '{file_path}' INI config file!")
|
|
440
|
+
raise
|
|
441
|
+
|
|
442
|
+
return _config
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
@validate_call
|
|
446
|
+
async def async_read_config_file(config_path: str | Path) -> dict[str, Any]:
|
|
447
|
+
"""Asynchronous read config file (YAML, JSON, TOML, INI).
|
|
448
|
+
|
|
449
|
+
Args:
|
|
450
|
+
config_path (str | Path, required): Config file path.
|
|
451
|
+
|
|
452
|
+
Raises:
|
|
453
|
+
FileNotFoundError: If config file is not found.
|
|
454
|
+
ValueError : If config file format is not supported.
|
|
455
|
+
|
|
456
|
+
Returns:
|
|
457
|
+
dict[str, Any]: Config file data as dictionary.
|
|
458
|
+
"""
|
|
459
|
+
|
|
460
|
+
_config: dict[str, Any] = {}
|
|
461
|
+
|
|
462
|
+
if isinstance(config_path, str):
|
|
463
|
+
config_path = Path(config_path)
|
|
464
|
+
|
|
465
|
+
if not await aiofiles.os.path.isfile(config_path):
|
|
466
|
+
raise FileNotFoundError(f"Not found '{config_path}' config file!")
|
|
467
|
+
|
|
468
|
+
_suffix = config_path.suffix.lower()
|
|
469
|
+
if _suffix in (".yaml", ".yml"):
|
|
470
|
+
_config = await async_read_yaml_file(config_path)
|
|
471
|
+
elif _suffix == ".json":
|
|
472
|
+
_config = await async_read_json_file(config_path)
|
|
473
|
+
elif _suffix == ".toml":
|
|
474
|
+
_config = await async_read_toml_file(config_path)
|
|
475
|
+
elif _suffix in (".ini", ".cfg"):
|
|
476
|
+
_config = await async_read_ini_file(config_path)
|
|
477
|
+
else:
|
|
478
|
+
raise ValueError(
|
|
479
|
+
f"Unsupported config file format '{_suffix}' for '{config_path}'!"
|
|
480
|
+
)
|
|
481
|
+
|
|
482
|
+
return _config
|
|
483
|
+
|
|
484
|
+
|
|
485
|
+
@validate_call
|
|
486
|
+
async def async_read_all_configs(
|
|
487
|
+
configs_dir: str | Path | list[str | Path],
|
|
488
|
+
allowed_formats: list[ConfigFileFormatEnum] = [
|
|
489
|
+
ConfigFileFormatEnum.YAML,
|
|
490
|
+
ConfigFileFormatEnum.JSON,
|
|
491
|
+
ConfigFileFormatEnum.TOML,
|
|
492
|
+
],
|
|
493
|
+
) -> dict[str, Any]:
|
|
494
|
+
"""Asynchronous read all config files from directory or directories and merge them.
|
|
495
|
+
|
|
496
|
+
Args:
|
|
497
|
+
configs_dir (str | Path | list[str | Path], required): Configs directory or directories.
|
|
498
|
+
allowed_formats (list[ConfigFileFormatEnum] , optional): Allowed config file formats to read.
|
|
499
|
+
Defaults to [YAML, JSON, TOML].
|
|
500
|
+
|
|
501
|
+
Returns:
|
|
502
|
+
dict[str, Any]: Dictionary containing all merged config data from all files.
|
|
503
|
+
"""
|
|
504
|
+
|
|
505
|
+
_config_dict: dict[str, Any] = {}
|
|
506
|
+
|
|
507
|
+
if not isinstance(configs_dir, list):
|
|
508
|
+
configs_dir = [configs_dir]
|
|
509
|
+
|
|
510
|
+
_file_paths: list[str] = []
|
|
511
|
+
for _config_dir in configs_dir:
|
|
512
|
+
if isinstance(_config_dir, str):
|
|
513
|
+
_config_dir = Path(_config_dir)
|
|
514
|
+
|
|
515
|
+
if not os.path.isabs(_config_dir):
|
|
516
|
+
_current_dir = await aiofiles.os.getcwd()
|
|
517
|
+
_config_dir = os.path.join(_current_dir, _config_dir)
|
|
518
|
+
|
|
519
|
+
if await aiofiles.os.path.isdir(_config_dir):
|
|
520
|
+
if ConfigFileFormatEnum.YAML in allowed_formats:
|
|
521
|
+
_file_paths.extend(glob.glob(os.path.join(_config_dir, "*.yaml")))
|
|
522
|
+
_file_paths.extend(glob.glob(os.path.join(_config_dir, "*.yml")))
|
|
523
|
+
|
|
524
|
+
if ConfigFileFormatEnum.JSON in allowed_formats:
|
|
525
|
+
_file_paths.extend(glob.glob(os.path.join(_config_dir, "*.json")))
|
|
526
|
+
|
|
527
|
+
if ConfigFileFormatEnum.TOML in allowed_formats:
|
|
528
|
+
_file_paths.extend(glob.glob(os.path.join(_config_dir, "*.toml")))
|
|
529
|
+
|
|
530
|
+
if ConfigFileFormatEnum.INI in allowed_formats:
|
|
531
|
+
_file_paths.extend(glob.glob(os.path.join(_config_dir, "*.ini")))
|
|
532
|
+
_file_paths.extend(glob.glob(os.path.join(_config_dir, "*.cfg")))
|
|
533
|
+
|
|
534
|
+
_file_paths.sort()
|
|
535
|
+
for _file_path in _file_paths:
|
|
536
|
+
_config_data = await async_read_config_file(config_path=_file_path)
|
|
537
|
+
_config_dict = deep_merge(_config_dict, _config_data)
|
|
538
|
+
|
|
539
|
+
return _config_dict
|
|
540
|
+
|
|
541
|
+
|
|
279
542
|
__all__ = [
|
|
280
543
|
"async_create_dir",
|
|
281
544
|
"async_remove_dir",
|
|
@@ -283,4 +546,10 @@ __all__ = [
|
|
|
283
546
|
"async_remove_file",
|
|
284
547
|
"async_remove_files",
|
|
285
548
|
"async_get_file_checksum",
|
|
549
|
+
"async_read_yaml_file",
|
|
550
|
+
"async_read_json_file",
|
|
551
|
+
"async_read_toml_file",
|
|
552
|
+
"async_read_ini_file",
|
|
553
|
+
"async_read_config_file",
|
|
554
|
+
"async_read_all_configs",
|
|
286
555
|
]
|
potato_util/io/_sync.py
CHANGED
|
@@ -1,12 +1,30 @@
|
|
|
1
|
+
# noqa: E402
|
|
2
|
+
|
|
1
3
|
import os
|
|
4
|
+
import sys
|
|
5
|
+
import json
|
|
6
|
+
import glob
|
|
2
7
|
import errno
|
|
3
8
|
import shutil
|
|
4
9
|
import hashlib
|
|
5
10
|
import logging
|
|
11
|
+
import configparser
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
15
|
+
_binary_toml = False
|
|
16
|
+
if sys.version_info >= (3, 11):
|
|
17
|
+
import tomllib # type: ignore
|
|
6
18
|
|
|
19
|
+
_binary_toml = True
|
|
20
|
+
else:
|
|
21
|
+
import toml as tomllib # type: ignore
|
|
22
|
+
|
|
23
|
+
import yaml
|
|
7
24
|
from pydantic import validate_call
|
|
8
25
|
|
|
9
|
-
from ..
|
|
26
|
+
from .._base import deep_merge
|
|
27
|
+
from ..constants import WarnEnum, HashAlgoEnum, ConfigFileFormatEnum, MAX_PATH_LENGTH
|
|
10
28
|
|
|
11
29
|
|
|
12
30
|
logger = logging.getLogger(__name__)
|
|
@@ -270,6 +288,242 @@ def get_file_checksum(
|
|
|
270
288
|
return _file_checksum
|
|
271
289
|
|
|
272
290
|
|
|
291
|
+
@validate_call
|
|
292
|
+
def read_yaml_file(file_path: str | Path) -> dict[str, Any]:
|
|
293
|
+
"""Read YAML file.
|
|
294
|
+
|
|
295
|
+
Args:
|
|
296
|
+
file_path (str | Path, required): YAML file path.
|
|
297
|
+
|
|
298
|
+
Raises:
|
|
299
|
+
FileNotFoundError: If YAML file is not found.
|
|
300
|
+
Exception : If failed to read YAML file.
|
|
301
|
+
|
|
302
|
+
Returns:
|
|
303
|
+
dict[str, Any]: YAML file data as dictionary.
|
|
304
|
+
"""
|
|
305
|
+
|
|
306
|
+
_data: dict[str, Any] = {}
|
|
307
|
+
|
|
308
|
+
if isinstance(file_path, str):
|
|
309
|
+
file_path = Path(file_path)
|
|
310
|
+
|
|
311
|
+
if not os.path.isfile(file_path):
|
|
312
|
+
raise FileNotFoundError(f"Not found '{file_path}' YAML file!")
|
|
313
|
+
|
|
314
|
+
try:
|
|
315
|
+
with open(file_path, encoding="utf-8") as _file:
|
|
316
|
+
_data = yaml.safe_load(_file) or {}
|
|
317
|
+
except Exception:
|
|
318
|
+
logger.error(f"Failed to read '{file_path}' YAML file!")
|
|
319
|
+
raise
|
|
320
|
+
|
|
321
|
+
return _data
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
@validate_call
|
|
325
|
+
def read_json_file(file_path: str | Path) -> dict[str, Any]:
|
|
326
|
+
"""Read JSON file.
|
|
327
|
+
|
|
328
|
+
Args:
|
|
329
|
+
file_path (str | Path, required): JSON file path.
|
|
330
|
+
|
|
331
|
+
Raises:
|
|
332
|
+
FileNotFoundError: If JSON file is not found.
|
|
333
|
+
Exception : If failed to read JSON file.
|
|
334
|
+
|
|
335
|
+
Returns:
|
|
336
|
+
dict[str, Any]: JSON file data as dictionary.
|
|
337
|
+
"""
|
|
338
|
+
|
|
339
|
+
_data: dict[str, Any] = {}
|
|
340
|
+
|
|
341
|
+
if isinstance(file_path, str):
|
|
342
|
+
file_path = Path(file_path)
|
|
343
|
+
|
|
344
|
+
if not os.path.isfile(file_path):
|
|
345
|
+
raise FileNotFoundError(f"Not found '{file_path}' JSON file!")
|
|
346
|
+
|
|
347
|
+
try:
|
|
348
|
+
with open(file_path, encoding="utf-8") as _file:
|
|
349
|
+
_data = json.load(_file) or {}
|
|
350
|
+
except Exception:
|
|
351
|
+
logger.error(f"Failed to read '{file_path}' JSON file!")
|
|
352
|
+
raise
|
|
353
|
+
|
|
354
|
+
return _data
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
@validate_call
|
|
358
|
+
def read_toml_file(file_path: str | Path) -> dict[str, Any]:
|
|
359
|
+
"""Read TOML file.
|
|
360
|
+
|
|
361
|
+
Args:
|
|
362
|
+
file_path (str | Path, required): TOML file path.
|
|
363
|
+
|
|
364
|
+
Raises:
|
|
365
|
+
FileNotFoundError: If TOML file is not found.
|
|
366
|
+
Exception : If failed to read TOML file.
|
|
367
|
+
|
|
368
|
+
Returns:
|
|
369
|
+
dict[str, Any]: TOML file data as dictionary.
|
|
370
|
+
"""
|
|
371
|
+
|
|
372
|
+
_data: dict[str, Any] = {}
|
|
373
|
+
|
|
374
|
+
if isinstance(file_path, str):
|
|
375
|
+
file_path = Path(file_path)
|
|
376
|
+
|
|
377
|
+
if not os.path.isfile(file_path):
|
|
378
|
+
raise FileNotFoundError(f"Not found '{file_path}' TOML file!")
|
|
379
|
+
|
|
380
|
+
try:
|
|
381
|
+
if _binary_toml:
|
|
382
|
+
with open(file_path, "rb") as _file:
|
|
383
|
+
_data = tomllib.load(_file) or {} # type: ignore
|
|
384
|
+
else:
|
|
385
|
+
with open(file_path, encoding="utf-8") as _file:
|
|
386
|
+
_data = tomllib.load(_file) or {} # type: ignore
|
|
387
|
+
except Exception:
|
|
388
|
+
logger.error(f"Failed to read '{file_path}' TOML file!")
|
|
389
|
+
raise
|
|
390
|
+
|
|
391
|
+
return _data
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
@validate_call
|
|
395
|
+
def read_ini_file(file_path: str | Path) -> dict[str, Any]:
|
|
396
|
+
"""Read INI config file.
|
|
397
|
+
|
|
398
|
+
Args:
|
|
399
|
+
file_path (str | Path, required): INI config file path.
|
|
400
|
+
|
|
401
|
+
Raises:
|
|
402
|
+
FileNotFoundError: If INI config file is not found.
|
|
403
|
+
Exception : If failed to read INI config file.
|
|
404
|
+
|
|
405
|
+
Returns:
|
|
406
|
+
dict[str, Any]: INI config file data as dictionary.
|
|
407
|
+
"""
|
|
408
|
+
|
|
409
|
+
_config: dict[str, Any] = {}
|
|
410
|
+
|
|
411
|
+
if isinstance(file_path, str):
|
|
412
|
+
file_path = Path(file_path)
|
|
413
|
+
|
|
414
|
+
if not os.path.isfile(file_path):
|
|
415
|
+
raise FileNotFoundError(f"Not found '{file_path}' INI config file!")
|
|
416
|
+
|
|
417
|
+
try:
|
|
418
|
+
_config_parser = configparser.ConfigParser()
|
|
419
|
+
_config_parser.read(file_path)
|
|
420
|
+
for _section in _config_parser.sections():
|
|
421
|
+
_config[_section] = dict(_config_parser.items(_section))
|
|
422
|
+
|
|
423
|
+
except Exception:
|
|
424
|
+
logger.error(f"Failed to read '{file_path}' INI config file!")
|
|
425
|
+
raise
|
|
426
|
+
|
|
427
|
+
return _config
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
@validate_call
|
|
431
|
+
def read_config_file(config_path: str | Path) -> dict[str, Any]:
|
|
432
|
+
"""Read config file (YAML, JSON, TOML, INI).
|
|
433
|
+
|
|
434
|
+
Args:
|
|
435
|
+
config_path (str | Path, required): Config file path.
|
|
436
|
+
|
|
437
|
+
Raises:
|
|
438
|
+
FileNotFoundError: If config file is not found.
|
|
439
|
+
ValueError : If config file format is not supported.
|
|
440
|
+
|
|
441
|
+
Returns:
|
|
442
|
+
dict[str, Any]: Config file data as dictionary.
|
|
443
|
+
"""
|
|
444
|
+
|
|
445
|
+
_config: dict[str, Any] = {}
|
|
446
|
+
|
|
447
|
+
if isinstance(config_path, str):
|
|
448
|
+
config_path = Path(config_path)
|
|
449
|
+
|
|
450
|
+
if not os.path.isfile(config_path):
|
|
451
|
+
raise FileNotFoundError(f"Not found '{config_path}' config file!")
|
|
452
|
+
|
|
453
|
+
_suffix = config_path.suffix.lower()
|
|
454
|
+
if _suffix in (".yaml", ".yml"):
|
|
455
|
+
_config = read_yaml_file(file_path=config_path)
|
|
456
|
+
elif _suffix == ".json":
|
|
457
|
+
_config = read_json_file(file_path=config_path)
|
|
458
|
+
elif _suffix == ".toml":
|
|
459
|
+
_config = read_toml_file(file_path=config_path)
|
|
460
|
+
elif _suffix in (".ini", ".cfg"):
|
|
461
|
+
_config = read_ini_file(file_path=config_path)
|
|
462
|
+
else:
|
|
463
|
+
raise ValueError(
|
|
464
|
+
f"Unsupported config file format '{config_path.suffix}' for '{config_path}'!"
|
|
465
|
+
)
|
|
466
|
+
|
|
467
|
+
return _config
|
|
468
|
+
|
|
469
|
+
|
|
470
|
+
@validate_call
|
|
471
|
+
def read_all_configs(
|
|
472
|
+
configs_dir: str | Path | list[str | Path],
|
|
473
|
+
allowed_formats: list[ConfigFileFormatEnum] = [
|
|
474
|
+
ConfigFileFormatEnum.YAML,
|
|
475
|
+
ConfigFileFormatEnum.JSON,
|
|
476
|
+
ConfigFileFormatEnum.TOML,
|
|
477
|
+
],
|
|
478
|
+
) -> dict[str, Any]:
|
|
479
|
+
"""Read all config files from directory or directories and merge them.
|
|
480
|
+
|
|
481
|
+
Args:
|
|
482
|
+
configs_dir (str | Path | list[str | Path], required): Configs directory or directories.
|
|
483
|
+
allowed_formats (list[ConfigFileFormatEnum] , optional): Allowed config file formats to read.
|
|
484
|
+
Defaults to [YAML, JSON, TOML].
|
|
485
|
+
|
|
486
|
+
Returns:
|
|
487
|
+
dict[str, Any]: Dictionary containing all merged config data from all files.
|
|
488
|
+
"""
|
|
489
|
+
|
|
490
|
+
_config_dict: dict[str, Any] = {}
|
|
491
|
+
|
|
492
|
+
if not isinstance(configs_dir, list):
|
|
493
|
+
configs_dir = [configs_dir]
|
|
494
|
+
|
|
495
|
+
_file_paths: list[str] = []
|
|
496
|
+
for _config_dir in configs_dir:
|
|
497
|
+
if isinstance(_config_dir, str):
|
|
498
|
+
_config_dir = Path(_config_dir)
|
|
499
|
+
|
|
500
|
+
if not os.path.isabs(_config_dir):
|
|
501
|
+
_current_dir = os.getcwd()
|
|
502
|
+
_config_dir = os.path.join(_current_dir, _config_dir)
|
|
503
|
+
|
|
504
|
+
if os.path.isdir(_config_dir):
|
|
505
|
+
if ConfigFileFormatEnum.YAML in allowed_formats:
|
|
506
|
+
_file_paths.extend(glob.glob(os.path.join(_config_dir, "*.yaml")))
|
|
507
|
+
_file_paths.extend(glob.glob(os.path.join(_config_dir, "*.yml")))
|
|
508
|
+
|
|
509
|
+
if ConfigFileFormatEnum.JSON in allowed_formats:
|
|
510
|
+
_file_paths.extend(glob.glob(os.path.join(_config_dir, "*.json")))
|
|
511
|
+
|
|
512
|
+
if ConfigFileFormatEnum.TOML in allowed_formats:
|
|
513
|
+
_file_paths.extend(glob.glob(os.path.join(_config_dir, "*.toml")))
|
|
514
|
+
|
|
515
|
+
if ConfigFileFormatEnum.INI in allowed_formats:
|
|
516
|
+
_file_paths.extend(glob.glob(os.path.join(_config_dir, "*.ini")))
|
|
517
|
+
_file_paths.extend(glob.glob(os.path.join(_config_dir, "*.cfg")))
|
|
518
|
+
|
|
519
|
+
_file_paths.sort()
|
|
520
|
+
for _file_path in _file_paths:
|
|
521
|
+
_config_data = read_config_file(config_path=_file_path)
|
|
522
|
+
_config_dict = deep_merge(_config_dict, _config_data)
|
|
523
|
+
|
|
524
|
+
return _config_dict
|
|
525
|
+
|
|
526
|
+
|
|
273
527
|
__all__ = [
|
|
274
528
|
"create_dir",
|
|
275
529
|
"remove_dir",
|
|
@@ -277,4 +531,10 @@ __all__ = [
|
|
|
277
531
|
"remove_file",
|
|
278
532
|
"remove_files",
|
|
279
533
|
"get_file_checksum",
|
|
534
|
+
"read_yaml_file",
|
|
535
|
+
"read_json_file",
|
|
536
|
+
"read_toml_file",
|
|
537
|
+
"read_ini_file",
|
|
538
|
+
"read_config_file",
|
|
539
|
+
"read_all_configs",
|
|
280
540
|
]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: potato_util
|
|
3
|
-
Version: 0.1
|
|
3
|
+
Version: 0.2.1
|
|
4
4
|
Summary: 'potato_util' is collection of simple useful utils package for python.
|
|
5
5
|
Author-email: Batkhuu Byambajav <batkhuu10@gmail.com>
|
|
6
6
|
Project-URL: Homepage, https://github.com/bybatkhuu/module-python-utils
|
|
@@ -21,6 +21,8 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
21
21
|
Requires-Python: <4.0,>=3.10
|
|
22
22
|
Description-Content-Type: text/markdown
|
|
23
23
|
License-File: LICENSE.txt
|
|
24
|
+
Requires-Dist: toml>=0.10.2; python_version < "3.11"
|
|
25
|
+
Requires-Dist: PyYAML<7.0.0,>=6.0.2
|
|
24
26
|
Requires-Dist: python-dotenv<2.0.0,>=1.0.1
|
|
25
27
|
Requires-Dist: pydantic[email,timezone]<3.0.0,>=2.0.3
|
|
26
28
|
Requires-Dist: pydantic-settings<3.0.0,>=2.2.1
|
|
@@ -53,7 +55,7 @@ Provides-Extra: docs
|
|
|
53
55
|
Requires-Dist: pylint<5.0.0,>=3.0.4; extra == "docs"
|
|
54
56
|
Requires-Dist: mkdocs-material<10.0.0,>=9.5.50; extra == "docs"
|
|
55
57
|
Requires-Dist: mkdocs-awesome-nav<4.0.0,>=3.0.0; extra == "docs"
|
|
56
|
-
Requires-Dist: mkdocstrings[python]<
|
|
58
|
+
Requires-Dist: mkdocstrings[python]<2.0.0,>=0.24.3; extra == "docs"
|
|
57
59
|
Requires-Dist: mike<3.0.0,>=2.1.3; extra == "docs"
|
|
58
60
|
Provides-Extra: dev
|
|
59
61
|
Requires-Dist: aiofiles<26.0.0,>=24.1.0; extra == "dev"
|
|
@@ -71,7 +73,7 @@ Requires-Dist: twine<7.0.0,>=6.0.1; extra == "dev"
|
|
|
71
73
|
Requires-Dist: pylint<5.0.0,>=3.0.4; extra == "dev"
|
|
72
74
|
Requires-Dist: mkdocs-material<10.0.0,>=9.5.50; extra == "dev"
|
|
73
75
|
Requires-Dist: mkdocs-awesome-nav<4.0.0,>=3.0.0; extra == "dev"
|
|
74
|
-
Requires-Dist: mkdocstrings[python]<
|
|
76
|
+
Requires-Dist: mkdocstrings[python]<2.0.0,>=0.24.3; extra == "dev"
|
|
75
77
|
Requires-Dist: mike<3.0.0,>=2.1.3; extra == "dev"
|
|
76
78
|
Requires-Dist: pyright<2.0.0,>=1.1.392; extra == "dev"
|
|
77
79
|
Requires-Dist: pre-commit<5.0.0,>=4.0.1; extra == "dev"
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
potato_util/__init__.py,sha256=xl4th2Z_OmTk-3aO1w05Vh8ob0BnX4RcY1fT9tGX61c,74
|
|
2
|
-
potato_util/__version__.py,sha256=
|
|
2
|
+
potato_util/__version__.py,sha256=HfjVOrpTnmZ-xVFCYSVmX50EXaBQeJteUHG-PD6iQs8,22
|
|
3
3
|
potato_util/_base.py,sha256=oKLzp_NheI7kQAKC5xAm77OI_UmBXRuEEpulZTVBg5w,2549
|
|
4
4
|
potato_util/dt.py,sha256=DatLzuXOxdanplBv-Kf3qOaomrTCUUBSYv_QgTtQB6U,7635
|
|
5
5
|
potato_util/generator.py,sha256=vdvHj1UrCOQI2WXQHQ2H63PMLQ6yOXAZPNl0m-6BgtY,1572
|
|
@@ -8,7 +8,7 @@ potato_util/secure.py,sha256=HxlUGyJl15qiL5WFrTZOJLgBZzuEr3Eu--qvBPmj2So,848
|
|
|
8
8
|
potato_util/validator.py,sha256=TANw7uWHi0PANPkrNDOFFvRcYL9Ge75b4xGSf3PTW1A,4083
|
|
9
9
|
potato_util/constants/__init__.py,sha256=lhxZ2k-QotMR_w9w-Gv-9pCEFrQSKxe1YEIvV83pf8E,80
|
|
10
10
|
potato_util/constants/_base.py,sha256=J1i611erzcrPRZ6USkVgnNZ_IvvN9YLbcltvyIngpcg,61
|
|
11
|
-
potato_util/constants/_enum.py,sha256=
|
|
11
|
+
potato_util/constants/_enum.py,sha256=RYg5MBxeRe2-oZ9CsqvFhDRIvB3Wi4tm-Wa4MLLfVy4,654
|
|
12
12
|
potato_util/constants/_regex.py,sha256=T7SSsqD0y8jXJJPURwAK2uA59AjLt1uqGfqqUd1a22o,710
|
|
13
13
|
potato_util/http/__init__.py,sha256=qWOzScMVJEHjzClEqOYmGtfkZyU8f8Hz8nT054Dbblo,229
|
|
14
14
|
potato_util/http/_async.py,sha256=dfs-ynchPKdnMNQXqyoqhcz-jhtQ7g_4JnNfVr3bXk0,1178
|
|
@@ -16,10 +16,10 @@ potato_util/http/_base.py,sha256=qoW5U8gxihVMx3v1gSLLmptMjScVsDS9PowvJ6tTc9c,143
|
|
|
16
16
|
potato_util/http/_sync.py,sha256=i8oyRmh-WraOzotaUbssIjOPvBh1pvLPupdoCvyHTK0,1169
|
|
17
17
|
potato_util/http/fastapi.py,sha256=iqCxZcQDIVtcqCN9dZ4itcoUs6KzsfRGgK7sRd5EmOA,675
|
|
18
18
|
potato_util/io/__init__.py,sha256=FoGcN7t0uArQV4hbMR1X5aeC8Yq-h_ds4xooNpkmgG0,209
|
|
19
|
-
potato_util/io/_async.py,sha256=
|
|
20
|
-
potato_util/io/_sync.py,sha256=
|
|
21
|
-
potato_util-0.1.
|
|
22
|
-
potato_util-0.1.
|
|
23
|
-
potato_util-0.1.
|
|
24
|
-
potato_util-0.1.
|
|
25
|
-
potato_util-0.1.
|
|
19
|
+
potato_util/io/_async.py,sha256=mFkLrqXreCRJcvWEysS2Tvmb0XEDQeAqnnt1b1NbYh0,18209
|
|
20
|
+
potato_util/io/_sync.py,sha256=Zdvh-2_XeLriI4XKkgsRn9TvI8I3KjQItmIF6w4aNyk,17098
|
|
21
|
+
potato_util-0.2.1.dist-info/licenses/LICENSE.txt,sha256=CUTK-r0BWIg1r0bBiemAcMhakgV0N7HuRhw6rQ-A9A4,1074
|
|
22
|
+
potato_util-0.2.1.dist-info/METADATA,sha256=dPFt_2g3qw7T5cegDm8ZePnoaaM0OT6cHHLAS0Fzzrk,15725
|
|
23
|
+
potato_util-0.2.1.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
24
|
+
potato_util-0.2.1.dist-info/top_level.txt,sha256=pLMnSfT6rhlYBpo2Gnd8kKMDxcuvxdVizqsv1dd1frw,12
|
|
25
|
+
potato_util-0.2.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|