Skip to content

Commit

Permalink
Merge branch 'master' into dependabot/pip/attrs-approx-eq-21.4
Browse files Browse the repository at this point in the history
  • Loading branch information
ncilfone committed Feb 14, 2022
2 parents ef4e0d2 + adbce2c commit f0d415b
Show file tree
Hide file tree
Showing 93 changed files with 1,918 additions and 4,678 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
# Debugging folder
debug/

# Auto-gen Reference Docs
website/docs/reference/**/*.md

# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
Expand Down
1 change: 1 addition & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
include versioneer.py
include spock/_version.py
include "README.md", "LICENSE.txt", "NOTICE.txt", "CONTRIBUTING.md"
116 changes: 78 additions & 38 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,48 +1,79 @@
[![Spock](https://raw.githubusercontent.com/fidelity/spock/master/resources/images/logo.png)](https://fidelity.github.io/spock/)
> Managing complex configurations any other way would be highly illogical...
[![License](https://img.shields.io/badge/License-Apache%202.0-9cf)](https://opensource.org/licenses/Apache-2.0)
[![Python](https://img.shields.io/badge/python-3.6+-informational.svg)](https://www.python.org/)
[![Style](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
[![PyPI version](https://badge.fury.io/py/spock-config.svg)](https://badge.fury.io/py/spock-config)
[![Coverage Status](https://coveralls.io/repos/github/fidelity/spock/badge.svg?branch=master)](https://coveralls.io/github/fidelity/spock?branch=master)
![Tests](https://github.com/fidelity/spock/workflows/pytest/badge.svg?branch=master)
![Docs](https://github.com/fidelity/spock/workflows/docs/badge.svg)
<html>
<h1 align="center">
<a href="https://fidelity.github.io/spock/"><img src="https://raw.githubusercontent.com/fidelity/spock/master/resources/images/logo.png"/></a>
<h6 align="center">Managing complex configurations any other way would be highly illogical...</h6>
</h1>

<p align="center">
<a href="https://opensource.org/licenses/Apache-2.0"><img src="https://img.shields.io/badge/License-Apache%202.0-9cf"/></a>
<a href="https://bestpractices.coreinfrastructure.org/projects/5551"><img src="https://bestpractices.coreinfrastructure.org/projects/5551/badge"/></a>
<a><img src="https://img.shields.io/badge/python-3.6+-informational.svg"/></a>
<a href="https://github.com/psf/black"><img src="https://img.shields.io/badge/code%20style-black-000000.svg"/></a>
<a href="https://badge.fury.io/py/spock-config"><img src="https://badge.fury.io/py/spock-config.svg"/></a>
<a href="https://coveralls.io/github/fidelity/spock?branch=master"><img src="https://coveralls.io/repos/github/fidelity/spock/badge.svg?branch=master"/></a>
<a><img src="https://github.com/fidelity/spock/workflows/pytest/badge.svg?branch=master"/></a>
<a><img src="https://github.com/fidelity/spock/workflows/docs/badge.svg"/></a>
</p>

<h3 align="center">
<a href="https://fidelity.github.io/spock/Quick-Start">Quick Start</a>
<span> · </span>
<a href="https://fidelity.github.io/spock/">Documentation</a>
<span> · </span>
<a href="https://github.com/fidelity/spock/blob/master/examples">Examples</a>
<span> · </span>
<a href="https://github.com/fidelity/spock/releases">Releases</a>
</h3>

</html>

## About

`spock` is a framework that helps manage complex parameter configurations during research and development of Python
applications. `spock` lets you focus on the code you need to write instead of re-implementing boilerplate code like
creating ArgParsers, reading configuration files, implementing traceability etc.
`spock` is a framework that helps users easily define, manage, and use complex parameter configurations within Python
applications. It lets you focus on the code you need to write instead of re-implementing boilerplate code such as
creating ArgParsers, reading configuration files, handling dependencies, implementing type validation,
maintaining traceability, etc.

`spock` configurations are normal python classes that are decorated with `@spock`. It supports
inheritance, dynamic class dependencies, loading/saving configurations from/to multiple markdown formats, automatically
generating CLI arguments, and hierarchical configuration by composition.

## 💥 Why You Should Use Spock 💥

In short, `spock` configurations are defined by simple and familiar class-based structures. This allows `spock` to
support inheritance, read from multiple markdown formats, automatically generate cmd-line arguments, and allow
hierarchical configuration by composition.
* Simple organized parameter definitions (i.e. a single line)
* Type checked (static-eqsue) & frozen parameters (i.e. fail early during long ML training runs)
* Complex parameter dependencies made simple (i.e. `@spock` class with a parameter that is an Enum of other
`@spock` classes)
* Fully serializable parameter state(s) (i.e. exactly reproduce prior runtime parameter configurations)
* Automatic type checked CLI generation w/o argparser boilerplate (i.e click and/or typer for free!)
* Easily maintain parity between CLIs and Python APIs (i.e. single line changes between CLI and Python API definitions)
* Unified hyper-parameter definitions and interface (i.e. don't write different definitions for Ax or Optuna)

## Key Features

* [Simple Declaration](https://fidelity.github.io/spock/docs/basic_tutorial/Define/): Type checked parameters are
* [Simple Declaration](https://fidelity.github.io/spock/basics/Define): Type checked parameters are
defined within a `@spock` decorated class. Supports required/optional and automatic defaults.
* Easily Managed Parameter Groups: Each class automatically generates its own object within a single namespace.
* [Parameter Inheritance](https://fidelity.github.io/spock/docs/advanced_features/Inheritance/): Classes support
inheritance allowing for complex configurations derived from a common base set of parameters.
* [Complex Types](https://fidelity.github.io/spock/docs/advanced_features/Advanced-Types/): Nested Lists/Tuples,
* [Parameter Inheritance](https://fidelity.github.io/spock/advanced_features/Inheritance): Classes support
inheritance (w/ lazy evaluation of inheritance/dependencies) allowing for complex configurations derived from
a common base set of parameters.
* [Complex Types](https://fidelity.github.io/spock/advanced_features/Advanced-Types/): Nested Lists/Tuples,
List/Tuples of Enum of `@spock` classes, List of repeated `@spock` classes
* Multiple Configuration File Types: Configurations are specified from YAML, TOML, or JSON files.
* [Hierarchical Configuration](https://fidelity.github.io/spock/docs/advanced_features/Composition/): Compose from
* [Hierarchical Configuration](https://fidelity.github.io/spock/advanced_features/Composition/): Compose from
multiple configuration files via simple include statements.
* [Command-Line Overrides](https://fidelity.github.io/spock/docs/advanced_features/Command-Line-Overrides/): Quickly
* [Command-Line Overrides](https://fidelity.github.io/spock/advanced_features/Command-Line-Overrides/): Quickly
experiment by overriding a value with automatically generated command line arguments.
* Immutable: All classes are *frozen* preventing any misuse or accidental overwrites (to the extent they can be in
Python).
* [Tractability and Reproducibility](https://fidelity.github.io/spock/docs/basic_tutorial/Saving/): Save runtime
* [Tractability and Reproducibility](https://fidelity.github.io/spock/basics/Saving): Save runtime
parameter configuration to YAML, TOML, or JSON with a single chained command (with extra runtime info such as Git info,
Python version, machine FQDN, etc). The saved markdown file can be used as the configuration input to reproduce
prior runtime configurations.
* [Hyper-Parameter Tuner Addon](https://fidelity.github.io/spock/docs/addons/tuner/About): Provides a unified
* [Hyper-Parameter Tuner Addon](https://fidelity.github.io/spock/addons/tuner/About): Provides a unified
interface for defining hyper-parameters (via `@spockTuner` decorator) that supports various tuning/algorithm
backends (currently: Optuna, Ax)
* [S3 Addon](https://fidelity.github.io/spock/docs/addons/S3/): Automatically detects `s3://` URI(s) and handles loading
* [S3 Addon](https://fidelity.github.io/spock/addons/S3): Automatically detects `s3://` URI(s) and handles loading
and saving `spock` configuration files when an active `boto3.Session` is passed in (plus any additional
`S3Transfer` configurations)

Expand All @@ -54,32 +85,41 @@ The basic install and `[s3]` extension require Python 3.6+ while the `[tune]` ex
|------|-----------------|--------------------------|
| `pip install spock-config` | `pip install spock-config[s3]` | `pip install spock-config[tune]` |

## Quick Start & Documentation
## News/Releases

Refer to the quick-start guide [here](https://fidelity.github.io/spock/docs/Quick-Start/).
See [Releases](https://github.com/fidelity/spock/releases) for more information.

Current documentation and more information can be found [here](https://fidelity.github.io/spock/).
<html>
<h2 id="features">
Recent Changes
</h2>
</html>

Example `spock` usage is located [here](https://github.com/fidelity/spock/blob/master/examples).
<details>

## News/Releases
#### January 26th, 2022
* Added `evolve` support to the underlying `SpockBuilder` class. This provides functionality similar to the underlying
attrs library ([attrs.evolve](https://www.attrs.org/en/stable/api.html#attrs.evolve)). `evolve()` creates a new
`Spockspace` instance based on differences between the underlying declared state and any passed in instantiated
`@spock` decorated classes.

See [Releases](https://github.com/fidelity/spock/releases) for more information.
#### January 18th, 2022
* Support for lazy evaluation: (1) inherited classes do not need to be `@spock` decorated, (2) dependencies/references
between `spock` classes can be lazily handled thus preventing the need for every `@spock` decorated classes to be
passed into `*args` within the main `SpockBuilder` API
* Updated main API interface for better top-level imports (backwards compatible): `ConfigArgBuilder`->`SpockBuilder`
* Added stubs to the underlying decorator that should help with type hinting in VSCode (pylance/pyright)

#### December 14, 2021
* Refactored the backend to better handle nested dependencies (and for clarity)
* Refactored the docs to use Docusaurus

#### 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)
</details>

## Original Implementation

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


6 changes: 3 additions & 3 deletions examples/quick-start/simple.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from typing import List

from spock.builder import ConfigArgBuilder
from spock.config import spock
from spock import SpockBuilder
from spock import spock


@spock
Expand Down Expand Up @@ -47,7 +47,7 @@ def add_by_parameter(multiply_param, list_vals, add_param, tf_round):

def main():
# Chain the generate function to the class call
config = ConfigArgBuilder(BasicConfig, desc="Quick start example").generate()
config = SpockBuilder(BasicConfig, desc="Quick start example").generate()
# One can now access the Spock config object by class name with the returned namespace
print(config.BasicConfig.parameter)
# And pass the namespace to our first function
Expand Down
6 changes: 3 additions & 3 deletions examples/tune/ax/tune.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@
RangeHyperParameter,
spockTuner,
)
from spock.builder import ConfigArgBuilder
from spock.config import spock
from spock import SpockBuilder
from spock import spock


@spock
Expand Down Expand Up @@ -46,7 +46,7 @@ def main():
# Use the builder to setup
# Call tuner to indicate that we are going to do some HP tuning -- passing in an ax study object
attrs_obj = (
ConfigArgBuilder(
SpockBuilder(
LogisticRegressionHP,
BasicParams,
desc="Example Logistic Regression Hyper-Parameter Tuning -- Ax Backend",
Expand Down
6 changes: 3 additions & 3 deletions examples/tune/optuna/tune.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@
RangeHyperParameter,
spockTuner,
)
from spock.builder import ConfigArgBuilder
from spock.config import spock
from spock import SpockBuilder
from spock import spock


@spock
Expand Down Expand Up @@ -48,7 +48,7 @@ def main():
# Use the builder to setup
# Call tuner to indicate that we are going to do some HP tuning -- passing in an optuna study object
attrs_obj = (
ConfigArgBuilder(
SpockBuilder(
LogisticRegressionHP,
BasicParams,
desc="Example Logistic Regression Hyper-Parameter Tuning -- Optuna Backend",
Expand Down
8 changes: 3 additions & 5 deletions examples/tutorial/advanced/tutorial.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,8 @@
import torch
from basic_nn import BasicNet

from spock.args import SavePath
from spock.builder import ConfigArgBuilder
from spock.config import spock
from spock import SpockBuilder
from spock import spock


class Activation(Enum):
Expand All @@ -22,7 +21,6 @@ class Optimizer(Enum):

@spock
class ModelConfig:
save_path: SavePath
n_features: int
dropout: Optional[List[float]]
hidden_sizes: Tuple[int, int, int] = (32, 32, 32)
Expand Down Expand Up @@ -90,7 +88,7 @@ def main():
# A simple description
description = "spock Advanced Tutorial"
# Build out the parser by passing in Spock config objects as *args after description
config = ConfigArgBuilder(
config = SpockBuilder(
ModelConfig, DataConfig, SGDConfig, desc=description
).generate()
# Instantiate our neural net using
Expand Down
8 changes: 3 additions & 5 deletions examples/tutorial/basic/tutorial.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,8 @@
import torch
from basic_nn import BasicNet

from spock.args import SavePath
from spock.builder import ConfigArgBuilder
from spock.config import spock
from spock import SpockBuilder
from spock import spock


class Activation(Enum):
Expand Down Expand Up @@ -35,7 +34,6 @@ class ModelConfig:
activation: choice from the Activation enum of the activation function to use
"""

save_path: SavePath
n_features: int
dropout: List[float]
hidden_sizes: Tuple[int, int, int]
Expand All @@ -47,7 +45,7 @@ def main():
description = "spock Basic Tutorial"
# Build out the parser by passing in Spock config objects as *args after description
config = (
ConfigArgBuilder(ModelConfig, desc=description, create_save_path=True)
SpockBuilder(ModelConfig, desc=description, create_save_path=True)
.save(file_extension=".toml")
.generate()
)
Expand Down
9 changes: 5 additions & 4 deletions requirements/DEV_REQUIREMENTS.txt
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
black~=21.4b0
black~=22.1 ; python_version >= '3.7'
black~=21.4b0 ; python_version == '3.6'
coveralls~=3.3
coverage~=6.1
isort~=5.10
moto~=2.3
moto~=3.0
pydoc-markdown~=4.3 ; python_version >= '3.7'
pydoc-markdown~=3.13 ; python_version == '3.6'
pytest~=6.2
pytest~=7.0
pytest-cov~=3.0
pylint~=2.11
pylint~=2.11
4 changes: 4 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,10 @@
packages=setuptools.find_packages(
exclude=["*.tests", "*.tests.*", "tests.*", "tests"]
),
package_data={
"": ["py.typed", "*.pyi"],
},
include_package_data=True,
python_requires=">=3.6",
install_requires=install_reqs,
extras_require={"s3": s3_reqs, "tune": tune_reqs},
Expand Down
7 changes: 6 additions & 1 deletion spock/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,13 @@
"""

from spock._version import get_versions
from spock.backend.typed import SavePath
from spock.builder import ConfigArgBuilder
from spock.config import spock

__all__ = ["args", "builder", "config"]
SpockBuilder = ConfigArgBuilder

__all__ = ["args", "builder", "config", "SavePath", "spock", "SpockBuilder"]

__version__ = get_versions()["version"]
del get_versions
54 changes: 54 additions & 0 deletions spock/__init__.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
from typing import Any, Callable, List, Optional, Tuple, TypeVar, Union, overload

from attr import attrib, field

from spock.backend.typed import SavePath
from spock.builder import ConfigArgBuilder

_T = TypeVar("_T")
_C = TypeVar("_C", bound=type)
SavePath: SavePath

# Note: from here
# https://github.com/python-attrs/attrs/blob/main/src/attr/__init__.pyi

# Static type inference support via __dataclass_transform__ implemented as per:
# https://github.com/microsoft/pyright/blob/main/specs/dataclass_transforms.md
# This annotation must be applied to all overloads of "spock_attr"

# NOTE: This is a typing construct and does not exist at runtime. Extensions
# wrapping attrs decorators should declare a separate __dataclass_transform__
# signature in the extension module using the specification linked above to
# provide pyright support -- this currently doesn't work in PyCharm
def __dataclass_transform__(
*,
eq_default: bool = True,
order_default: bool = False,
kw_only_default: bool = False,
field_descriptors: Tuple[Union[type, Callable[..., Any]], ...] = (()),
) -> Callable[[_T], _T]: ...
@overload
@__dataclass_transform__(kw_only_default=True, field_descriptors=(attrib, field))
def spock(
maybe_cls: _C,
kw_only: bool = True,
make_init: bool = True,
dynamic: bool = False,
) -> _C: ...
@overload
@__dataclass_transform__(kw_only_default=True, field_descriptors=(attrib, field))
def spock(
maybe_cls: None = ...,
kw_only: bool = True,
make_init: bool = True,
dynamic: bool = False,
) -> Callable[[_C], _C]: ...
def SpockBuilder(
*args: _C,
configs: Optional[List] = None,
desc: str = "",
lazy: bool = False,
no_cmd_line: bool = False,
s3_config: Optional[_C] = None,
**kwargs,
) -> ConfigArgBuilder: ...
Loading

0 comments on commit f0d415b

Please sign in to comment.