Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Version 3.2.0 - Stubber Update #3

Merged
merged 3 commits into from
Apr 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,25 @@ 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.0] - 2024-04-22

### Added

- Stub generators from descriptor config files where you structure out your
config file and give each value the Python type you expect the config to
yield in order to generate type-hinting stub-classes that match the structure
of your config file(s).
- CLI commands for running the stub generation: `alviss-stubber`
- CLI commands for running the config rendering: `alviss-render`
- A bunch of Alviss specific Error Exceptions that are raised e.g.
when files aren't found or when Fidelius is required but isn't installed

### Changed

- The rendering of static single file configs from Alviss config files and
expressions has now been moved into its own sub-module with a standard API


## [3.1.0] - 2024-04-12

### Added
Expand Down
2 changes: 1 addition & 1 deletion alviss/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
__version__ = '3.1.0'
__version__ = '3.2.0'

__author__ = 'Thordur Matthiasson <thordurm@ccpgames.com>'
__license__ = 'MIT License'
Expand Down
Empty file added alviss/cli/__init__.py
Empty file.
Empty file added alviss/cli/render/__init__.py
Empty file.
66 changes: 66 additions & 0 deletions alviss/cli/render/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import argparse

from alviss import __version__ as version
from alviss.renderers import SimpleStaticRenderer
from alviss.structs.errors import *

import sys


def main():
parser = argparse.ArgumentParser(description='Renders a single static config from an Alviss formatted file and '
'its extends, includes and other expressions.',
epilog=f'Alviss version {version}')

parser.add_argument('file', help='The Alviss formatted config file to read, parse and render')
parser.add_argument('-o', '--output', help='File to write the results 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')

loudness_group = parser.add_mutually_exclusive_group()
loudness_group.add_argument('-s', '--silent', action='store_true',
help='Only outputs the resulting rendered config (and errors print to stderr)')
loudness_group.add_argument('-v', '--verbose', action="store_true",
help='Spits out DEBUG level logs')

args = parser.parse_args()

if args.verbose:
import logging
logging.basicConfig(level=logging.DEBUG)

if not args.silent:
print(f'Reading and parsing file: {args.file}...')

try:
if args.output:
if not args.silent:
print(f'Writing output to: {args.output}...')

SimpleStaticRenderer().render_static_config_to_file(input_file=args.file,
output_file=args.output,
overwrite_existing=args.force_overwrite)

else:
if not args.silent:
print(f'Printing results:')
print(f'==================================================')
print(SimpleStaticRenderer().render_static_config_from_file(args.file))

if not args.silent:
print(f'==================================================')

if not args.silent:
print(f'Done!')

except AlvissError as e:
if not args.silent:
print(f'An error occurred: {e!r}')
else:
print(f'An error occurred: {e!r}', file=sys.stderr)
sys.exit(3)


if __name__ == '__main__':
main()
Empty file added alviss/cli/stubber/__init__.py
Empty file.
62 changes: 62 additions & 0 deletions alviss/cli/stubber/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import argparse

from alviss import __version__ as version
from alviss import stubber
from alviss.structs.errors import *
import sys


def main():
parser = argparse.ArgumentParser(description='Generates Python dataclass stubs based on the given alviss type descriptor file.',
epilog=f'Alviss version {version}')

parser.add_argument('file', help='The Alviss config type descriptor file to generate strubs from.')
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')

loudness_group = parser.add_mutually_exclusive_group()
loudness_group.add_argument('-s', '--silent', action='store_true',
help='Only outputs the resulting rendered code (and errors print to stderr)')
loudness_group.add_argument('-v', '--verbose', action="store_true",
help='Spits out DEBUG level logs')

args = parser.parse_args()

if args.verbose:
import logging
logging.basicConfig(level=logging.DEBUG)

if not args.silent:
print(f'Reading and stubbing file: {args.file}...')

try:
if args.output:
if not args.silent:
print(f'Writing output to: {args.output}...')

