config as dataclass
This commit is contained in:
parent
6f68913e11
commit
ccc6679e21
32
setup.py
32
setup.py
|
@ -40,11 +40,6 @@ PHASE0 = 'phase0'
|
|||
ALTAIR = 'altair'
|
||||
MERGE = 'merge'
|
||||
|
||||
CONFIG_LOADER = '''
|
||||
PRESET_BASE = 'mainnet'
|
||||
apply_constants_config(globals())
|
||||
'''
|
||||
|
||||
# The helper functions that are used when defining constants
|
||||
CONSTANT_DEP_SUNDRY_CONSTANTS_FUNCTIONS = '''
|
||||
def ceillog2(x: int) -> uint64:
|
||||
|
@ -333,7 +328,6 @@ from typing import (
|
|||
Any, Callable, Dict, Set, Sequence, Tuple, Optional, TypeVar
|
||||
)
|
||||
|
||||
from eth2spec.config.config_util import apply_constants_config
|
||||
from eth2spec.utils.ssz.ssz_impl import hash_tree_root, copy, uint_to_bytes
|
||||
from eth2spec.utils.ssz.ssz_typing import (
|
||||
View, boolean, Container, List, Vector, uint8, uint32, uint64,
|
||||
|
@ -347,7 +341,6 @@ from eth2spec.utils.hash_function import hash
|
|||
def preparations(cls) -> str:
|
||||
return '''
|
||||
SSZObject = TypeVar('SSZObject', bound=View)
|
||||
CONFIG_NAME = 'mainnet'
|
||||
'''
|
||||
|
||||
@classmethod
|
||||
|
@ -598,18 +591,30 @@ def objects_to_spec(spec_object: SpecObject, builder: SpecBuilder, ordered_class
|
|||
del spec_object.functions[k]
|
||||
functions_spec = '\n\n\n'.join(spec_object.functions.values())
|
||||
|
||||
# Access global dict of config vars for runtime configurables
|
||||
for name in spec_object.config_vars.keys():
|
||||
functions_spec = functions_spec.replace(name, 'config.' + name)
|
||||
|
||||
def format_config_var(name: str, vardef: VariableDefinition) -> str:
|
||||
out = f'{name}={vardef.type_name}({vardef.value}),'
|
||||
if vardef.comment is not None:
|
||||
out += f' # {vardef.comment}'
|
||||
return out
|
||||
|
||||
config_spec = '@dataclass\nclass Configuration(object):\n'
|
||||
config_spec += '\n'.join(f' {k}: {v.type_name}' for k, v in spec_object.config_vars.items())
|
||||
config_spec += '\n\n\nconfig = Configuration(\n'
|
||||
config_spec += '\n'.join(' ' + format_config_var(k, v) for k, v in spec_object.config_vars.items())
|
||||
config_spec += '\n)\n'
|
||||
|
||||
def format_constant(name: str, vardef: VariableDefinition) -> str:
|
||||
if not hasattr(vardef, 'value'):
|
||||
print(vardef)
|
||||
raise Exception("oh no")
|
||||
out = f'{name} = {vardef.type_name}({vardef.value})'
|
||||
if vardef.comment is not None:
|
||||
out += f'# {vardef.comment}'
|
||||
out += f' # {vardef.comment}'
|
||||
return out
|
||||
|
||||
constant_vars_spec = '# Constant vars \n' + '\n'.join(format_constant(k, v) for k, v in spec_object.constant_vars.items())
|
||||
preset_vars_spec = '# Preset vars \n' + '\n'.join(format_constant(k, v) for k, v in spec_object.preset_vars.items())
|
||||
config_vars_spec = '# Config vars\n' + '\n'.join(format_constant(k, v) for k, v in spec_object.config_vars.items()) # TODO make config reloading easier.
|
||||
ordered_class_objects_spec = '\n\n\n'.join(ordered_class_objects.values())
|
||||
ssz_dep_constants = '\n'.join(map(lambda x: '%s = %s' % (x, builder.hardcoded_ssz_dep_constants()[x]), builder.hardcoded_ssz_dep_constants()))
|
||||
ssz_dep_constants_verification = '\n'.join(map(lambda x: 'assert %s == %s' % (x, spec_object.ssz_dep_constants[x]), builder.hardcoded_ssz_dep_constants()))
|
||||
|
@ -626,8 +631,7 @@ def objects_to_spec(spec_object: SpecObject, builder: SpecBuilder, ordered_class
|
|||
+ ('\n\n' + ssz_dep_constants if ssz_dep_constants != '' else '')
|
||||
+ '\n\n' + constant_vars_spec
|
||||
+ '\n\n' + preset_vars_spec
|
||||
+ '\n\n' + config_vars_spec
|
||||
+ '\n\n' + CONFIG_LOADER
|
||||
+ '\n\n\n' + config_spec
|
||||
+ '\n\n' + ordered_class_objects_spec
|
||||
+ ('\n\n\n' + protocols_spec if protocols_spec != '' else '')
|
||||
+ '\n\n\n' + functions_spec
|
||||
|
|
|
@ -6,22 +6,15 @@ For configuration, see [Configs documentation](../../../../../configs/README.md)
|
|||
|
||||
```python
|
||||
from eth2spec.config import config_util
|
||||
from eth2spec.phase0 import spec
|
||||
from importlib import reload
|
||||
from eth2spec.phase0.mainnet import as spec
|
||||
from pathlib import Path
|
||||
|
||||
# To load the presets and configurations
|
||||
# To load the default configurations
|
||||
config_util.load_defaults(Path("eth2.0-specs/configs")) # change path to point to equivalent of specs `configs` dir.
|
||||
# After loading the defaults, a config can be chosen: 'mainnet', 'minimal', or custom network config
|
||||
config_util.prepare_config('minimal')
|
||||
# Alternatively, load a custom testnet config:
|
||||
config_util.prepare_config('my_config.yaml')
|
||||
# reload spec to make loaded config effective
|
||||
reload(spec)
|
||||
# After loading the defaults, a config can be chosen: 'mainnet', 'minimal', or custom network config (by file path)
|
||||
spec.config = spec.Configuration(**config_util.load_config_file('mytestnet.yaml'))
|
||||
```
|
||||
|
||||
Note: previously the testnet config files included both preset and runtime-configuration data.
|
||||
The new config loader is compatible with this: just run `prepare_config` without loading preset defaults,
|
||||
and omit the `PRESET_BASE` from the config.
|
||||
|
||||
WARNING: this overwrites globals, make sure to prevent accidental collisions with other usage of the same imported specs package.
|
||||
The new config loader is compatible with this: all config vars are loaded from the file,
|
||||
but those that have become presets will be ignored.
|
||||
|
|
|
@ -1,52 +1,7 @@
|
|||
import os
|
||||
from pathlib import Path
|
||||
from copy import deepcopy
|
||||
from typing import Dict, Iterable, Union, BinaryIO, TextIO, Literal, Any
|
||||
from typing import Dict, Iterable, Union, BinaryIO, TextIO, Any
|
||||
from ruamel.yaml import YAML
|
||||
|
||||
# This holds the full config (both runtime config and compile-time preset), for specs to initialize
|
||||
config: Dict[str, Any] = {}
|
||||
|
||||
|
||||
# Access to overwrite spec constants based on configuration
|
||||
# This is called by the spec module after declaring its globals, and applies the loaded presets.
|
||||
def apply_constants_config(spec_globals: Dict[str, Any], warn_if_unknown: bool = False) -> None:
|
||||
global config
|
||||
for k, v in config.items():
|
||||
# the spec should have default values for everything, if not, the config key is invalid.
|
||||
if k in spec_globals:
|
||||
# Keep the same type as the default value indicates (which may be an SSZ basic type subclass, e.g. 'Gwei')
|
||||
spec_globals[k] = spec_globals[k].__class__(v)
|
||||
else:
|
||||
# Note: The phase 0 spec will not warn if Altair or later config values are applied.
|
||||
# During debugging you can enable explicit warnings.
|
||||
if warn_if_unknown:
|
||||
print(f"WARNING: unknown config key: '{k}' with value: '{v}'")
|
||||
|
||||
|
||||
# Load YAML configuration from a file path or input, or pick the default 'mainnet' and 'minimal' configs.
|
||||
# This prepares the global config memory. This does not apply the config.
|
||||
# To apply the config, reload the spec module (it will re-initialize with the config taken from here).
|
||||
def prepare_config(config_path: Union[Path, BinaryIO, TextIO, Literal['mainnet'], Literal['minimal']]) -> None:
|
||||
# Load the configuration, and try in-memory defaults.
|
||||
if config_path == 'mainnet':
|
||||
conf_data = deepcopy(mainnet_config_data)
|
||||
elif config_path == 'minimal':
|
||||
conf_data = deepcopy(minimal_config_data)
|
||||
else:
|
||||
conf_data = load_config_file(config_path)
|
||||
# Apply configuration if everything checks out
|
||||
global config
|
||||
if 'PRESET_BASE' in conf_data:
|
||||
# Check the configured preset
|
||||
base = conf_data['PRESET_BASE']
|
||||
if base not in ('minimal', 'mainnet'):
|
||||
raise Exception(f"unknown PRESET_BASE: {base}")
|
||||
config = deepcopy(mainnet_preset_data if base == 'mainnet' else minimal_preset_data)
|
||||
config.update(conf_data)
|
||||
else:
|
||||
config = conf_data
|
||||
|
||||
|
||||
def parse_config_vars(conf: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""
|
||||
|
@ -93,21 +48,14 @@ def load_config_file(config_path: Union[Path, BinaryIO, TextIO]) -> Dict[str, An
|
|||
return parse_config_vars(config_data)
|
||||
|
||||
|
||||
# Can't load these with pkg_resources, because the files are not in a package (requires `__init__.py`).
|
||||
mainnet_preset_data: Dict[str, Any]
|
||||
minimal_preset_data: Dict[str, Any]
|
||||
mainnet_config_data: Dict[str, Any]
|
||||
minimal_config_data: Dict[str, Any]
|
||||
loaded_defaults = False
|
||||
|
||||
|
||||
def load_defaults(spec_configs_path: Path) -> None:
|
||||
global mainnet_preset_data, minimal_preset_data, mainnet_config_data, minimal_config_data
|
||||
global mainnet_config_data, minimal_config_data
|
||||
|
||||
_, _, mainnet_preset_file_names = next(os.walk(spec_configs_path / 'mainnet_preset'))
|
||||
mainnet_preset_data = load_preset([spec_configs_path / 'mainnet_preset' / p for p in mainnet_preset_file_names])
|
||||
_, _, minimal_preset_file_names = next(os.walk(spec_configs_path / 'minimal_preset'))
|
||||
minimal_preset_data = load_preset([spec_configs_path / 'minimal_preset' / p for p in minimal_preset_file_names])
|
||||
mainnet_config_data = load_config_file(spec_configs_path / 'mainnet_config.yaml')
|
||||
minimal_config_data = load_config_file(spec_configs_path / 'minimal_config.yaml')
|
||||
|
||||
|
|
Loading…
Reference in New Issue