diff --git a/CHANGELOG.md b/CHANGELOG.md index 842ef46..3f5914f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,31 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [3.2.1] - 2024-04-29 + +### Changed + +- Stubber now has an option to only "export" the resulting "Final" stub + class and keeping all the other stubs "private" and this option is True by + default (they don't really need to be public) +- You can now give the resulting "Final" config stub a custom name +- You can also give the "Final" config stub an empty string for a name which + will simply omit generating it (for custom/complex composition of stubs + later on if people want) +- All stubs now also inherit from `dict` because any attribute in + `BaseConfig` that's a Map (and thus has a stub class) will also behave as + a `dict` (even if `Empty`) + +### Fixed + +- Stubber now appends an underscore to stubs with Python reserved keywords + like `class` and `def` etc. +- The `BaseConfig` object now also spots attribute fetching of those + keywords with an appended underscore and fetches the correct attribute + nevertheless. + + ## [3.2.0] - 2024-04-22 ### Added diff --git a/alviss/__init__.py b/alviss/__init__.py index 0831f99..fedf30a 100644 --- a/alviss/__init__.py +++ b/alviss/__init__.py @@ -1,4 +1,4 @@ -__version__ = '3.2.0' +__version__ = '3.2.1' __author__ = 'Thordur Matthiasson ' __license__ = 'MIT License' diff --git a/alviss/cli/stubber/main.py b/alviss/cli/stubber/main.py index bbb9054..5463691 100644 --- a/alviss/cli/stubber/main.py +++ b/alviss/cli/stubber/main.py @@ -14,6 +14,11 @@ def main(): parser.add_argument('-o', '--output', help='File to write the generated stub code to (otherwise its just printed to stdout)', default='', nargs='?') parser.add_argument('-f', '--force-overwrite', help='Overwrite existing output file if it exists', action='store_true') + parser.add_argument('-n', '--class-name', + help='The name of the resulting "final" stub class generated when outputting to a file (default is "AlvissConfigStub"). Set to "None" to skip generating the "final" class.', + default='AlvissConfigStub') + parser.add_argument('-x', '--export-all', help='Make all stub class names public and export via __all__ when outputting to a file.', + action='store_true') loudness_group = parser.add_mutually_exclusive_group() loudness_group.add_argument('-s', '--silent', action='store_true', @@ -37,12 +42,16 @@ def main(): stubber.SimpleStubMaker().render_stub_classes_to_file(input_file=args.file, output_file=args.output, - overwrite_existing=args.force_overwrite) + overwrite_existing=args.force_overwrite, + is_private=not args.export_all) else: if not args.silent: print(f'Printing results:') print(f'==================================================') - print(stubber.SimpleStubMaker().render_stub_classes_from_descriptor_file(args.file)) + cls_name = 'AlvissConfigStub' if args.class_name is None else args.class_name + if cls_name.lower().strip() == 'none': + cls_name = '' + print(stubber.SimpleStubMaker().render_stub_classes_from_descriptor_file(args.file, class_name=cls_name, is_private=not args.export_all)) if not args.silent: print(f'==================================================') diff --git a/alviss/structs/baseconfig.py b/alviss/structs/baseconfig.py index 6bf212d..d7c156b 100644 --- a/alviss/structs/baseconfig.py +++ b/alviss/structs/baseconfig.py @@ -6,6 +6,36 @@ from ccptools.tpu import string import json import yaml +import collections +from alviss.utils import * + + +class _KwSafeEmptyDict(EmptyDict): + def __getattribute__(self, name): + name = unescape_keyword(name) + if hasattr(collections.defaultdict, name): + return collections.defaultdict.__getattribute__(self, name) + if collections.defaultdict.__contains__(self, name): + data = collections.defaultdict.__getitem__(self, name) + if isinstance(data, dict): + return _KwSafeEmptyDict(**data) + elif data is None: + return Empty + else: + return data + return Empty + + def __getitem__(self, item): + item = unescape_keyword(item) + if collections.defaultdict.__contains__(self, item): + data = collections.defaultdict.__getitem__(self, item) + if isinstance(data, dict): + return _KwSafeEmptyDict(**data) + elif data is None: + return Empty + else: + return data + return Empty class BaseConfig(object): @@ -41,10 +71,10 @@ class BaseConfig(object): _secret_keys = {'pass', 'secret', 'token', 'key'} def __init__(self, **kwargs): - super().__setattr__('_data', EmptyDict(**kwargs)) + super().__setattr__('_data', _KwSafeEmptyDict(**kwargs)) def __getattr__(self, item): - return EmptyDict.__getattribute__(self._data, item) + return _KwSafeEmptyDict.__getattribute__(self._data, item) def __setattr__(self, key, value): if key.startswith('_'): @@ -52,10 +82,10 @@ def __setattr__(self, key, value): self.update(**{key: value}) def __str__(self): - return EmptyDict.__str__(self._repr_dump(self._data)) # noqa + return _KwSafeEmptyDict.__str__(self._repr_dump(self._data)) # noqa def __repr__(self): - return EmptyDict.__repr__(self._repr_dump(self._data)) # noqa + return _KwSafeEmptyDict.__repr__(self._repr_dump(self._data)) # noqa def as_json(self, unmaksed: bool = False) -> str: return json.dumps(self.as_dict(unmaksed=unmaksed), indent=4) @@ -87,8 +117,8 @@ def _is_key_secret(cls, key: str): return False def load(self, **kwargs): - super().__setattr__('_data', EmptyDict(**kwargs)) + super().__setattr__('_data', _KwSafeEmptyDict(**kwargs)) def update(self, **kwargs): - iters.nested_dict_update(self._data, EmptyDict(**kwargs)) + iters.nested_dict_update(self._data, _KwSafeEmptyDict(**kwargs)) diff --git a/alviss/stubber/stubmaker/_simple.py b/alviss/stubber/stubmaker/_simple.py index b77b508..d7ffe18 100644 --- a/alviss/stubber/stubmaker/_simple.py +++ b/alviss/stubber/stubmaker/_simple.py @@ -14,43 +14,54 @@ class SimpleStubMaker(IStubMaker): - def render_stub_classes_from_descriptor_file(self, file) -> str: + def render_stub_classes_from_descriptor_file(self, file, is_private: bool = True, + class_name: str = 'AlvissConfigStub') -> str: cfg = autoload(file) - root_stub = StubClass.from_dict(cfg.as_dict(unmaksed=True)) + root_stub = StubClass.from_dict(cfg.as_dict(unmaksed=True), is_private=is_private) res = [] class_names = [] for stub in root_stub.get_all_sub_stubs(): - class_names.append(stub.class_name) + if not stub.class_name.startswith('_'): + class_names.append(stub.class_name) res.append(stub.render_class_str()) - class_names.append(root_stub.class_name) + if not root_stub.class_name.startswith('_'): + class_names.append(root_stub.class_name) res.append(root_stub.render_class_str()) class_str = '\n\n\n'.join(res) - all_str = '\n'.join([f" '{c}'," for c in class_names]) + if class_name: + class_names.append(class_name) - return f"""__all__ = [ + all_str = '' + if class_names: + all_str = '\n'.join([f" '{c}'," for c in class_names]) + all_str = f"""__all__ = [ {all_str} - 'AlvissConfigStub', ] -from typing import * +""" + root_cls = '' + if class_name: + root_cls = f""" + + +class {class_name}(BaseConfig, {root_stub.class_name}): + pass""" + + return f"""{all_str}from typing import * from alviss.structs import Empty from alviss.structs.cfgstub import _BaseCfgStub from alviss.structs import BaseConfig -{class_str} - - -class AlvissConfigStub(BaseConfig, CfgStub): - pass""" +{class_str}{root_cls}""" - def render_stub_classes_to_file(self, input_file: str, output_file: str, overwrite_existing: bool = False): + def render_stub_classes_to_file(self, input_file: str, output_file: str, overwrite_existing: bool = False, is_private: bool = True): out = pathlib.Path(output_file).absolute() if out.exists() and not overwrite_existing: raise AlvissFileAlreadyExistsError('Output file already exists', file_name=output_file) - results = self.render_stub_classes_from_descriptor_file(input_file) + results = self.render_stub_classes_from_descriptor_file(input_file, is_private=is_private) if not out.parent.exists(): log.debug(f'Creating output path: {out.parent}') @@ -58,5 +69,6 @@ def render_stub_classes_to_file(self, input_file: str, output_file: str, overwri with open(output_file, 'w') as fin: fin.write(results) + fin.write('\n') return diff --git a/alviss/stubber/stubmaker/_structs.py b/alviss/stubber/stubmaker/_structs.py index a105208..89e8f9a 100644 --- a/alviss/stubber/stubmaker/_structs.py +++ b/alviss/stubber/stubmaker/_structs.py @@ -5,6 +5,7 @@ import dataclasses from typing import * from alviss.structs.errors import * +from alviss.utils import * import re import logging @@ -89,6 +90,10 @@ def _fix_and_validate_type_names(self): else: self.value = self._loop_over_type_words(self.value) + @property + def safe_field_name(self) -> str: + return escape_keyword(self.name) + @property def is_required(self) -> bool: return self.is_self_required or self.has_required_children @@ -102,7 +107,8 @@ def has_required_children(self) -> bool: @classmethod def from_keyval(cls, key: str, value: Union[str, List, Dict], ancestors: Optional[List[str]], is_map_of_stuff: bool = False, - pre_required: bool = False) -> 'StubField': + pre_required: bool = False, + is_private: bool = True) -> 'StubField': required = pre_required if key.endswith('*'): @@ -115,10 +121,10 @@ def from_keyval(cls, key: str, value: Union[str, List, Dict], ancestors: Optiona if sub_key.endswith('*'): required = True return cls.from_keyval(key=key, value=sub_val, pre_required=required, - ancestors=ancestors, is_map_of_stuff=True) + ancestors=ancestors, is_map_of_stuff=True, is_private=is_private) else: - value = StubClass.from_dict(input_dict_or_list=value, field_name=key, ancestors=ancestors) + value = StubClass.from_dict(input_dict_or_list=value, field_name=key, ancestors=ancestors, is_private=is_private) return cls(name=key, value=value, is_self_required=required, ancestors=ancestors, is_map_of_stuff=is_map_of_stuff) @@ -128,7 +134,7 @@ def from_keyval(cls, key: str, value: Union[str, List, Dict], ancestors: Optiona field_name='.'.join(ancestors+[key])) if isinstance(value[0], dict): - value = StubClass.from_dict(input_dict_or_list=value[0], field_name=key, ancestors=ancestors) + value = StubClass.from_dict(input_dict_or_list=value[0], field_name=key, ancestors=ancestors, is_private=is_private) return cls(name=key, value=value, is_self_required=required, ancestors=ancestors, is_dict_list=True, is_map_of_stuff=is_map_of_stuff) @@ -163,11 +169,11 @@ def render_field_str(self) -> str: if isinstance(self.value, StubClass): if self.is_required: if self.is_dict_list: - return f' {self.name}: List[{self.value.class_name}]' + return f' {self.safe_field_name}: List[{self.value.class_name}]' elif self.is_map_of_stuff: - return f' {self.name}: Dict[str, {self.value.class_name}]' + return f' {self.safe_field_name}: Dict[str, {self.value.class_name}]' else: - return f' {self.name}: {self.value.class_name}' + return f' {self.safe_field_name}: {self.value.class_name}' else: if self.is_dict_list: type_list = [f'List[{self.value.class_name}]', 'Empty'] @@ -182,11 +188,11 @@ def render_field_str(self) -> str: type_list.append('Empty') else: if self.is_required: - return f' {self.name}: {self.value}' + return f' {self.safe_field_name}: {self.value}' else: type_list = [self.value, 'Empty'] - return f' {self.name}: Union[{", ".join(type_list)}]' + return f' {self.safe_field_name}: Union[{", ".join(type_list)}]' @dataclasses.dataclass @@ -194,6 +200,7 @@ class StubClass: name: str ancestors: List[str] = dataclasses.field(default_factory=list) fields: List[StubField] = dataclasses.field(default_factory=list) + is_private: bool = True @property def has_required_fields(self) -> bool: @@ -206,18 +213,19 @@ def has_required_fields(self) -> bool: def from_dict(cls, input_dict_or_list: Union[Dict[str, Any], List[Any]], field_name: str = '', - ancestors: Optional[List[str]] = None) -> 'StubClass': + ancestors: Optional[List[str]] = None, + is_private: bool = True) -> 'StubClass': ancestors = ancestors or [] ancestors.append(field_name) if isinstance(input_dict_or_list, dict): - fields = [StubField.from_keyval(k, v, ancestors.copy()) for k, v in input_dict_or_list.items()] + fields = [StubField.from_keyval(k, v, ancestors.copy(), is_private=is_private) for k, v in input_dict_or_list.items()] elif isinstance(input_dict_or_list, list): # Should you ever get a list...?!? log.warning('NOT SURE THIS SHOULD EVER HAPPEN!!!') - fields = [StubField.from_keyval('__list__', input_dict_or_list[0], ancestors.copy())] + fields = [StubField.from_keyval('__list__', input_dict_or_list[0], ancestors.copy(), is_private=is_private)] else: raise AlvissStubberSyntaxError(f'Unexpected type fed to StubClass.from_dict: {type(input_dict_or_list)}') ancestors.pop() - return cls(name=field_name, ancestors=ancestors, fields=fields) + return cls(name=field_name, ancestors=ancestors, fields=fields, is_private=is_private) @staticmethod def field_name_to_class_name(field_name: str) -> str: @@ -239,20 +247,24 @@ def field_name_to_class_name(field_name: str) -> str: if not part: continue buff.append(part.capitalize()) - return ''.join(buff) + result = ''.join(buff) + return escape_keyword(result) # Just in case! @property def class_name(self) -> str: + start = 'Cfg' + if self.is_private: + start = '_Cfg' if not self.name: - return 'CfgStub' + return f'{start}Stub' else: if self.ancestors: - return f'Cfg{"".join([self.field_name_to_class_name(a) for a in self.ancestors+[self.name]])}Stub' + return f'{start}{"".join([self.field_name_to_class_name(a) for a in self.ancestors+[self.name]])}Stub' else: - return f'Cfg{self.field_name_to_class_name(self.name)}Stub' + return f'{start}{self.field_name_to_class_name(self.name)}Stub' def render_class_str(self) -> str: - lines = [f'class {self.class_name}(_BaseCfgStub):'] + lines = [f'class {self.class_name}(_BaseCfgStub, dict):'] for field in self.fields: lines.append(field.render_field_str()) return '\n'.join(lines) diff --git a/alviss/stubber/stubmaker/interface.py b/alviss/stubber/stubmaker/interface.py index 2dc5b72..260c0ae 100644 --- a/alviss/stubber/stubmaker/interface.py +++ b/alviss/stubber/stubmaker/interface.py @@ -6,14 +6,16 @@ class IStubMaker(abc.ABC): @abc.abstractmethod - def render_stub_classes_from_descriptor_file(self, file: str) -> str: + def render_stub_classes_from_descriptor_file(self, file: str, is_private: bool = True, + class_name: str = 'AlvissConfigStub') -> str: """Renders a Python module file with type hinting stub classes from the Alviss config type descriptor file. """ pass @abc.abstractmethod - def render_stub_classes_to_file(self, input_file: str, output_file: str, overwrite_existing: bool = False): + def render_stub_classes_to_file(self, input_file: str, output_file: str, + overwrite_existing: bool = False, is_private: bool = True): """Writer the results of the `render_stub_classes_from_descriptor_file` call to the given output file. """ diff --git a/alviss/utils/__init__.py b/alviss/utils/__init__.py new file mode 100644 index 0000000..8de44ee --- /dev/null +++ b/alviss/utils/__init__.py @@ -0,0 +1 @@ +from .kwfields import * diff --git a/alviss/utils/kwfields.py b/alviss/utils/kwfields.py new file mode 100644 index 0000000..a24a814 --- /dev/null +++ b/alviss/utils/kwfields.py @@ -0,0 +1,89 @@ +__all__ = [ + 'is_keyword', + 'escape_keyword', + 'unescape_keyword', +] + + +_KW_FIELDS = { # These can't be field names in dataclasses + 'False', + 'None', + 'True', + 'and', + 'as', + 'assert', + 'async', + 'await', + 'break', + 'class', + 'continue', + 'def', + 'del', + 'elif', + 'else', + 'except', + 'finally', + 'for', + 'from', + 'global', + 'if', + 'import', + 'in', + 'is', + 'lambda', + 'nonlocal', + 'not', + 'or', + 'pass', + 'raise', + 'return', + 'try', + 'while', + 'with', + 'yield', +} + + +def is_keyword(string: str, include_escaped: bool = False) -> bool: + """Given a string, returns True if it is a Python keyword (and thus can't + be used as a field name in a dataclass). + + :param string: The string to check + :param include_escaped: Also checks if the given string is an escaped + keyword (keyword with an appended underscore). False + by default. + :return: True if the string is a Python keyword') + """ + if include_escaped and string[-1] == '_': + string = string[:-1] + return string in _KW_FIELDS + + +def escape_keyword(string: str) -> str: + """Returns an escaped version of the given string, if it is a Python + keyword. Escaping just involves adding an appended underscore to its name. + + :param string: The string to escape + :return: The string with an appended underscore, if it was a Python keyword, + but otherwise the same string, unaltered. + """ + if is_keyword(string): + return f'{string}_' + return string + + +def unescape_keyword(string: str) -> str: + """If the given string is an escaped Python keyword (i.e. a keyword with an + appended underscore) removes the underscore and returns that keyword. + Otherwise the given string is returned unaltered. + + :param string: The string to unescape + :return: The string with its underscore removed, if it was an escaped Python + keyword + """ + if string[-1] != '_': + return string # Doesn't need unescaping! + check_string = string[:-1] + if is_keyword(check_string): + return check_string + return string diff --git a/tests/basic/test_stubber.py b/tests/basic/test_stubber.py index 1577216..717b53a 100644 --- a/tests/basic/test_stubber.py +++ b/tests/basic/test_stubber.py @@ -2,7 +2,7 @@ import unittest from alviss.stubber import * - +from alviss.quickloader import autoload import logging @@ -37,22 +37,27 @@ def test_class_name_maker(self): def test_simple(self): file = os.path.join(_HERE, '../res/stubber/simple.yaml') - expected_file = os.path.join(_HERE, '../res/stubber/expected/simple.txt') - stubs = SimpleStubMaker().render_stub_classes_from_descriptor_file(file) + expected_file = os.path.join(_HERE, '../res/stubber/expected/simple.py') + stubs = SimpleStubMaker().render_stub_classes_from_descriptor_file(file, is_private=True) stub_lines = [line.rstrip() for line in stubs.split('\n')] with open(expected_file, 'r') as fin: - expected_lines = [line.rstrip() for line in fin.readlines()] + expected_lines = [line.rstrip() for line in fin] self.assertEqual(len(expected_lines), len(stub_lines)) for i in range(len(expected_lines)): self.assertEqual(expected_lines[i], stub_lines[i], f'Mismatch in line {i + 1}') + from tests.res.stubber.expected.simple import AlvissConfigStub + cfg: AlvissConfigStub = autoload(os.path.join(_HERE, '../res/stubber/simple_data.yaml')) # noqa + self.assertEqual(42, cfg.collapsed.group.in_.one.string) + self.assertEqual(42, cfg.collapsed.group['in']['one']['string']) + def test_required_children(self): file = os.path.join(_HERE, '../res/stubber/required_by_child.yaml') - expected_file = os.path.join(_HERE, '../res/stubber/expected/required_by_child.txt') - stubs = SimpleStubMaker().render_stub_classes_from_descriptor_file(file) + expected_file = os.path.join(_HERE, '../res/stubber/expected/required_by_child.py') + stubs = SimpleStubMaker().render_stub_classes_from_descriptor_file(file, is_private=False) stub_lines = [line.rstrip() for line in stubs.split('\n')] with open(expected_file, 'r') as fin: @@ -65,8 +70,8 @@ def test_required_children(self): def test_with_complex_lists(self): file = os.path.join(_HERE, '../res/stubber/with_complex_lists.yaml') - expected_file = os.path.join(_HERE, '../res/stubber/expected/with_complex_lists.txt') - stubs = SimpleStubMaker().render_stub_classes_from_descriptor_file(file) + expected_file = os.path.join(_HERE, '../res/stubber/expected/with_complex_lists.py') + stubs = SimpleStubMaker().render_stub_classes_from_descriptor_file(file, class_name='', is_private=False) stub_lines = [line.rstrip() for line in stubs.split('\n')] with open(expected_file, 'r') as fin: @@ -83,8 +88,8 @@ def test_with_complex_lists(self): def test_with_lists_of_dicts(self): file = os.path.join(_HERE, '../res/stubber/with_lists_of_dicts.yaml') - expected_file = os.path.join(_HERE, '../res/stubber/expected/with_lists_of_dicts.txt') - stubs = SimpleStubMaker().render_stub_classes_from_descriptor_file(file) + expected_file = os.path.join(_HERE, '../res/stubber/expected/with_lists_of_dicts.py') + stubs = SimpleStubMaker().render_stub_classes_from_descriptor_file(file, class_name='', is_private=True) stub_lines = [line.rstrip() for line in stubs.split('\n')] with open(expected_file, 'r') as fin: @@ -101,8 +106,8 @@ def test_with_lists_of_dicts(self): def test_with_complex_maps_and_lists(self): file = os.path.join(_HERE, '../res/stubber/with_complex_maps_and_lists.yaml') - expected_file = os.path.join(_HERE, '../res/stubber/expected/with_complex_maps_and_lists.txt') - stubs = SimpleStubMaker().render_stub_classes_from_descriptor_file(file) + expected_file = os.path.join(_HERE, '../res/stubber/expected/with_complex_maps_and_lists.py') + stubs = SimpleStubMaker().render_stub_classes_from_descriptor_file(file, class_name='ComplexConfig', is_private=False) stub_lines = [line.rstrip() for line in stubs.split('\n')] with open(expected_file, 'r') as fin: diff --git a/tests/res/stubber/__init__.py b/tests/res/stubber/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/res/stubber/complex_maps_logger.yaml b/tests/res/stubber/complex_maps_logger.yaml new file mode 100644 index 0000000..032e005 --- /dev/null +++ b/tests/res/stubber/complex_maps_logger.yaml @@ -0,0 +1,23 @@ +logging: + version: int + disable_existing_loggers: bool + + root: + level: str + handlers: List[str] + + formatters: + base: + format: str + + handlers: + ${str}: + level: str + class: str + formatter: str + + loggers: + ${str}: + handlers: List[str] + level: str + propagate: bool \ No newline at end of file diff --git a/tests/res/stubber/expected/__init__.py b/tests/res/stubber/expected/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/res/stubber/expected/complex_maps_logger.py b/tests/res/stubber/expected/complex_maps_logger.py new file mode 100644 index 0000000..e58e559 --- /dev/null +++ b/tests/res/stubber/expected/complex_maps_logger.py @@ -0,0 +1,57 @@ +__all__ = [ + 'CfgLoggingRootStub', + 'CfgLoggingFormattersBaseStub', + 'CfgLoggingFormattersStub', + 'CfgLoggingHandlersStub', + 'CfgLoggingLoggersStub', + 'CfgLoggingStub', + 'CfgStub', + 'AlvissConfigStub', +] + +from typing import * +from alviss.structs import Empty +from alviss.structs.cfgstub import _BaseCfgStub +from alviss.structs import BaseConfig + + +class CfgLoggingRootStub(_BaseCfgStub, dict): + level: Union[str, Empty] + handlers: Union[List[str], Empty] + + +class CfgLoggingFormattersBaseStub(_BaseCfgStub, dict): + format: Union[str, Empty] + + +class CfgLoggingFormattersStub(_BaseCfgStub, dict): + base: Union[CfgLoggingFormattersBaseStub, Empty] + + +class CfgLoggingHandlersStub(_BaseCfgStub, dict): + level: Union[str, Empty] + class_: Union[str, Empty] + formatter: Union[str, Empty] + + +class CfgLoggingLoggersStub(_BaseCfgStub, dict): + handlers: Union[List[str], Empty] + level: Union[str, Empty] + propagate: Union[bool, Empty] + + +class CfgLoggingStub(_BaseCfgStub, dict): + version: Union[int, Empty] + disable_existing_loggers: Union[bool, Empty] + root: Union[CfgLoggingRootStub, Empty] + formatters: Union[CfgLoggingFormattersStub, Empty] + handlers: Union[Dict[str, CfgLoggingHandlersStub], Empty] + loggers: Union[Dict[str, CfgLoggingLoggersStub], Empty] + + +class CfgStub(_BaseCfgStub, dict): + logging: Union[CfgLoggingStub, Empty] + + +class AlvissConfigStub(BaseConfig, CfgStub): + pass diff --git a/tests/res/stubber/expected/required_by_child.txt b/tests/res/stubber/expected/required_by_child.py similarity index 71% rename from tests/res/stubber/expected/required_by_child.txt rename to tests/res/stubber/expected/required_by_child.py index ac0edb8..9eb0f13 100644 --- a/tests/res/stubber/expected/required_by_child.txt +++ b/tests/res/stubber/expected/required_by_child.py @@ -13,24 +13,24 @@ from alviss.structs import BaseConfig -class CfgTopSecondShouldBeOptionalStub(_BaseCfgStub): +class CfgTopSecondShouldBeOptionalStub(_BaseCfgStub, dict): bar: Union[str, Empty] -class CfgTopSecondMustExistStub(_BaseCfgStub): +class CfgTopSecondMustExistStub(_BaseCfgStub, dict): foo: str -class CfgTopSecondStub(_BaseCfgStub): +class CfgTopSecondStub(_BaseCfgStub, dict): should_be_optional: Union[CfgTopSecondShouldBeOptionalStub, Empty] must_exist: CfgTopSecondMustExistStub -class CfgTopStub(_BaseCfgStub): +class CfgTopStub(_BaseCfgStub, dict): second: CfgTopSecondStub -class CfgStub(_BaseCfgStub): +class CfgStub(_BaseCfgStub, dict): top: CfgTopStub diff --git a/tests/res/stubber/expected/simple.py b/tests/res/stubber/expected/simple.py new file mode 100644 index 0000000..8cbb2c2 --- /dev/null +++ b/tests/res/stubber/expected/simple.py @@ -0,0 +1,67 @@ +__all__ = [ + 'AlvissConfigStub', +] + +from typing import * +from alviss.structs import Empty +from alviss.structs.cfgstub import _BaseCfgStub +from alviss.structs import BaseConfig + + +class _CfgInternalRefDeeperStub(_BaseCfgStub, dict): + there: Union[str, Empty] + everywhere: str + + +class _CfgInternalRefStub(_BaseCfgStub, dict): + deeper: _CfgInternalRefDeeperStub + + +class _CfgGroupStub(_BaseCfgStub, dict): + alpha: Union[str, int, Empty] + beta: Union[float, int, str] + gamma_with_var: Union[str, None, Empty] + delta: Union[int, None, Empty] + + +class _CfgCollapsedGroupInOneStub(_BaseCfgStub, dict): + string: Union[int, Empty] + + +class _CfgCollapsedGroupInStub(_BaseCfgStub, dict): + one: Union[_CfgCollapsedGroupInOneStub, Empty] + + +class _CfgCollapsedGroupStub(_BaseCfgStub, dict): + successful: Union[bool, Empty] + in_: Union[_CfgCollapsedGroupInStub, Empty] + + +class _CfgCollapsedStub(_BaseCfgStub, dict): + other_group: Union[Dict[str, Any], None, Empty] + group: Union[_CfgCollapsedGroupStub, Empty] + + +class _CfgImportTestStub(_BaseCfgStub, dict): + imported_keys: Union[Dict[str, str], Empty] + + +class _CfgShouldBeSevenStub(_BaseCfgStub, dict): + sub_seven: Dict[str, Any] + + +class _CfgStub(_BaseCfgStub, dict): + foo: Union[str, Empty] + internal_ref: _CfgInternalRefStub + base_key: Union[int, Empty] + foo_inherited: Union[float, Empty] + bar: float + group: _CfgGroupStub + collapsed: Union[_CfgCollapsedStub, Empty] + my_list: Union[List[str], Empty] + import_test: Union[_CfgImportTestStub, Empty] + should_be_seven: _CfgShouldBeSevenStub + + +class AlvissConfigStub(BaseConfig, _CfgStub): + pass diff --git a/tests/res/stubber/expected/simple.txt b/tests/res/stubber/expected/simple.txt deleted file mode 100644 index 9139853..0000000 --- a/tests/res/stubber/expected/simple.txt +++ /dev/null @@ -1,77 +0,0 @@ -__all__ = [ - 'CfgInternalRefDeeperStub', - 'CfgInternalRefStub', - 'CfgGroupStub', - 'CfgCollapsedGroupInOneStub', - 'CfgCollapsedGroupInStub', - 'CfgCollapsedGroupStub', - 'CfgCollapsedStub', - 'CfgImportTestStub', - 'CfgShouldBeSevenStub', - 'CfgStub', - 'AlvissConfigStub', -] - -from typing import * -from alviss.structs import Empty -from alviss.structs.cfgstub import _BaseCfgStub -from alviss.structs import BaseConfig - - -class CfgInternalRefDeeperStub(_BaseCfgStub): - there: Union[str, Empty] - everywhere: str - - -class CfgInternalRefStub(_BaseCfgStub): - deeper: CfgInternalRefDeeperStub - - -class CfgGroupStub(_BaseCfgStub): - alpha: Union[str, int, Empty] - beta: Union[float, int, str] - gamma_with_var: Union[str, None, Empty] - delta: Union[int, None, Empty] - - -class CfgCollapsedGroupInOneStub(_BaseCfgStub): - string: Union[int, Empty] - - -class CfgCollapsedGroupInStub(_BaseCfgStub): - one: Union[CfgCollapsedGroupInOneStub, Empty] - - -class CfgCollapsedGroupStub(_BaseCfgStub): - successful: Union[bool, Empty] - in: Union[CfgCollapsedGroupInStub, Empty] - - -class CfgCollapsedStub(_BaseCfgStub): - other_group: Union[Dict[str, Any], None, Empty] - group: Union[CfgCollapsedGroupStub, Empty] - - -class CfgImportTestStub(_BaseCfgStub): - imported_keys: Union[Dict[str, str], Empty] - - -class CfgShouldBeSevenStub(_BaseCfgStub): - sub_seven: Dict[str, Any] - - -class CfgStub(_BaseCfgStub): - foo: Union[str, Empty] - internal_ref: CfgInternalRefStub - base_key: Union[int, Empty] - foo_inherited: Union[float, Empty] - bar: float - group: CfgGroupStub - collapsed: Union[CfgCollapsedStub, Empty] - my_list: Union[List[str], Empty] - import_test: Union[CfgImportTestStub, Empty] - should_be_seven: CfgShouldBeSevenStub - - -class AlvissConfigStub(BaseConfig, CfgStub): - pass diff --git a/tests/res/stubber/expected/with_complex_lists.txt b/tests/res/stubber/expected/with_complex_lists.py similarity index 72% rename from tests/res/stubber/expected/with_complex_lists.txt rename to tests/res/stubber/expected/with_complex_lists.py index 6092307..94facb5 100644 --- a/tests/res/stubber/expected/with_complex_lists.txt +++ b/tests/res/stubber/expected/with_complex_lists.py @@ -4,7 +4,6 @@ 'CfgBarYomamaStub', 'CfgBarStub', 'CfgStub', - 'AlvissConfigStub', ] from typing import * @@ -13,30 +12,26 @@ from alviss.structs import BaseConfig -class CfgFooStub(_BaseCfgStub): +class CfgFooStub(_BaseCfgStub, dict): my_list: Union[List[str], Empty] my_required_list: List[str] -class CfgFoolStub(_BaseCfgStub): +class CfgFoolStub(_BaseCfgStub, dict): myDifferentList: Union[List[str], Empty] myDifferentMultiTypeList: Union[List[Union[str, float]], Empty] -class CfgBarYomamaStub(_BaseCfgStub): +class CfgBarYomamaStub(_BaseCfgStub, dict): myDifferentRequiredList: List[Union[str, int]] myDifferentRequiredListByKeyName: List[Any] -class CfgBarStub(_BaseCfgStub): +class CfgBarStub(_BaseCfgStub, dict): yomama: CfgBarYomamaStub -class CfgStub(_BaseCfgStub): +class CfgStub(_BaseCfgStub, dict): Foo: CfgFooStub fool: Union[CfgFoolStub, Empty] bar: CfgBarStub - - -class AlvissConfigStub(BaseConfig, CfgStub): - pass diff --git a/tests/res/stubber/expected/with_complex_maps_and_lists.txt b/tests/res/stubber/expected/with_complex_maps_and_lists.py similarity index 75% rename from tests/res/stubber/expected/with_complex_maps_and_lists.txt rename to tests/res/stubber/expected/with_complex_maps_and_lists.py index cddd955..5c1e78f 100644 --- a/tests/res/stubber/expected/with_complex_maps_and_lists.txt +++ b/tests/res/stubber/expected/with_complex_maps_and_lists.py @@ -6,7 +6,7 @@ 'CfgHereBeStufMandatoryPeopleStub', 'CfgHereBeStufStub', 'CfgStub', - 'AlvissConfigStub', + 'ComplexConfig', ] from typing import * @@ -15,42 +15,42 @@ from alviss.structs import BaseConfig -class CfgImportTestSkillsStub(_BaseCfgStub): +class CfgImportTestSkillsStub(_BaseCfgStub, dict): level: Union[int, Empty] group: str -class CfgImportTestInventoryStub(_BaseCfgStub): +class CfgImportTestInventoryStub(_BaseCfgStub, dict): weight: Union[float, Empty] quantity: Union[int, Empty] -class CfgImportTestStub(_BaseCfgStub): +class CfgImportTestStub(_BaseCfgStub, dict): other_list: Union[List[Union[str, float]], Empty] required_list: List[str] skills: Dict[str, CfgImportTestSkillsStub] inventory: Union[Dict[str, CfgImportTestInventoryStub], Empty] -class CfgHereBeStufPeopleStub(_BaseCfgStub): +class CfgHereBeStufPeopleStub(_BaseCfgStub, dict): name: str age: Union[int, Empty] favorite_color: Union[str, Empty] -class CfgHereBeStufMandatoryPeopleStub(_BaseCfgStub): +class CfgHereBeStufMandatoryPeopleStub(_BaseCfgStub, dict): name: str age: Union[int, Empty] level: int -class CfgHereBeStufStub(_BaseCfgStub): +class CfgHereBeStufStub(_BaseCfgStub, dict): people: List[CfgHereBeStufPeopleStub] mandatory_people: List[CfgHereBeStufMandatoryPeopleStub] sub_seven: Dict[str, Any] -class CfgStub(_BaseCfgStub): +class CfgStub(_BaseCfgStub, dict): my_list: Union[List[str], Empty] my_required_list: List[str] myDifferentList: Union[List[str], Empty] @@ -60,5 +60,5 @@ class CfgStub(_BaseCfgStub): here_be_stuf: CfgHereBeStufStub -class AlvissConfigStub(BaseConfig, CfgStub): +class ComplexConfig(BaseConfig, CfgStub): pass diff --git a/tests/res/stubber/expected/with_lists_of_dicts.py b/tests/res/stubber/expected/with_lists_of_dicts.py new file mode 100644 index 0000000..0cea452 --- /dev/null +++ b/tests/res/stubber/expected/with_lists_of_dicts.py @@ -0,0 +1,36 @@ +from typing import * +from alviss.structs import Empty +from alviss.structs.cfgstub import _BaseCfgStub +from alviss.structs import BaseConfig + + +class _CfgFooBarListOfPeopleHealthStub(_BaseCfgStub, dict): + blood_pressure: Union[int, Empty] + weight: Union[float, Empty] + alive: Union[bool, Empty] + + +class _CfgFooBarListOfPeopleStub(_BaseCfgStub, dict): + name: Union[str, Empty] + age: Union[int, Empty] + shoe_size: Union[float, Empty] + favorite_foods: Union[List[str], Empty] + health: Union[_CfgFooBarListOfPeopleHealthStub, Empty] + + +class _CfgFooBarListOfImportantPeopleStub(_BaseCfgStub, dict): + name: str + title: Union[str, Empty] + + +class _CfgFooBarStub(_BaseCfgStub, dict): + list_of_people: Union[List[_CfgFooBarListOfPeopleStub], Empty] + list_of_important_people: List[_CfgFooBarListOfImportantPeopleStub] + + +class _CfgFooStub(_BaseCfgStub, dict): + bar: _CfgFooBarStub + + +class _CfgStub(_BaseCfgStub, dict): + foo: _CfgFooStub diff --git a/tests/res/stubber/expected/with_lists_of_dicts.txt b/tests/res/stubber/expected/with_lists_of_dicts.txt deleted file mode 100644 index 4da77de..0000000 --- a/tests/res/stubber/expected/with_lists_of_dicts.txt +++ /dev/null @@ -1,50 +0,0 @@ -__all__ = [ - 'CfgFooBarListOfPeopleHealthStub', - 'CfgFooBarListOfPeopleStub', - 'CfgFooBarListOfImportantPeopleStub', - 'CfgFooBarStub', - 'CfgFooStub', - 'CfgStub', - 'AlvissConfigStub', -] - -from typing import * -from alviss.structs import Empty -from alviss.structs.cfgstub import _BaseCfgStub -from alviss.structs import BaseConfig - - -class CfgFooBarListOfPeopleHealthStub(_BaseCfgStub): - blood_pressure: Union[int, Empty] - weight: Union[float, Empty] - alive: Union[bool, Empty] - - -class CfgFooBarListOfPeopleStub(_BaseCfgStub): - name: Union[str, Empty] - age: Union[int, Empty] - shoe_size: Union[float, Empty] - favorite_foods: Union[List[str], Empty] - health: Union[CfgFooBarListOfPeopleHealthStub, Empty] - - -class CfgFooBarListOfImportantPeopleStub(_BaseCfgStub): - name: str - title: Union[str, Empty] - - -class CfgFooBarStub(_BaseCfgStub): - list_of_people: Union[List[CfgFooBarListOfPeopleStub], Empty] - list_of_important_people: List[CfgFooBarListOfImportantPeopleStub] - - -class CfgFooStub(_BaseCfgStub): - bar: CfgFooBarStub - - -class CfgStub(_BaseCfgStub): - foo: CfgFooStub - - -class AlvissConfigStub(BaseConfig, CfgStub): - pass diff --git a/tests/res/stubber/simple_data.yaml b/tests/res/stubber/simple_data.yaml new file mode 100644 index 0000000..cecb062 --- /dev/null +++ b/tests/res/stubber/simple_data.yaml @@ -0,0 +1,31 @@ +foo: Some foo +internal_ref: + deeper: + there: Some there + everywhere: Some everywhere +base_key: 17 +foo_inherited: 1.3 +bar: 3.1415 +group: + alpha: Beta + beta: 54 + gamma_with_var: ~ + delta: 7 +collapsed: + other_group: ~ + group: + successful: True + in: + one: + string: 42 +my_list: + - One + - Two +import_test: + imported_keys: + you: me + him: her +should_be_seven: + sub_seven: + seven: Eight +