stubber.SimpleStubMaker().render_stub_classes_to_file(input_file=args.file,
output_file=args.output,
overwrite_existing=args.force_overwrite)
else:
if not args.silent:
print(f'Printing results:')
print(f'==================================================')
print(stubber.SimpleStubMaker().render_stub_classes_from_descriptor_file(args.file))

if not args.silent:
print(f'==================================================')

if not args.silent:
print(f'Done!')

except AlvissError as e:
if not args.silent:
print(f'An error occurred: {e!r}')
else:
print(f'An error occurred: {e!r}', file=sys.stderr)
sys.exit(3)


if __name__ == '__main__':
main()
2 changes: 1 addition & 1 deletion alviss/loaders/autoloader.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ def guess_loader_class(file_name: str) -> Type[IAlvissLoader]:
ext = os.path.splitext(file_name)[-1]
loader = _EXTENSION_LOADER_MAP.get(ext, None)
if not loader:
raise NotImplementedError(f'dont know how to autoload file extension {ext}')
raise AlvissUnknownFileTypeError(f'Dont know how to autoload file extension {ext}', file_name=file_name)
return loader


Expand Down
31 changes: 20 additions & 11 deletions alviss/loaders/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ def _find_fidelius_mode(self):
import fidelius
except ImportError:
log.error('ALVISS_FIDELIUS_MODE is ENABLED but fidelius is not installed')
raise
raise AlvissFideliusNotInstalledError('ALVISS_FIDELIUS_MODE is ENABLED but fidelius is not installed')

