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

Final -- Improve handling of nested dependencies #181

Merged
merged 16 commits into from
Dec 13, 2021
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
11 changes: 4 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,22 +66,19 @@ Example `spock` usage is located [here](https://github.com/fidelity/spock/blob/m

See [Releases](https://github.com/fidelity/spock/releases) for more information.

#### December 13, 2021
* Refactored the backend to better handle nested dependencies (and for clarity)

#### August 17, 2021
* Added hyper-parameter tuning backend support for Ax via Service API

#### July 21, 2021
* Added hyper-parameter tuning support with `pip install spock-config[tune]`
* Hyper-parameter tuning backend support for Optuna define-and-run API (WIP for Ax)

#### May 6th, 2021
* Added S3 support with `pip install spock-config[s3]`
* S3 addon supports automatically handling loading/saving from paths defined with `s3://` URI(s) by passing in an
active `boto3.Session`


## Original Implementation

[Nicholas Cilfone](https://github.com/ncilfone), [Siddharth Narayanan](https://github.com/sidnarayanan)
___
`spock` is developed and maintained by the **Artificial Intelligence Center of Excellence at Fidelity Investments**.
`spock` was originally developed by the **Artificial Intelligence Center of Excellence at Fidelity Investments**.

3 changes: 2 additions & 1 deletion REQUIREMENTS.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
attrs==21.2.0
GitPython==3.1.18
pytomlpp==1.0.3
pyYAML==6.0
pyYAML==6.0
networkx
22 changes: 0 additions & 22 deletions spock/addons/tune/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,28 +22,6 @@ def __init__(self, *args, **kwargs):
"""
super().__init__(*args, module_name="spock.addons.tune.config", **kwargs)

def _handle_arguments(self, args, class_obj):
"""Ovverides base -- Handles all argument mapping

Creates a dictionary of named parameters that are mapped to the final type of object

*Args*:

args: read file arguments
class_obj: instance of a class obj

*Returns*:

fields: dictionary of mapped parameters

"""
attr_name = class_obj.__name__
fields = {
val.name: val.type(**args[attr_name][val.name])
for val in class_obj.__attrs_attrs__
}
return fields

@staticmethod
def _make_group_override_parser(parser, class_obj, class_name):
"""Makes a name specific override parser for a given class obj
Expand Down
192 changes: 189 additions & 3 deletions spock/args.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,193 @@
# Copyright FMR LLC <[email protected]>
# SPDX-License-Identifier: Apache-2.0

"""Handles import aliases to allow backwards compat with backends"""
"""Handles mapping config arguments to a payload with both general and class specific sets"""

# from spock.backend.dataclass.args import *
from spock.backend.typed import SavePath

from _warnings import warn

from spock.graph import Graph


class SpockArguments:
"""Class that handles mapping the read parameter dictionary to general or class level arguments

Attributes:
_arguments: dictionary of arguments

"""

def __init__(self, arguments: dict, config_dag: Graph):
"""Init call for SpockArguments class

Handles creating a clean arguments dictionary that can be cleanly mapped to spock classes

Args:
arguments: dictionary of parameters from the config file(s)
config_dag: graph of the dependencies between spock classes

"""
general_arguments = self._get_general_arguments(arguments, config_dag)
attribute_name_to_config_name_mapping = (
self._attribute_name_to_config_name_mapping(config_dag, general_arguments)
)
self._arguments = self._clean_arguments(arguments, general_arguments)
self._assign_general_arguments_to_config(
general_arguments, attribute_name_to_config_name_mapping
)

def __getitem__(self, key: str):
"""Gets value from the _arguments dictionary

Args:
key: dictionary key

Returns:
argument at the specified key

"""
return self._arguments[key]

def __iter__(self):
"""Returns the next value of the keys within the _arguments dictionary

Returns:
current key for the _arguments dictionary

"""
for key in self._arguments:
yield key

@property
def items(self):
"""Returns the k,v tuple iterator for the _arguments dictionary"""
return self._arguments.items()

@property
def keys(self):
"""Returns an iterator for the keys of the _arguments dictionary"""
return self._arguments.keys()

@property
def values(self):
"""Returns an iterator for the values of the _arguments dictionary"""
return self._arguments.values()

@staticmethod
def _get_general_arguments(arguments: dict, config_dag: Graph):
"""Creates a dictionary of config file parameters that are defined at the general level (not class specific)

Args:
arguments: dictionary of parameters from the config file(s)
config_dag: graph of the dependencies between spock classes

Returns:
dictionary of general level parameters

"""
config_names = {n.__name__ for n in config_dag.nodes}
return {
key: value
for key, value in arguments.items()
if key not in config_names and key != "config"
}

def _attribute_name_to_config_name_mapping(
self, config_dag: Graph, general_arguments: dict
):
"""Returns a mapping of names to spock config class parameter names

Args:
config_dag: graph of the dependencies between spock classes
general_arguments: dictionary of arguments at the general level

Returns:
dictionary of parameters mapped to spock classes

"""
attribute_name_to_config_name_mapping = {}
for n in config_dag.nodes:
for attr in n.__attrs_attrs__:
if attr.name in general_arguments:
if self._is_duplicated_key(
attribute_name_to_config_name_mapping, attr.name, n.__name__
):
raise SpockDuplicateArgumentError(
f"`{attr.name}` key is located in more than one config and cannot be resolved automatically."
f"Either specify the config name (`<config>.{attr.name}`) or change the key name in the config."
)
attribute_name_to_config_name_mapping[attr.name] = n.__name__

return attribute_name_to_config_name_mapping

@staticmethod
def _is_duplicated_key(
attribute_name_to_config_name_mapping: dict, attr_name: str, config_name: str
):
"""Checks if a duplicated key exists in multiple classes

Args:
attribute_name_to_config_name_mapping: dictionary of class specific name mappings
attr_name:
config_name:

Returns:
boolean if duplicated

"""
return (
attr_name in attribute_name_to_config_name_mapping
and attribute_name_to_config_name_mapping[attr_name] != config_name
)

def _assign_general_arguments_to_config(
self, general_arguments: dict, attribute_name_to_config_name_mapping: dict
):
"""Assigns the values from general definitions to values within specific classes if the specific definition
doesn't exist

Args:
general_arguments:
attribute_name_to_config_name_mapping:

Returns:
None
"""
for arg, value in general_arguments.items():
config_name = attribute_name_to_config_name_mapping[arg]
if config_name in self._arguments:
# Specific arguments supersede general arguments
if arg not in self._arguments[config_name]:
self._arguments[config_name][arg] = value
else:
warn(
f"Ignoring general argument `{arg}` for config `{config_name}`\n"
f"Specific argument value preceded general arguments.",
SyntaxWarning,
)
else:
self._arguments[config_name] = {arg: value}

@staticmethod
def _clean_arguments(arguments: dict, general_arguments: dict):
"""Sets up a clean dictionary for those not in the general dictionary

Args:
arguments: dictionary of all arguments
general_arguments: dictionary of general level arguments

Returns:
clean dictionary of parameters not at the general level

"""
clean_arguments = {}
for arg, value in arguments.items():
if arg not in general_arguments:
clean_arguments[arg] = value
return clean_arguments


class SpockDuplicateArgumentError(Exception):
"""Custom exception type for duplicated values"""

pass
14 changes: 13 additions & 1 deletion spock/backend/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,16 @@
Please refer to the documentation provided in the README.md
"""

__all__ = ["builder", "config", "payload", "saver", "typed"]
__all__ = [
"builder",
"config",
"field_handlers",
"handler",
"help",
"payload",
"saver",
"spaces",
"typed",
"utils",
"wrappers",
]
Loading