Coverage for src / c41811 / config / basic / core.py: 100%
357 statements
« prev ^ index » next coverage.py v7.13.3, created at 2026-02-06 06:04 +0000
« prev ^ index » next coverage.py v7.13.3, created at 2026-02-06 06:04 +0000
1# cython: language_level = 3 # noqa: ERA001
4"""
5主要中间层
7.. versionadded:: 0.2.0
8"""
10from abc import ABC
11from collections import OrderedDict
12from collections.abc import Callable
13from collections.abc import Iterable
14from collections.abc import Iterator
15from collections.abc import Mapping
16from collections.abc import Sequence
17from contextlib import suppress
18from copy import deepcopy
19from re import Pattern
20from typing import Any
21from typing import Literal
22from typing import Self
23from typing import cast
24from typing import overload
25from typing import override
27from .factory import ConfigDataFactory
28from .utils import check_read_only
29from .utils import fmt_path
30from .._protocols import Indexed
31from ..abc import ABCConfigData
32from ..abc import ABCConfigFile
33from ..abc import ABCConfigPool
34from ..abc import ABCIndexedConfigData
35from ..abc import ABCPath
36from ..abc import ABCProcessorHelper
37from ..abc import ABCSLProcessorPool
38from ..abc import AnyKey
39from ..abc import PathLike
40from ..errors import ConfigDataReadOnlyError
41from ..errors import ConfigDataTypeError
42from ..errors import ConfigOperate
43from ..errors import FailedProcessConfigFileError
44from ..errors import KeyInfo
45from ..errors import RequiredPathNotFoundError
46from ..errors import UnsupportedConfigFormatError
49class BasicConfigData[D](ABCConfigData, ABC):
50 # noinspection GrazieInspection
51 """
52 配置数据基类
54 .. versionadded:: 0.1.5
56 .. versionchanged:: 0.2.0
57 重命名 ``BaseConfigData`` 为 ``BasicConfigData``
58 """
60 _read_only: bool | None = False
62 @property
63 @override
64 def data_read_only(self) -> bool | None:
65 return True # 全被子类复写了 测不到 # pragma: no cover
67 @property # type: ignore[explicit-override] # mypy抽风
68 @override
69 def read_only(self) -> bool | None:
70 return super().read_only or self._read_only
72 @read_only.setter
73 @override
74 def read_only(self, value: Any) -> None:
75 if self.data_read_only:
76 raise ConfigDataReadOnlyError
77 self._read_only = bool(value)
80class BasicSingleConfigData[D](BasicConfigData[D], ABC):
81 """
82 单文件配置数据基类
84 .. versionadded:: 0.2.0
85 """
87 def __init__(self, data: D):
88 """
89 :param data: 配置的原始数据
90 :type data: Any
91 """ # noqa: D205
92 self._data: D = deepcopy(data)
94 @property
95 def data(self) -> D:
96 """配置的原始数据*快照*"""
97 return deepcopy(self._data)
99 @override
100 def __eq__(self, other: Any) -> bool:
101 if not isinstance(other, type(self)):
102 return NotImplemented
103 return self._data == other._data
105 __hash__ = None # type: ignore[assignment]
107 def __bool__(self) -> bool:
108 return bool(self._data)
110 @override
111 def __str__(self) -> str:
112 return str(self._data)
114 @override
115 def __repr__(self) -> str:
116 return f"{self.__class__.__name__}({self._data!r})"
118 def __deepcopy__(self, memo: dict[str, Any]) -> Self:
119 return self.from_data(self._data)
122class BasicIndexedConfigData[D: Indexed[Any, Any]](BasicSingleConfigData[D], ABCIndexedConfigData[D], ABC):
123 # noinspection GrazieInspection
124 """
125 支持 ``索引`` 操作的配置数据基类
127 .. versionadded:: 0.1.5
129 .. versionchanged:: 0.2.0
130 重命名 ``BaseSupportsIndexConfigData`` 为 ``BasicIndexedConfigData``
131 """
133 def _process_path[X, Y](
134 self,
135 path: ABCPath[Any],
136 path_checker: Callable[[Any, AnyKey, ABCPath[Any], int], X],
137 process_return: Callable[[Any], Y],
138 ) -> X | Y:
139 # noinspection GrazieInspection
140 """
141 处理键路径的通用函数
143 :param path: 键路径
144 :type path: ABCPath
145 :param path_checker: 检查并处理每个路径段,返回值非None时结束操作并返回值
146 :type path_checker:
147 Callable[(current_data: Any, current_key: ABCKey, last_path: ABCPath, path_index: int), X]
148 :param process_return: 处理最终结果,该函数返回值会被直接返回
149 :type process_return: Callable[(current_data: Any), Y]
151 :return: 处理结果
152 :rtype: X | Y
154 .. versionchanged:: 0.2.0
155 重命名参数 ``process_check`` 为 ``path_checker``
156 """ # noqa: RUF002
157 current_data = self._data
159 for key_index, current_key in enumerate(path):
160 last_path: ABCPath[Any] = path[key_index + 1 :]
162 check_result = path_checker(current_data, current_key, last_path, key_index)
163 if check_result is not None:
164 return check_result
166 current_data = current_key.__get_inner_element__(current_data)
168 return process_return(current_data)
170 @override
171 def retrieve(self, path: PathLike, *, return_raw_value: bool = False) -> Any:
172 path = fmt_path(path)
174 def checker(current_data: Any, current_key: AnyKey, _last_path: ABCPath[Any], key_index: int) -> None:
175 missing_protocol = current_key.__supports__(current_data)
176 if missing_protocol:
177 raise ConfigDataTypeError(
178 KeyInfo(cast(ABCPath[Any], path), current_key, key_index), missing_protocol, type(current_data)
179 )
180 if not current_key.__contains_inner_element__(current_data):
181 raise RequiredPathNotFoundError(
182 KeyInfo(cast(ABCPath[Any], path), current_key, key_index), ConfigOperate.Read
183 )
185 def process_return[V: Any](current_data: V) -> V | ABCConfigData:
186 if return_raw_value:
187 return deepcopy(current_data)
189 is_sequence = isinstance(current_data, Sequence) and not isinstance(current_data, str | bytes)
190 if isinstance(current_data, Mapping) or is_sequence:
191 return ConfigDataFactory(current_data) # type: ignore[return-value]
193 return deepcopy(current_data)
195 return self._process_path(path, checker, process_return)
197 @override
198 @check_read_only
199 def modify(self, path: PathLike, value: Any, *, allow_create: bool = True) -> Self:
200 path = fmt_path(path)
202 def checker(current_data: Any, current_key: AnyKey, last_path: ABCPath[Any], key_index: int) -> None:
203 missing_protocol = current_key.__supports_modify__(current_data)
204 if missing_protocol:
205 raise ConfigDataTypeError(
206 KeyInfo(cast(ABCPath[Any], path), current_key, key_index), missing_protocol, type(current_data)
207 )
208 if not current_key.__contains_inner_element__(current_data):
209 if not allow_create:
210 raise RequiredPathNotFoundError(
211 KeyInfo(cast(ABCPath[Any], path), current_key, key_index), ConfigOperate.Write
212 )
213 current_key.__set_inner_element__(current_data, type(self._data)())
215 if not last_path:
216 current_key.__set_inner_element__(current_data, value)
218 self._process_path(path, checker, lambda *_: None)
219 return self
221 @override
222 @check_read_only
223 def delete(self, path: PathLike) -> Self:
224 path = fmt_path(path)
226 def checker(
227 current_data: Any,
228 current_key: AnyKey,
229 last_path: ABCPath[Any],
230 key_index: int,
231 ) -> Literal[True] | None:
232 missing_protocol = current_key.__supports_modify__(current_data)
233 if missing_protocol:
234 raise ConfigDataTypeError(
235 KeyInfo(cast(ABCPath[Any], path), current_key, key_index), missing_protocol, type(current_data)
236 )
237 if not current_key.__contains_inner_element__(current_data):
238 raise RequiredPathNotFoundError(
239 KeyInfo(cast(ABCPath[Any], path), current_key, key_index), ConfigOperate.Delete
240 )
242 if not last_path:
243 current_key.__delete_inner_element__(current_data)
244 return True
245 return None # 被mypy强制要求
247 self._process_path(path, checker, lambda *_: None)
248 return self
250 @override
251 def unset(self, path: PathLike) -> Self:
252 with suppress(RequiredPathNotFoundError):
253 self.delete(path)
254 return self
256 @override
257 def exists(self, path: PathLike, *, ignore_wrong_type: bool = False) -> bool:
258 path = fmt_path(path)
260 def checker(current_data: Any, current_key: AnyKey, _last_path: ABCPath[Any], key_index: int) -> bool | None:
261 missing_protocol = current_key.__supports__(current_data)
262 if missing_protocol:
263 if ignore_wrong_type:
264 return False
265 raise ConfigDataTypeError(
266 KeyInfo(cast(ABCPath[Any], path), current_key, key_index), missing_protocol, type(current_data)
267 )
268 if not current_key.__contains_inner_element__(current_data):
269 return False
270 return None
272 return cast(bool, self._process_path(path, checker, lambda *_: True))
274 @override
275 def get[V](self, path: PathLike, default: V | None = None, *, return_raw_value: bool = False) -> V | Any:
276 try:
277 return self.retrieve(path, return_raw_value=return_raw_value)
278 except RequiredPathNotFoundError:
279 return default
281 @override
282 def setdefault[V](self, path: PathLike, default: V | None = None, *, return_raw_value: bool = False) -> V | Any:
283 try:
284 return self.retrieve(path)
285 except RequiredPathNotFoundError:
286 self.modify(path, default)
287 return default
289 @override
290 def __contains__(self, key: Any) -> bool:
291 return key in self._data # type: ignore[operator]
293 @override
294 def __iter__(self) -> Iterator[D]:
295 return iter(self._data)
297 @override
298 def __len__(self) -> int:
299 return len(self._data) # type: ignore[arg-type]
301 @override
302 def __getitem__(self, index: Any) -> Any:
303 data = self._data[index]
304 is_sequence = isinstance(data, Sequence) and not isinstance(data, str | bytes)
305 if isinstance(data, Mapping) or is_sequence:
306 return cast(Self, ConfigDataFactory(data))
307 return cast(D, deepcopy(data))
309 @override
310 def __setitem__(self, index: Any, value: Any) -> None:
311 self._data[index] = value # type: ignore[index]
313 @override
314 def __delitem__(self, index: Any) -> None:
315 del self._data[index] # type: ignore[attr-defined]
318class ConfigFile[D: ABCConfigData](ABCConfigFile[D]):
319 """配置文件类"""
321 def __init__(self, initial_config: D | Any, *, config_format: str | None = None):
322 """
323 :param initial_config: 配置数据
324 :type initial_config: D
325 :param config_format: 配置文件的格式
326 :type config_format: str | None
328 .. caution::
329 本身并未对 ``initial_config`` 参数进行深拷贝,但是 :py:class:`ConfigDataFactory` 分发的类可能会将其深拷贝
331 .. versionchanged:: 0.2.0
332 现在会自动尝试使用 :py:class:`ConfigDataFactory` 转换 ``initial_config`` 参数
334 重命名参数 ``config_data`` 为 ``initial_config``
335 """ # noqa: RUF002, D205
336 super().__init__(cast(D, ConfigDataFactory(initial_config)), config_format=config_format)
338 @override
339 def save(
340 self,
341 processor_pool: ABCSLProcessorPool,
342 namespace: str,
343 file_name: str,
344 config_format: str | None = None,
345 *processor_args: Any,
346 **processor_kwargs: Any,
347 ) -> None:
348 if config_format is None:
349 config_format = self._config_format
351 if config_format not in processor_pool.SLProcessors:
352 raise UnsupportedConfigFormatError(config_format)
354 return processor_pool.SLProcessors[config_format].save(
355 processor_pool, self, processor_pool.root_path, namespace, file_name, *processor_args, **processor_kwargs
356 )
358 @classmethod
359 @override
360 def load(
361 cls,
362 processor_pool: ABCSLProcessorPool,
363 namespace: str,
364 file_name: str,
365 config_format: str,
366 *processor_args: Any,
367 **processor_kwargs: Any,
368 ) -> Self:
369 if config_format not in processor_pool.SLProcessors:
370 raise UnsupportedConfigFormatError(config_format)
372 return cast(
373 Self,
374 processor_pool.SLProcessors[config_format].load(
375 processor_pool, processor_pool.root_path, namespace, file_name, *processor_args, **processor_kwargs
376 ),
377 )
379 @classmethod
380 @override
381 def initialize(
382 cls,
383 processor_pool: ABCSLProcessorPool,
384 namespace: str,
385 file_name: str,
386 config_format: str,
387 *processor_args: Any,
388 **processor_kwargs: Any,
389 ) -> Self:
390 if config_format not in processor_pool.SLProcessors:
391 raise UnsupportedConfigFormatError(config_format)
393 return cast(
394 Self,
395 processor_pool.SLProcessors[config_format].initialize(
396 processor_pool, processor_pool.root_path, namespace, file_name, *processor_args, **processor_kwargs
397 ),
398 )
401class PHelper(ABCProcessorHelper):
402 """处理器助手类"""
405class BasicConfigPool(ABCConfigPool, ABC):
406 """
407 基础配置池类
409 实现了一些通用方法
411 .. versionchanged:: 0.2.0
412 重命名 ``BaseConfigPool`` 为 ``BasicConfigPool``
413 """
415 def __init__(self, root_path: str = "./.config"):
416 """
417 :param root_path: 配置根路径
418 :type root_path: str
419 """ # noqa: D205
420 super().__init__(root_path)
421 self._configs: dict[str, dict[str, ABCConfigFile[Any]]] = {}
422 self._helper = PHelper()
424 @property
425 @override
426 def helper(self) -> ABCProcessorHelper:
427 return self._helper
429 # noinspection PyMethodOverriding
430 @overload # 咱也不知道为什么mypy只有这样检查会通过而pycharm会报错
431 def get(self, namespace: str) -> dict[str, ABCConfigFile[Any]] | None: ...
433 # noinspection PyMethodOverriding
434 @overload
435 def get(self, namespace: str, file_name: str) -> ABCConfigFile[Any] | None: ...
437 @overload
438 def get(
439 self,
440 namespace: str,
441 file_name: str | None = None,
442 ) -> dict[str, ABCConfigFile[Any]] | ABCConfigFile[Any] | None: ...
444 @override
445 def get(
446 self,
447 namespace: str,
448 file_name: str | None = None,
449 ) -> dict[str, ABCConfigFile[Any]] | ABCConfigFile[Any] | None:
450 if namespace not in self._configs:
451 return None
452 result = self._configs[namespace]
454 if file_name is None:
455 return result
457 if file_name in result:
458 return result[file_name]
460 return None
462 @override
463 def set(self, namespace: str, file_name: str, config: ABCConfigFile[Any]) -> Self:
464 if namespace not in self._configs:
465 self._configs[namespace] = {}
467 self._configs[namespace][file_name] = config
468 return self
470 def _get_formats(
471 self,
472 file_name: str,
473 config_formats: str | Iterable[str] | None,
474 configfile_format: str | None = None,
475 ) -> Iterable[str]:
476 """
477 从给定参数计算所有可能的配置格式
479 .. attention::
480 返回所有可能的配置格式,不会检查配置格式是否存在!
481 可迭代对象的产生顺序即为配置格式优先级,优先级逻辑见下表
483 :param file_name: 文件名
484 :type file_name: str
485 :param config_formats: 配置格式
486 :type config_formats: str | Iterable[str] | None
487 :param configfile_format:
488 该配置文件对象本身配置格式属性的值
489 可选项,一般在保存时填入
490 用于在没手动指定配置格式且没文件后缀时使用该值进行尝试
492 .. seealso::
493 :py:attr:`ABCConfigFile.config_format`
495 :return: 配置格式
496 :rtype: Iterable[str]
498 :raise UnsupportedConfigFormatError: 不支持的配置格式
500 格式计算优先级
501 --------------
503 1.config_formats的bool求值为真
505 2.文件名注册了对应的SL处理器
507 3.configfile_format非None
509 .. versionadded:: 0.2.0
510 """ # noqa: RUF002
511 result_formats = []
512 # 先尝试从传入的参数中获取配置文件格式
513 if config_formats is None:
514 config_formats = []
515 elif isinstance(config_formats, str):
516 config_formats = [config_formats]
517 else:
518 config_formats = list(config_formats)
519 result_formats.extend(config_formats)
521 def _check_file_name(match: str | Pattern[str]) -> bool:
522 if isinstance(match, str):
523 return file_name.endswith(match)
524 return bool(match.fullmatch(file_name)) # 目前没SL处理器用得上 # pragma: no cover
526 # 再尝试从文件名匹配配置文件格式
527 for m in self.FileNameProcessors:
528 if _check_file_name(m):
529 result_formats.extend(self.FileNameProcessors[m])
531 # 最后尝试从配置文件对象本身获取配置文件格式
532 if configfile_format is not None:
533 result_formats.append(configfile_format)
535 if not result_formats:
536 raise UnsupportedConfigFormatError(None)
538 return OrderedDict.fromkeys(result_formats)
540 def _try_sl_processors[R](
541 self,
542 namespace: str,
543 file_name: str,
544 config_formats: str | Iterable[str] | None,
545 processor: Callable[[Self, str, str, str], R],
546 file_config_format: str | None = None,
547 ) -> R:
548 """
549 自动尝试推断ABCConfigFile所支持的config_format
551 :param namespace: 命名空间
552 :type namespace: str
553 :param file_name: 文件名
554 :type file_name: str
555 :param config_formats: 配置格式
556 :type config_formats: str | Iterable[str] | None
557 :param processor:
558 处理器,参数为[配置池对象, 命名空间, 文件名, 配置格式]返回值会被直接返回,
559 出现意料内的SL处理器无法处理需抛出FailedProcessConfigFileError以允许继续尝试别的SL处理器
560 :type processor: Callable[[Self, str, str, str], R]
561 :param file_config_format:
562 该配置文件对象本身配置格式属性的值
563 可选项,一般在保存时填入
564 用于在没手动指定配置格式且没文件后缀时使用该值进行尝试
566 .. seealso::
567 :py:attr:`ABCConfigFile.config_format`
569 :return: 处理器返回值
570 :rtype: R
572 :raise UnsupportedConfigFormatError: 不支持的配置格式
573 :raise FailedProcessConfigFileError: 处理配置文件失败
575 .. seealso::
576 格式计算优先级
578 :py:meth:`_get_formats`
580 .. versionadded:: 0.1.2
582 .. versionchanged:: 0.2.0
583 拆分格式计算到方法 :py:meth:`_get_formats`
584 """ # noqa: RUF002
586 def callback_wrapper(cfg_fmt: str) -> R:
587 return processor(self, namespace, file_name, cfg_fmt)
589 # 尝试从多个SL加载器中找到能正确加载的那一个
590 errors: dict[str, FailedProcessConfigFileError[Any] | UnsupportedConfigFormatError] = {}
591 for config_format in self._get_formats(file_name, config_formats, file_config_format):
592 if config_format not in self.SLProcessors:
593 errors[config_format] = UnsupportedConfigFormatError(config_format)
594 continue
595 try:
596 # 能正常运行直接返回结果不再进行尝试
597 return callback_wrapper(config_format)
598 except FailedProcessConfigFileError as err:
599 errors[config_format] = err
601 for error in errors.values():
602 if isinstance(error, UnsupportedConfigFormatError):
603 raise error from None
605 # 如果没有一个SL加载器能正确加载则抛出异常
606 raise FailedProcessConfigFileError(errors)
608 @override
609 def save(
610 self,
611 namespace: str,
612 file_name: str,
613 config_formats: str | Iterable[str] | None = None,
614 config: ABCConfigFile[Any] | None = None,
615 *args: Any,
616 **kwargs: Any,
617 ) -> Self:
618 if config is not None:
619 self.set(namespace, file_name, config)
621 file = self._configs[namespace][file_name]
623 def processor(pool: Self, ns: str, fn: str, cf: str) -> None:
624 file.save(pool, ns, fn, cf, *args, **kwargs)
626 self._try_sl_processors(namespace, file_name, config_formats, processor, file_config_format=file.config_format)
627 return self
629 @override
630 def save_all(
631 self, *, ignore_err: bool = False
632 ) -> dict[str, dict[str, tuple[ABCConfigFile[Any], Exception]]] | None:
633 errors: dict[str, dict[str, tuple[ABCConfigFile[Any], Exception]]] = {}
634 for namespace, configs in deepcopy(self._configs).items():
635 errors[namespace] = {}
636 for file_name, config in configs.items():
637 try:
638 self.save(namespace, file_name)
639 except Exception as err:
640 if not ignore_err:
641 raise
642 errors[namespace][file_name] = (config, err)
644 if not ignore_err:
645 return None
647 return {k: v for k, v in errors.items() if v}
649 @override
650 def initialize(
651 self,
652 namespace: str,
653 file_name: str,
654 *args: Any,
655 config_formats: str | Iterable[str] | None = None,
656 **kwargs: Any,
657 ) -> ABCConfigFile[Any]:
658 def processor(pool: Self, ns: str, fn: str, cf: str) -> ABCConfigFile[Any]:
659 config_file_cls: type[ABCConfigFile[Any]] = self.SLProcessors[cf].supported_file_classes[0]
660 result = config_file_cls.initialize(pool, ns, fn, cf, *args, **kwargs)
662 pool.set(namespace, file_name, result)
663 return result
665 return self._try_sl_processors(namespace, file_name, config_formats, processor)
667 @override
668 def load(
669 self,
670 namespace: str,
671 file_name: str,
672 *args: Any,
673 config_formats: str | Iterable[str] | None = None,
674 allow_initialize: bool = False,
675 **kwargs: Any,
676 ) -> ABCConfigFile[Any]:
677 """
678 加载配置到指定命名空间并返回
680 :param namespace: 命名空间
681 :type namespace: str
682 :param file_name: 文件名
683 :type file_name: str
684 :param config_formats: 配置格式
685 :type config_formats: str | Iterable[str] | None
686 :param allow_initialize: 是否允许初始化配置文件
687 :type allow_initialize: bool
689 :return: 配置对象
690 :rtype: ABCConfigFile
692 .. versionchanged:: 0.2.0
693 现在会像 :py:meth:`save` 一样接收并传递额外参数
695 删除参数 ``config_file_cls``
697 重命名参数 ``allow_create`` 为 ``allow_initialize``
699 现在由 :py:meth:`ABCConfigFile.initialize` 创建新的空 :py:class:`ABCConfigFile` 对象
700 """
701 cache = self.get(namespace, file_name)
702 if cache is not None:
703 return cache
705 def processor(pool: Self, ns: str, fn: str, cf: str) -> ABCConfigFile[Any]:
706 config_file_cls = self.SLProcessors[cf].supported_file_classes[0]
707 try:
708 result = config_file_cls.load(pool, ns, fn, cf, *args, **kwargs)
709 except FileNotFoundError:
710 if not allow_initialize:
711 raise
712 result = pool.initialize(ns, fn, *args, config_formats=cf, **kwargs)
714 pool.set(namespace, file_name, result)
715 return result
717 return self._try_sl_processors(namespace, file_name, config_formats, processor)
719 @override
720 def remove(self, namespace: str, file_name: str | None = None) -> Self:
721 if file_name is None:
722 del self._configs[namespace]
723 return self
725 del self._configs[namespace][file_name]
726 if not self._configs[namespace]:
727 del self._configs[namespace]
728 return self
730 @override
731 def discard(self, namespace: str, file_name: str | None = None) -> Self:
732 with suppress(KeyError):
733 self.remove(namespace, file_name)
734 return self
736 def __getitem__(self, item: str | tuple[str, str]) -> dict[str, ABCConfigFile[Any]] | ABCConfigFile[Any]:
737 if isinstance(item, tuple):
738 if len(item) != 2:
739 msg = f"item must be a tuple of length 2, got {item}"
740 raise ValueError(msg)
741 return deepcopy(self.configs[item[0]][item[1]])
742 return deepcopy(self.configs[item])
744 def __contains__(self, item: Any) -> bool:
745 """.. versionadded:: 0.1.2"""
746 if isinstance(item, str):
747 return item in self._configs
748 if isinstance(item, Iterable):
749 item = tuple(item)
750 if len(item) == 1:
751 return item[0] in self._configs
752 if len(item) != 2:
753 msg = f"item must be a tuple of length 2, got {item}"
754 raise ValueError(msg)
755 return (item[0] in self._configs) and (item[1] in self._configs[item[0]])
757 def __len__(self) -> int:
758 """配置文件总数"""
759 return sum(len(v) for v in self._configs.values())
761 @property
762 def configs(self) -> dict[str, dict[str, ABCConfigFile[Any]]]:
763 """配置文件字典"""
764 return deepcopy(self._configs)
766 @override
767 def __repr__(self) -> str:
768 return f"{self.__class__.__name__}({self.configs!r})"
771__all__ = (
772 "BasicConfigData",
773 "BasicConfigPool",
774 "BasicIndexedConfigData",
775 "BasicSingleConfigData",
776 "ConfigFile",
777 "PHelper",
778)