@property
def data(self) -> Dict:
Expand All @@ -82,6 +82,9 @@ def load_file(self,

self._file_name = os.path.basename(file_name)
self._file_path = os.path.dirname(os.path.abspath(file_name))
if not os.path.exists(file_name):
raise AlvissFileNotFoundError('File not found', file_name=file_name)

with open(file_name, 'r', encoding=encoding) as fin:
self.load_raw(fin.read(), no_resolve=no_resolve, no_extend=no_extend,
no_includes=no_includes, no_env_load=no_env_load, no_fidelius=no_fidelius)
Expand All @@ -91,8 +94,11 @@ def _extend(self):
for inc_file, location in self._extends:
extended = self.__class__()

extended.load_file(os.path.join(self._file_path, inc_file),
no_resolve=True, no_env_load=self._skip_env_loading, no_fidelius=True)
ext_file = os.path.join(self._file_path, inc_file)
if not os.path.exists(ext_file):
raise AlvissFileNotFoundError('File to extend not found', file_name=ext_file)

extended.load_file(ext_file, no_resolve=True, no_env_load=self._skip_env_loading, no_fidelius=True)

old_data = self._data
old_unresolved = self._unresolved
Expand All @@ -118,8 +124,11 @@ def _includes(self):
if self._include:
for inc_file, location in self._include:
inc_loader = self.__class__()
inc_loader.load_file(os.path.join(self._file_path, inc_file),
no_resolve=True, no_env_load=self._skip_env_loading, no_fidelius=True)
full_file = os.path.join(self._file_path, inc_file)
if not os.path.exists(full_file):
raise AlvissFileNotFoundError('File to include not found', file_name=full_file)

inc_loader.load_file(full_file, no_resolve=True, no_env_load=self._skip_env_loading, no_fidelius=True)
new_data = {}
new_unresolved = {}
if location:
Expand Down Expand Up @@ -160,7 +169,7 @@ def _resolve(self):
if required_list:
for location, value in required_list:
log.error(f'Required variable config value is unresolved: {location}, {value}')
raise ValueError(f'Required variable config values were unresolved: {required_list!r}')
raise AlvissSyntaxError(f'Required variable config values were unresolved: {required_list!r}')

def _fetch_fidelius(self):
if self._fidelius_keys:
Expand All @@ -171,17 +180,17 @@ def _fetch_fidelius(self):
if self.get_fidelius_mode() == FideliusMode.ON_DEMAND:
for path_tuple, value in self._fidelius_keys.items():
log.error(f'Fidelius tag ({path_tuple}, {value}) found but Fidelius is not installed (ALVISS_FIDELIUS_MODE=ON_DEMAND)')
raise
raise AlvissFideliusNotInstalledError(f'Fidelius tags found but Fidelius is not installed (ALVISS_FIDELIUS_MODE=ON_DEMAND)')

app_var = iters.nested_get(self.data, ('app', 'slug'), iters.nested_get(self.data, ('app', 'module_name')))
if not app_var:
raise ValueError('unable to resolve fidelius keys without app.slug or app.module_name')
raise AlvissFideliusSyntaxError('unable to resolve fidelius keys without app.slug or app.module_name')
group_var = iters.nested_get(self.data, ('app', 'group'))
if not group_var:
raise ValueError('unable to resolve fidelius keys without app.group')
raise AlvissFideliusSyntaxError('unable to resolve fidelius keys without app.group')
env_var = iters.nested_get(self.data, ('app', 'env'))
if not env_var:
raise ValueError('unable to resolve fidelius keys without app.env')
raise AlvissFideliusSyntaxError('unable to resolve fidelius keys without app.env')

fidcls = FideliusFactory.get_class('mock' if self.get_fidelius_mode() == FideliusMode.MOCK else 'paramstore')

Expand Down Expand Up @@ -394,7 +403,7 @@ def _special_key_fidelius(self, value: Dict[str, Any], path: List[str]):
import fidelius
except ImportError:
log.error('ALVISS_FIDELIUS_MODE is ENABLED but fidelius is not installed')
raise
raise AlvissFideliusNotInstalledError('ALVISS_FIDELIUS_MODE is ENABLED but fidelius is not installed')

if 'kwargs' in value:
if not isinstance(value['kwargs'], dict):
Expand Down
1 change: 1 addition & 0 deletions alviss/renderers/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .static import *
2 changes: 2 additions & 0 deletions alviss/renderers/static/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from .interface import *
from ._simple import *
34 changes: 34 additions & 0 deletions alviss/renderers/static/_simple.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
__all__ = [
'SimpleStaticRenderer',
]

from .interface import *
from alviss import quickloader
from alviss.structs.errors import *

import os
import pathlib

import logging
log = logging.getLogger(__file__)


class SimpleStaticRenderer(IStaticRenderer):
def render_static_config_from_file(self, file: str) -> str:
return quickloader.render_load(file)

def render_static_config_to_file(self, input_file: str, output_file: str, overwrite_existing: bool = False):
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_static_config_from_file(input_file)

if not out.parent.exists():
log.debug(f'Creating output path: {out.parent}')
os.makedirs(out.parent, exist_ok=True)

with open(output_file, 'w') as fin:
fin.write(results)

return
21 changes: 21 additions & 0 deletions alviss/renderers/static/interface.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
__all__ = [
'IStaticRenderer',
]
import abc


class IStaticRenderer(abc.ABC):
@abc.abstractmethod
def render_static_config_from_file(self, file: str) -> str:
"""Renders a single static configuration file from an Alviss formatted
file, including all included and/or extended files and resolving all
expressions, variables and internal references and such.
"""
pass

@abc.abstractmethod
def render_static_config_to_file(self, input_file: str, output_file: str, overwrite_existing: bool = False):
"""Writer the results of the `render_static_config_from_file` call to
the given output file.
"""
pass
1 change: 1 addition & 0 deletions alviss/structs/_base.py
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
from ccptools.structs import *
from .errors import *
12 changes: 12 additions & 0 deletions alviss/structs/cfgstub.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
__all__ = [
'_BaseCfgStub',
]
from ._base import *


class _BaseCfgStub(Protocol):
"""This is the base Protocol for all generated Stubs

"""
def __getattr__(self, item) -> Union[Any, Empty]:
...
Loading
Loading