Repository: bbux-dev/datacraft (language: python)

Issue:
Registry defaults



Discussion / hints:

Fix patch:
diff --git a/.gitignore b/.gitignore
index 3f894fa..fe6bf73 100644
--- a/.gitignore
+++ b/.gitignore
@@ -92,3 +92,5 @@ ENV/
 # intellij
 .idea
 
+# mypy
+.mypy_cache
diff --git a/.travis.yml b/.travis.yml
index 7fb14f7..3866c5b 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -9,11 +9,12 @@ before_install:
   - pip install -U pip
   - pip install -U pytest
   - pip install -U pytest-cov
-  - pip install codecov
+  - pip install codecov mypy types-Jinja2
 install:
   - pip install -r requirements.txt
   - pip install .
 script:
   - pytest --cov=dataspec
+  - mypy --show-error-codes dataspec/
 after_success:
   - codecov # submit coverage
diff --git a/README.md b/README.md
index 667d577..b923b50 100644
--- a/README.md
+++ b/README.md
@@ -7,6 +7,8 @@ Data Spec Repository
 1. [Overview](#Overview)
 1. [Build](#Build)
 1. [Usage](#Usage)
+   1. [Example Usage](#ExampleUsage)
+   1. [Useful Flags](#UsefulFlags)
 1. [Examples](#Examples)
 1. [Core Concepts](#Core_Concepts)
     1. [Data Spec](#Data_Spec)
@@ -18,17 +20,33 @@ Data Spec Repository
         1. [Loops in Templates](#Templating_Loops)
         1. [Dynamic Loop Counters](#Dynamic_Loop_Counters)
     1. [Custom Code Loading](#Custom_Code_Loading)
+1. [Programmatic Usage](#Programmatic)
+    1. [Building Specs](#BuildSpecs)
+    1. [Generating Records](#Generator)
 
 ## <a name="Overview"></a>Overview
 
-This is a tool for making data according to specifications. The goal is to separate the structure of the data from the
-values that populate it. We do this by defining two core concepts the Data Spec and the Field Spec. A Data Spec is used
-to define all of the fields that should be generated for a record. The Data Spec does not care about the structure that
-the data will populate. A single Data Spec could be used to generate JSON, XML, or a csv file. Each field in the Data
-Spec has its own Field Spec that defines how the values for it should be created. There are a variety of core field
-types that are used to generate the data for each field. Where the built-in types are not sufficient, there is an easy
-way to create custom types and handlers for them. The tool supports templating using
-the [Jinja2](https://pypi.org/project/Jinja2/) templating engine format.
+This is a tool for making data according to specifications. The goal is to
+separate the structure of the data from the values that populate it. We do this
+by defining two core concepts the Data Spec and the Field Spec. A Data Spec is
+used to define all of the fields that should be generated for a record. The Data
+Spec does not care about the structure that the data will populate. A single
+Data Spec could be used to generate JSON, XML, or a csv file. Each field in the
+Data Spec has its own Field Spec that defines how the values for it should be
+created. There are a variety of core field types that are used to generate the
+data for each field. Where the built-in types are not sufficient, there is an
+easy way to create custom types and handlers for them. The tool supports
+templating using the [Jinja2](https://pypi.org/project/Jinja2/) templating
+engine format.
+
+Data is a key part of any application. Synthetic data can be used to test and
+exercise a system while it is under development or modification. By using a Data
+Spec to generate this synthetic data, it is more compact and easier to modify,
+update and manage. It also lends itself to sharing. Instead of hosting large
+data files full of synthetic test data, you can build Data Specs that
+encapsulate the information needed to generate the data. If well-designed, these
+can be easier to inspect and reason through compared with scanning thousands of
+lines of a csv file.
 
 ## <a name="Build"></a>Build
 
@@ -38,13 +56,14 @@ To Install:
 pip install git+https://github.com/bbux-dev/dataspec.git
 ```
 
-This will install the `dataspec` command line utility which should now be on your path
+This will install the `dataspec` command line utility which should now be on
+your path. Assumes there is a python executable on the path.
 
 ## <a name="Usage"></a>Usage
 
 ```
-usage: dataspec [-h] [-s SPEC] [--inline INLINE] [-i ITERATIONS] [-o OUTDIR] [-p OUTFILEPREFIX] [-e EXTENSION] [-t TEMPLATE] [-r RECORDSPERFILE] [-k] 
-                [-c CODE [CODE ...]] [-d DATADIR] [-l LOG_LEVEL] [-f FORMAT] [--strict] [--debug-spec]
+usage: dataspec [-h] [-s SPEC] [--inline INLINE] [-i ITERATIONS] [-o OUTDIR] [-p OUTFILEPREFIX] [-e EXTENSION] [-t TEMPLATE] [-r RECORDSPERFILE] [-k] [-c CODE [CODE ...]]
+                [-d DATADIR] [-l LOG_LEVEL] [-f FORMAT] [--strict] [--apply-raw] [--debug-spec] [--debug-defaults] [-x] [--sample-lists] [--defaults DEFAULTS]
 
 Run dataspec.
 
@@ -72,17 +91,24 @@ optional arguments:
   -f FORMAT, --format FORMAT
                         Formatter for output records, default is none, valid are: ['json', 'json-pretty', 'csv']
   --strict              Enforce schema validation for all registered field specs
+  --apply-raw           Data from -s argument should be applied to the template with out treating as a Data Spec
   --debug-spec          Debug spec after internal reformatting
+  --debug-defaults      List default values from registry after any external code loading
+  -x, --exclude-internal
+                        Do not include non data fields in output records
+  --sample-lists        Turns on sampling for all list backed types
+  --defaults DEFAULTS   Path to defaults overrides
 
 input:
   -s SPEC, --spec SPEC  Spec to Use
   --inline INLINE       Spec as string
 ```
 
-### Example Usage
+### <a name="ExampleUsage"></a>Example Usage
 
-The most common way to use `dataspec` is to define the field specifications in a JSON or YAML file and to specify this file with
-the --spec command line argument:
+The most common way to use `dataspec` is to define the field specifications in a
+JSON or YAML file and to specify this file with the --spec command line
+argument:
 
 ```shell
 dataspec --spec /path/to/dataspec.json \
@@ -91,8 +117,9 @@ dataspec --spec /path/to/dataspec.json \
   --output /path/to/output
 ```
 
-The command above will generate 1000 records and apply the generated values to the supplied template which will be
-output to the specified directory. The default is to write all outputs to a single file.  Use the `-r` or
+The command above will generate 1000 records and apply the generated values to
+the supplied template which will be output to the specified directory. The
+default is to write all outputs to a single file. Use the `-r` or
 `--records-per-file` command line argument to modify this if desired.
 
 An alternative way to specify the data for a spec is by using the `--inline` argument:
@@ -112,11 +139,11 @@ INFO [12-Mar-2050 06:24:58 PM] Finished Processing
 
 This can be useful for troubleshooting or experimenting
 
-### Useful Flags
+### <a name="UsefulFlags"></a>Useful Flags
 
 #### Inline JSON/YAML
-The default is to output the generated values to the console. Use the `-k` or `--printkey` arg to print out the key
-along with the value:
+
+To test small spec fragments, you can use the `--inline <spec>` flag. Example:
 
 ```shell
 dataspec --inline '{ "handle": { "type": "cc-word", "config": {"min": 3, "mean": 5, "prefix": "@" } } }' \
@@ -131,15 +158,18 @@ handle -> @jf7Ax
 ```
 #### Log Levels
 
-You can change the logging levels to one of `debug, info, warn, error, or off` by using the `-l` or `--log-level` flag.
-See example above.
+You can change the logging levels to one of `debug, info, warn, error, or off`
+by using the `-l` or `--log-level` flag. See example above.
 
 #### Formatting Output
 
-Sometimes it may be useful to dump the generated data into a format that is easier to consume or view. Use the `-f` or `--format` flag to specify one
-of `json` or `json-pretty` or `csv`. The `json` format will print a flat version of each record that takes up a single line for each iteration. The
-`json-pretty` format will print an indented version of each record that will span multiple lines. The `csv` format will output each record as a comma
-separated value line.  Examples:
+Sometimes it may be useful to dump the generated data into a format that is
+easier to consume or view. Use the `-f` or `--format` flag to specify one
+of `json` or `json-pretty` or `csv`. The `json` format will print a flat version
+of each record that takes up a single line for each iteration. The
+`json-pretty` format will print an indented version of each record that will
+span multiple lines. The `csv` format will output each record as a comma
+separated value line. Examples:
 
 ```shell
 # NOTE: This inline spec is in YAML
@@ -165,10 +195,19 @@ dataspec --inline '{"id:uuid": {}, "handle": { "type": "cc-word", "config": {"mi
 d97e8dad-8dfd-49f1-b25e-eaaf2d6953fd,@IYn
 ```
 
+#### <a name="ApplyRaw"></a>Apply Raw `--apply-raw`
+
+The `--apply-raw` command line flag will treat the argument of the `-s` flag as
+the raw-data that should be applied to the template. This can be helpful when
+working on adjusting the template that is being generated. You can dump the
+generated data from N iterations using the `--format json`
+or `--format json-pretty` then use this as raw input to the template file.
+
 #### Debugging Specifications
 
-There are a bunch of shorthand formats for creating specifications.  These ultimately get turned into a full spec format.
-It may be useful to see what the full spec looks like after all the transformations have taken place.  Use the
+There are a bunch of shorthand formats for creating specifications. These
+ultimately get turned into a full spec format. It may be useful to see what the
+full spec looks like after all the transformations have taken place. Use the
 `--debug-spec` to dump the internal form of the specification for inspection.
 
 ```shell
@@ -187,9 +226,10 @@ dataspec --inline 'geo:geo.pair?start_lat=-99.0: {}' \
 
 #### Schema Level Validation
 
-Most of the default supported field spec types have JSON based schemas defined for them. Schema based validation is
-turned off by default.  Use the `--strict` command line flag to turn on the strict schema based checks for types that
-have schemas defined.  Example:
+Most of the default supported field spec types have JSON based schemas defined
+for them. Schema based validation is turned off by default. Use the `--strict`
+command line flag to turn on the strict schema based checks for types that have
+schemas defined. Example:
 
 ```shell
 dataspec --inline 'geo: {type: geo.pair, config: {start_lat: -99.0}}' \
@@ -203,20 +243,75 @@ WARNING [12-Mar-2050 07:24:11 PM] -99.0 is less than the minimum of -90
 ERROR [12-Mar-2050 07:24:11 PM] Failed to validate spec type: geo.pair with spec: {'type': 'geo.pair', 'config': {'start_lat': -99.0}}
 ```
 
+#### Default Values
+
+There are some default values used when a given spec does not provide them.
+These defaults can be viewed using the `--debug-defaults` flag.
+
+```shell
+dataspec --debug-defaults -l off
+{
+    "sample_mode": false,
+    "combine_join_with": "",
+    "char_class_join_with": "",
+    "geo_as_list": false,
+    "combine_as_list": false,
+    "geo_lat_first": false,
+    "geo_join_with": ",",
+    "date_stddev_days": 15,
+    "date_format": "%d-%m-%Y",
+    "geo_precision": 4,
+    "json_indent": 4
+}
+```
+
+The general convention is to use the type as a prefix for the key that it
+effects. You can save this information to disk by specifying the `-o`
+or `--outdir` flag. In the output above the default `join_with` config param is
+a comma for the `geo` type, but is an empty string for the `combine`
+and `char_class` types.
+
+#### Override Defaults
+
+To override the default values, use the `--defaults` /path/to/custom_defaults.json
+
+```shell
+dataspec --debug-defaults -l off --defaults /path/to/custom_defaults.json
+{
+    "sample_mode": true,
+    "combine_join_with": ";",
+    "char_class_join_with": "-",
+    "geo_as_list": true,
+    "combine_as_list": false,
+    "geo_lat_first": false,
+    "geo_join_with": ",",
+    "date_stddev_days": 42,
+    "date_format": "%Y-%m-%d",
+    "geo_precision": 5,
+    "json_indent": 2,
+    "custom_thing": "foo"
+}
+```
+
 ## <a name="Examples"></a>Examples
 
-See [examples](docs/EXAMPLES.md) to dive into detailed examples and practical use cases.
+See [examples](docs/EXAMPLES.md) to dive into detailed example and practical use
+case.
 
 ## <a name="Core_Concepts"></a>Core Concepts
 
 ### <a name="Data_Spec"></a>Data Spec
 
-A Data Spec is a Dictionary where the keys are the names of the fields to generate and each value is
-a [Field Spec](docs/FIELDSPECS.md)
-that describes how the values for that field are to be generated. There is one reserved key in the root of the Data
-Spec: refs. The refs is a special section of the Data Spec where Field Specs are defined but not tied to any specific
-field. These refs can then be used or referenced by other Specs. An example would be a combine Spec which points to two
-references that should be joined. Below is an example Data Spec for creating email addresses.
+A Data Spec is a Dictionary where the keys are the names of the fields to
+generate and each value is a [Field Spec](docs/FIELDSPECS.md)
+that describes how the values for that field are to be generated. There are two
+reserved keys in the root of the Data Spec: `refs` and `field_groups`.
+The `refs` is a special section of the Data Spec where Field Specs are defined
+but not tied to any specific field. These refs can then be used or referenced by
+other Specs. An example would be a combine Spec which points to two references
+that should be joined. The `field_groups` is a mechanism to generate subsets of
+defined fields in different ways. Below is an example Data Spec for creating
+email addresses.
 
 ```json
 {
@@ -247,10 +342,12 @@ references that should be joined. Below is an example Data Spec for creating ema
 }
 ```
 
-This Data Spec uses two Combine Specs to build up the pieces for the email address. The first Combine Spec lives in the
-Refs section. This one creates the user name or handle by combining the values generated by the ANIMALS Ref with the
-ACTIONS one. The email field then combines the HANDLE Ref with the DOMAINS one. See [Field Specs](docs/FIELDSPECS.md)
-for more details on each of the Field Specs used in this example.
+This Data Spec uses two Combine Specs to build up the pieces for the email
+address. The first Combine Spec lives in the Refs section. This one creates the
+user name or handle by combining the values generated by the ANIMALS Ref with
+the ACTIONS one. The email field then combines the HANDLE Ref with the DOMAINS
+one. See [Field Specs](docs/FIELDSPECS.md) for more details on each of the Field
+Specs used in this example.
 
 Running dataspec from the command line against this spec:
 
@@ -272,7 +369,7 @@ flamingo_jump@gmail.com
 
 #### <a name="YAML_Format"></a>YAML Format
 
-Dats specs can also be created using YAML. Below is the same spec above in YAML.
+Data specs can also be created using YAML. Below is the same spec above in YAML.
 
 ```yaml
 ---
@@ -311,10 +408,11 @@ See [field specs](docs/FIELDSPECS.md) and [schemas](docs/SCHEMAS.md) for details
 
 ### <a name="FieldGroups"></a>Field Groups
 
-Field groups provide a mechanism to generate different subsets of the defined fields together. This can be useful when
-modeling data that contains field that are not present in all records. There are several formats that are supported
-for Field Groups. Field Groups are defined in a root section of the document named `field_groups`. Below is an example
-spec with no `field_groups` defined.
+Field groups provide a mechanism to generate different subsets of the defined
+fields together. This can be useful when modeling data that contains field that
+are not present in all records. There are several formats that are supported for
+Field Groups. Field Groups are defined in a root section of the document
+named `field_groups`. Below is an example spec with no `field_groups` defined.
 
 ```json
 {
@@ -328,8 +426,9 @@ spec with no `field_groups` defined.
 }
 ```
 
-If the tag field was only present in 50% of the data, we would want to be able to adjust our output to match this. Here
-is an updated version of the spec with the `field_groups` specified to give us our 50/50 output. This uses the first
+If the tag field was only present in 50% of the data, we would want to be able
+to adjust our output to match this. Here is an updated version of the spec with
+the `field_groups` specified to give us our 50/50 output. This uses the first
 form of the `field_groups` a List of Lists of field names to output together.
 
 ```json
@@ -348,8 +447,9 @@ form of the `field_groups` a List of Lists of field names to output together.
 }
 ```
 
-If we need more precise weightings we can use the second format where we specify a weight for each field group along
-with the fields that should be output together.
+If we need more precise weightings we can use the second format where we specify
+a weight for each field group along with the fields that should be output
+together.
 
 ```json
 {
@@ -369,7 +469,8 @@ with the fields that should be output together.
 }
 ```
 
-The keys of the `field_groups` dictionary are arbitrary. The `weight` and `fields` element underneath are required.
+The keys of the `field_groups` dictionary are arbitrary. The `weight`
+and `fields` element underneath are required.
 
 Running this example:
 
@@ -387,7 +488,8 @@ dataspec -s pets.json -i 10 -l off -x --format json
 {"id": 10, "name": "Fluffy", "tag": "Affectionate"}
 ```
 
-The final form is a variation on form 2. Here the `field_groups` value is a dictionary of name to fields list. i.e.:
+The final form is a variation on form 2. Here the `field_groups` value is a
+dictionary of name to fields list. i.e.:
 
 ```json
 { 
@@ -406,18 +508,22 @@ The final form is a variation on form 2. Here the `field_groups` value is a dict
 
 #### Processing Large CSVs
 
-There are [Field Specs](https://github.com/bbux-dev/dataspec/blob/main/docs/FIELDSPECS.md#CSV_Data) that support using
-csv data to feed the data generation process. If the input CSV file is very large, not all features will be supported.
-You will not be able to set sampling to true or use a field count > 1. The maximum number of iterations will be equal to
-the size of the smallest number of lines for all the large input CSV file. The current size threshold is set to 250 MB.
-So, if you are using two different csv files as inputs and one is 300 MB with 5 million entries and another is 500 MB
-with 2 million entries, you will be limited to 2 million iterations before an exception will be raised and processing
-will cease.
+There
+are [Field Specs](https://github.com/bbux-dev/dataspec/blob/main/docs/FIELDSPECS.md#CSV_Data)
+that support using csv data to feed the data generation process. If the input
+CSV file is very large, not all features will be supported. You will not be able
+to set sampling to true or use a field count > 1. The maximum number of
+iterations will be equal to the size of the smallest number of lines for all the
+large input CSV file. The current size threshold is set to 250 MB. So, if you
+are using two different csv files as inputs and one is 300 MB with 5 million
+entries and another is 500 MB with 2 million entries, you will be limited to 2
+million iterations before an exception will be raised and processing will cease.
 
 #### More efficient processing using csv_select
 
-A common process is to select subsets of the columns from a csv file to use. The `csv_select` type makes this more
-efficient than using the standard `csv` type. Below is an example that will Convert data from
+A common process is to select subsets of the columns from a csv file to use.
+The `csv_select` type makes this more efficient than using the standard `csv`
+type. Below is an example that will Convert data from
 the [Geonames](http://www.geonames.org/) [allCountries.zip](http://download.geonames.org/export/dump/allCountries.zip)
 dataset by selecting a subset of the columns from the tab delimited file.
 
@@ -445,7 +551,7 @@ dataspec --spec csv-select.yaml \
          --iterations 5  \
          --datadir ./data \
          --format json \
-         --log-level off
+         --log-level off -x
 {"geonameid": "2986043", "name": "Pic de Font Blanca", "latitude": "42.64991", "longitude": "1.53335", "country_code": "AD", "population": "0"}
 {"geonameid": "2994701", "name": "Roc M\u00e9l\u00e9", "latitude": "42.58765", "longitude": "1.74028", "country_code": "AD", "population": "0"}
 {"geonameid": "3007683", "name": "Pic des Langounelles", "latitude": "42.61203", "longitude": "1.47364", "country_code": "AD", "population": "0"}
@@ -455,17 +561,20 @@ dataspec --spec csv-select.yaml \
 
 ### <a name="Templating"></a>Templating
 
-To populate a template file with the generated values for each iteration, pass the -t /path/to/template arg to the
-dataspec command. We use the [Jinja2](https://pypi.org/project/Jinja2/) templating engine under the hood. The basic
-format is to put the field names in {{ field name }} notation wherever they should be substituted. For example the
-following is a template for bulk indexing data into Elasticsearch.
+To populate a template file with the generated values for each iteration, pass
+the -t /path/to/template arg to the dataspec command. We use
+the [Jinja2](https://pypi.org/project/Jinja2/) templating engine under the hood.
+The basic format is to put the field names in {{ field name }} notation wherever
+they should be substituted. For example the following is a template for bulk
+indexing data into Elasticsearch.
 
 ```json
 {"index": {"_index": "test", "_id": "{{ id }}"}}
 {"doc": {"name": "{{ name }}", "age": "{{ age }}", "gender": "{{ gender }}"}}
 ```
 
-We could then create a spec to populate the id, name, age, and gender fields. Such as:
+We could then create a spec to populate the id, name, age, and gender fields.
+Such as:
 
 ```json
 {
@@ -479,7 +588,7 @@ We could then create a spec to populate the id, name, age, and gender fields. Su
 When we run the tool we get the data populated for the template:
 
 ```shell script
-dataspec -s ~/scratch/es-spec.json -t ~/scratch/template.json -i 10
+dataspec -s es-spec.json -t template.json -i 10 --log-level off -x
 { "index" : { "_index" : "test", "_id" : "1" } }
 { "doc" : {"name" : "bob", "age": "22", "gender": "F" } }
 { "index" : { "_index" : "test", "_id" : "2" } }
@@ -492,9 +601,10 @@ dataspec -s ~/scratch/es-spec.json -t ~/scratch/template.json -i 10
 
 #### <a name="Templating_Loops"></a>Loops in Templates
 
-[Jinja2](https://jinja.palletsprojects.com/en/2.11.x/templates/#list-of-control-structures) supports looping. To provide
-multiple values to use in a loop use the `count` parameter. For example modifying the example from the Jinja2
-documentation to work with our tool:
+[Jinja2](https://jinja.palletsprojects.com/en/2.11.x/templates/#list-of-control-structures)
+supports looping. To provide multiple values to use in a loop use the `count`
+parameter. For example modifying the example from the Jinja2 documentation to
+work with our tool:
 
 ```html
 <h1>Members</h1>
@@ -505,8 +615,9 @@ documentation to work with our tool:
 </ul>
 ```
 
-If we use a regular spec such as `{"users":["bob","bobby","rob"]}` the templating engine will not populate the template
-correctly since during each iteration only a single name is returned as a string for the engine to process.
+If we use a regular spec such as `{"users":["bob","bobby","rob"]}` the
+templating engine will not populate the template correctly since during each
+iteration only a single name is returned as a string for the engine to process.
 
 ```html
 <h1>Members</h1>
@@ -517,7 +628,8 @@ correctly since during each iteration only a single name is returned as a string
 </ul>
 ```
 
-The engine requires collections to iterate over. A small change to our spec will address this issue:
+The engine requires collections to iterate over. A small change to our spec will
+address this issue:
 
 ```json
 {"users?count=2": ["bob", "bobby", "rob"]}
@@ -535,8 +647,9 @@ Now we get
 
 #### <a name="Dynamic_Loop_Counters"></a>Dynamic Loop Counters
 
-Another mechanism to do loops in Jinja2 is by using the python builtin `range` function. For example if we wanted a
-variable number of line items we could create a template like the following:
+Another mechanism to do loops in Jinja2 is by using the python builtin `range`
+function. For example if we wanted a variable number of line items we could
+create a template like the following:
 
 ```html
 <h1>Members</h1>
@@ -551,7 +664,7 @@ Then we could update our spec to contain a `num_users` field:
 
 ```json
 {
-  "users?count=4": ["bob", "bobby", "rob", "roberta", "steve"],
+  "users?count=4?sample=true": ["bob", "bobby", "rob", "roberta", "steve"],
   "num_users": {
     "2": 0.5,
     "3": 0.3,
@@ -560,18 +673,23 @@ Then we could update our spec to contain a `num_users` field:
 }
 ```
 
-In this spec the number of users created will be weighted so that half the time there are two, and the other half there
-are three or four. NOTE: It is important to make sure that the `count` param is equal to the maximum number that will be
-indexed. If it is less, then there will be empty line items whenever the num_users exceeds the count.
+In the above spec the number of users created will be weighted so that half the
+time there are two, and the other half there are three or four. NOTE: It is
+important to make sure that the `count` param is equal to the maximum number
+that will be indexed. If it is less, then there will be empty line items
+whenever the num_users exceeds the count.
 
 ### <a name="Custom_Code_Loading"></a>Custom Code Loading and Schemas
 
-There are a lot of types of data that are not generated with this tool. Instead of adding them all, there is a mechanism
-to bring your own data suppliers. We make use of the handy [catalogue](https://pypi.org/project/catalogue/) package to
-allow auto discovery of custom functions using decorators. Use the @dataspec.registry.types('\<type key\>') to register
-a function that will create a Value Supplier for the supplied Field Spec. Below is an example of a custom class which
-reverses the output of another supplier. Types that are amazing and useful should be nominated for core inclusion.
-Please put up a PR if you create or use one that solves many of your data generation issues.
+There are a lot of types of data that are not generated with this tool. Instead
+of adding them all, there is a mechanism to bring your own data suppliers. We
+make use of the handy [catalogue](https://pypi.org/project/catalogue/) package
+to allow auto discovery of custom functions using decorators. Use the
+@dataspec.registry.types('\<type key\>') to register a function that will create
+a Value Supplier for the supplied Field Spec. Below is an example of a custom
+class which reverses the output of another supplier. Types that are amazing and
+useful should be nominated for core inclusion. Please put up a PR if you create
+or use one that solves many of your data generation issues.
 
 ```python
 import dataspec
@@ -606,18 +724,20 @@ def get_reverse_string_schema():
         "type": "object",
         "required": ["type", "ref"],
         "properties": {
-            "type": {"type": "string", "pattern": "^reverse_string"},
+            "type": {"type": "string", "pattern": "^reverse_string$"},
             "ref": {"type": "string"}
         }
     }
 ```
 
-Now when we see a type of "reverse_string" like in the example below, we will use the given function to configure the
-supplier for it. The function name for the decorated function is arbitrary, but the signature must match. The signature
-for the Value Supplier is required to match the interface and have a single `next(iteration)` method that returns the
-next value for the given iteration. You can also optionally register a schema for the type. The schema will be applied
-to the spec if the `--strict` command line flag is specified, otherwise you will have to perform your own validation in
-your code.
+Now when we see a type of "reverse_string" like in the example below, we will
+use the given function to configure the supplier for it. The function name for
+the decorated function is arbitrary, but the signature must match. The signature
+for the Value Supplier is required to match the interface and have a
+single `next(iteration)` method that returns the next value for the given
+iteration. You can also optionally register a schema for the type. The schema
+will be applied to the spec if the `--strict` command line flag is specified,
+otherwise you will have to perform your own validation in your code.
 
 ```
 {
@@ -634,12 +754,89 @@ your code.
 }
 ```
 
-To supply custom code to the tool use the -c or --code arguments. One or more module files can be imported.
+To supply custom code to the tool use the -c or --code arguments. One or more
+module files can be imported.
 
 ```shell script
-.dataspec -s reverse-spec.json -i 4 -c custom.py another.py
+.dataspec -s reverse-spec.json -i 4 -c custom.py another.py -x --log-level off
 arbez
 gohegdeh
 amall
 ognimalf
 ```
+
+# <a name="Programmatic"></a>Programmatic Usage
+
+## <a name="BuildSpecs"></a>Building Specs
+
+The `dataspec.builder` module contains tools that can be used to
+programmatically generate Data Specs. This may be easier for some who are not as
+familiar with JSON or prefer to manage their structures in code. The core object
+is the `Builder`. You can add fields, refs, and field groups to this. Each of
+the core field types has a builder function that will generate a Field Spec for
+it. See example below.
+
+This is the email address example from above using the `builder` module.
+
+```python
+import dataspec
+
+animal_names = ['zebra', 'hedgehog', 'llama', 'flamingo']
+action_list = ['fling', 'jump', 'launch', 'dispatch']
+domain_weights = {
+    "gmail.com": 0.6,
+    "yahoo.com": 0.3,
+    "hotmail.com": 0.1
+}
+# for building the final spec
+spec_builder = dataspec.spec_builder()
+# for building the references, is it self also a Builder, but with no refs
+refs = spec_builder.refs()
+# info for each reference added
+domains = refs.values('DOMAINS', data=domain_weights)
+animals = refs.values('ANIMALS', data=animal_names)
+actions = refs.values('ACTIONS', data=action_list, sample=True)
+# combines ANIMALS and ACTIONS with an _
+handles = refs.combine('HANDLE', refs=[animals, actions], join_with='_')
+
+spec_builder.combine('email', refs=[handles, domains], join_with='@')
+
+spec = spec_builder.build()
+```
+
+## <a name="Generator"></a>Generating Records
+
+The `spec.generator` function will create a python generator that can be used to
+incrementally generate the records from the DataSpec.
+
+Example:
+
+```python
+import dataspec
+
+name_list = ['bob', 'bobby', 'robert', 'bobo']
+spec = dataspec.Builder().values('names', name_list).to_spec()
+
+template = 'Name: {{ name }}'
+
+generator = spec.generator(
+    iterations=4,
+    template=template)
+
+single_record = next(generator)
+
+all_records = list(generator)
+
+# since we only specified 4 iterations our batch of 100 will contain 4 items
+batch_of_100 = batch(generator)
+
+
+def batch(generator, batch_size=100):
+    values = []
+    for i in range(batch_size):
+        try:
+            values.append(next(generator))
+        except StopIteration:
+            pass
+    return values
+```
diff --git a/bin/dataspec b/bin/dataspec
index 4678644..898f347 100755
--- a/bin/dataspec
+++ b/bin/dataspec
@@ -2,16 +2,17 @@
 """
 Entry point for dataspec tool
 """
+import os
 import sys
 import json
 import argparse
 import logging
 import yaml
-from dataspec import Loader, preprocess_spec
+from dataspec import preprocess_spec
 import dataspec.template_engines as engines
 import dataspec.outputs as outputs
 import dataspec.types as types
-import dataspec.key_providers as key_providers
+import dataspec.builder as builder
 from dataspec import utils
 from dataspec import SpecException
 
@@ -50,41 +51,94 @@ def main():
                         help='Formatter for output records, default is none, valid are: ' + str(types.valid_formats()))
     parser.add_argument('--strict', action='store_true', default=False,
                         help='Enforce schema validation for all registered field specs')
+    parser.add_argument('--apply-raw', action='store_true', dest='apply_raw', default=False,
+                        help='Data from -s argument should be applied to the template with out treating as a Data Spec')
     parser.add_argument('--debug-spec', dest='debug_spec', action='store_true', default=False,
                         help='Debug spec after internal reformatting')
+    parser.add_argument('--debug-defaults', dest='debug_defaults', action='store_true', default=False,
+                        help='List default values from registry after any external code loading')
     parser.add_argument('-x', '--exclude-internal', dest='exclude_internal', action='store_true', default=False,
                         help='Do not include non data fields in output records')
+    parser.add_argument('--sample-lists', dest='sample_lists', action='store_true', default=False,
+                        help='Turns on sampling for all list backed types')
+    parser.add_argument('--defaults', help='Path to defaults overrides')
 
     try:
         args = parser.parse_args()
 
+        ###################
+        # Set Up
+        ###################
+
         _configure_logging(args)
         log.info('Starting Loading Configurations...')
-        log.debug("Parsing Args")
+        log.debug('Parsing Args')
 
         if args.code:
             log.debug('Loading custom code from %s', args.code)
             for code in args.code:
                 utils.load_custom_code(code)
 
+        ###################
+        # Default Overrides
+        ###################
+        if args.defaults:
+            defaults = _load_json_or_yaml(args.defaults)
+            for key, value in defaults.items():
+                types.set_default(key, value)
+        # command line overrides any configs
+        if args.sample_lists:
+            types.set_default('sample_mode', True)
+
+        # print out the defaults as currently registered
+        if args.debug_defaults:
+            writer = _get_writer(args, outfile='dataspec_defaults.json', overwrite=True)
+            defaults = dataspec.types.all_defaults()
+            writer.write(json.dumps(defaults, indent=4))
+            return
+
+        ###################
+        # Load Data Spec
+        ###################
+
         log.debug('Attempting to load Data Spec from %s', args.spec if args.spec else args.inline)
         spec = _load_spec(args)
+        if spec is None:
+            return
+
+        ###################
+        # By Pass Arguments
+        ###################
+
+        # Only dump out the reformatted spec
         if args.debug_spec:
-            print(json.dumps(preprocess_spec(spec), indent=4))
+            writer = _get_writer(args)
+            writer.write(json.dumps(preprocess_spec(spec), indent=4))
             return
-        loader = Loader(spec, args.datadir, enforce_schema=args.strict)
 
-        output = _configure_output(args)
+        # apply the spec as data to the template
+        if args.apply_raw:
+            engine = engines.for_file(args.template)
+            writer = _get_writer(args)
+            writer.write(engine.process(spec))
+            return
 
-        key_provider = key_providers.from_spec(loader.specs)
+        ###################
+        # Regular Flow
+        ##################
+        output = _configure_output(args)
 
         log.info('Starting Processing...')
+        generator = builder.generator(
+            spec,
+            args.iterations,
+            enforce_schema=args.strict,
+            data_dir=args.datadir,
+            exclude_internal=args.exclude_internal,
+            output=output)
         for i in range(0, args.iterations):
-            group, keys = key_provider.get()
-            for key in keys:
-                value = loader.get(key).next(i)
-                output.handle(key, value)
-            output.finished_record(i, group, args.exclude_internal)
+            # Generator will handle using to configured output
+            next(generator)
         log.info('Finished Processing')
     except SpecException as exc:
         log.error(str(exc))
@@ -96,21 +150,11 @@ def _configure_output(args):
     If none of these configurations are specified, it will return the default output
     which is to print each value to standard out.
     """
-    if args.outdir:
-        log.debug('Creating output file writer for dir: %s', args.outdir)
-        writer = outputs.FileWriter(
-            outdir=args.outdir,
-            outname=args.outfileprefix,
-            extension=args.extension,
-            records_per_file=args.recordsperfile
-        )
-    else:
-        log.debug('Writing output to stdout')
-        writer = outputs.StdOutWriter()
+    writer = _get_writer(args)
 
     if args.template:
         log.debug('Using template from specified file: %s', args.template)
-        return outputs.RecordLevelOutput(engines.load(args.template), writer)
+        return outputs.RecordLevelOutput(engines.for_file(args.template), writer)
 
     if args.format:
         log.debug('Using %s formatter for output', args.format)
@@ -121,6 +165,33 @@ def _configure_output(args):
     return outputs.SingleFieldOutput(writer, args.printkey)
 
 
+def _get_writer(args, outfile: str = None, overwrite: bool = False) -> outputs.WriterInterface:
+    """
+    creates the appropriate output writer from the given args and params
+
+    If no output directory is specified/configured will write to stdout
+    """
+    if args.outdir:
+        log.debug('Creating output file writer for dir: %s', args.outdir)
+        if outfile:
+            writer = outputs.SingleFileWriter(
+                outdir=args.outdir,
+                outname=outfile,
+                overwrite=overwrite
+            )
+        else:
+            writer = outputs.FileWriter(
+                outdir=args.outdir,
+                outname=args.outfileprefix,
+                extension=args.extension,
+                records_per_file=args.recordsperfile
+            )
+    else:
+        log.debug('Writing output to stdout')
+        writer = outputs.StdOutWriter()
+    return writer
+
+
 def _load_spec(args):
     """
     Attempts to load the spec first as JSON then as YAML if JSON fails.
@@ -134,19 +205,27 @@ def _load_spec(args):
         raise SpecException('Only one of --spec <spec path> or --inline "<spec string>" must be specified')
     if inline:
         return _parse_spec_string(inline)
-    with open(spec_path, 'r') as handle:
-        log.debug('Attempting to load spec as JSON')
+    return _load_json_or_yaml(data_path=spec_path)
+
+
+def _load_json_or_yaml(data_path):
+    """ attempts to load the data at path as JSON, if that fails tries as YAML """
+    if not os.path.exists(data_path):
+        log.error('Unable to load data from path: %s', data_path)
+        return None
+    with open(data_path, 'r') as handle:
+        log.debug('Attempting to load data as JSON')
         try:
             return json.load(handle)
         except json.decoder.JSONDecodeError:
-            log.debug('Spec is not Valid JSON')
+            log.debug('Data is not Valid JSON')
     # not JSON, try yaml
-    with open(spec_path, 'r') as handle:
-        log.debug('Attempting to load spec as YAML')
-        spec = yaml.load(handle, Loader=yaml.FullLoader)
-    if not isinstance(spec, dict):
-        raise SpecException(f'Unable to load spec from path: {spec_path}, Please verify it is valid JSON or YAML')
-    return spec
+    with open(data_path, 'r') as handle:
+        log.debug('Attempting to load data as YAML')
+        loaded = yaml.load(handle, Loader=yaml.FullLoader)
+    if not isinstance(loaded, dict):
+        raise SpecException(f'Unable to load data from path: {data_path}, Please verify it is valid JSON or YAML')
+    return loaded
 
 
 def _parse_spec_string(inline: str):
@@ -178,7 +257,8 @@ def _configure_logging(args):
 
 if __name__ == '__main__':
     # this activates the decorators, so they will be discoverable
-    from dataspec.type_handlers import *
+    from dataspec.supplier import *
+    from dataspec.defaults import *
     import dataspec.preprocessor
     import dataspec.logging_handler
 
diff --git a/dataspec/__init__.py b/dataspec/__init__.py
index 8ce7824..3a276f9 100644
--- a/dataspec/__init__.py
+++ b/dataspec/__init__.py
@@ -1,7 +1,11 @@
 """ init for dataspec """
+
 from .types import registry
+from .model import DataSpec, Distribution, ValueSupplierInterface
 from .loader import Loader, preprocess_spec
 from .exceptions import SpecException, ResourceError
-from .suppliers import ValueSupplierInterface
+from .builder import spec_builder
+from .supplier.core import *
+from .preprocessor import *
 
 __version__ = '0.1.0'
diff --git a/dataspec/builder.py b/dataspec/builder.py
new file mode 100644
index 0000000..946a0b6
--- /dev/null
+++ b/dataspec/builder.py
@@ -0,0 +1,968 @@
+"""
+Module for building Data Specs programmatically
+"""
+from pathlib import Path
+from typing import Any, Union, Dict, List
+import json
+import logging
+from .model import DataSpec
+from .loader import Loader
+from . import utils, template_engines, key_providers
+
+
+log = logging.getLogger(__name__)
+
+
+class FieldInfo:
+    """
+    Class for holding info after adding field or ref to spec. Can be passed to specs that use other specs
+    like combine
+    """
+
+    def __init__(self, key: str, type_name: str, builder=None):
+        self.key = key
+        self.type_name = type_name
+        self.builder = builder
+
+    def to_spec(self):
+        """
+        Builds the Data Spec from the underlying builder
+        :return: the Data Spec
+        """
+        return self.builder.build()
+
+
+def spec_builder():
+    """
+    Creates a new DataSpec builder
+
+    :return: the Builder()
+    """
+    return Builder()
+
+
+class Builder:
+    """
+    Container class for constructing the Data Spec by adding fields, refs, and field_groups
+
+    Basic Usage:
+
+    >>> builder = dataspec.Builder()
+    >>> builder.values('names', ['amy', 'bob', 'cat', 'dan', 'earl'])
+    >>> builder.range('ages', start=22, end=33)
+    >>> spec = builder.build()
+    """
+
+    def __init__(self, has_refs=True):
+        if has_refs:
+            self.refs_builder = Builder(False)
+        else:
+            self.refs_builder = None
+        self.fields = {}
+        self.field_groups = []
+        self.keys = set()
+
+    def refs(self):
+        """
+        :return: the refs builder which is itself also a builder
+        """
+        return self.refs_builder
+
+    def values(self, key: str, data: Union[int, float, str, bool, List, Dict[str, float]], **config):
+        """
+        creates values Field Spec and adds to Data Spec
+
+        :param key: name of ref/field
+        :param data: to use to supply values
+        :param config: in **kwargs format
+        """
+        return self._add_field_spec(key, values(data, **config))
+
+    def combine(self, key: str,
+                refs: Union[List[str], List[FieldInfo]] = None,
+                fields: Union[List[str], List[FieldInfo]] = None,
+                **config):
+        """
+        creates combine Field Spec and adds to Data Spec
+
+        :param key: name of ref/field
+        :param refs: refs to combine
+        :param fields: fields to combine
+        :param config: in **kwargs format
+        """
+        return self._add_field_spec(key, combine(refs, fields, **config))
+
+    def combine_list(self, key: str, refs: List[Union[List[str], List[FieldInfo]]] = None, **config):
+        """
+        creates combine-list Field Spec and adds to Data Spec
+
+        :param key: name of ref/field
+        :param refs: lists of lists of refs to combine
+        :param config: in **kwargs format
+        """
+        return self._add_field_spec(key, combine_list(refs, **config))
+
+    def range_spec(self, key: str, data: list, **config):
+        """
+        creates range Field Spec and adds to Data Spec
+
+        :param key: name of ref/field
+        :param data: with start, end, and optional step
+        :param config: in **kwargs format
+        """
+        return self._add_field_spec(key, range_spec(data, **config))
+
+    def rand_range(self, key: str,  data: list, **config):
+        """
+        creates rand_range Field Spec and adds to Data Spec
+
+        :param key: name of ref/field
+        :param data: with start, end
+        :param config: in **kwargs format
+        """
+        return self._add_field_spec(key, rand_range(data, **config))
+
+    def date(self, key: str, **config):
+        """
+        creates date Field Spec and adds to Data Spec
+
+        :param key: name of ref/field
+        :param config: in **kwargs format
+        """
+        return self._add_field_spec(key, date(**config))
+
+    def date_iso(self, key: str, **config):
+        """
+        creates date.iso Field Spec and adds to Data Spec
+
+        :param key: name of ref/field
+        :param config: in **kwargs format
+        """
+        return self._add_field_spec(key, date_iso(**config))
+
+    def date_iso_us(self, key: str, **config):
+        """
+        creates date.iso.us Field Spec and adds to Data Spec
+
+        :param key: name of ref/field
+        :param config: in **kwargs format
+        """
+        return self._add_field_spec(key, date_iso_us(**config))
+
+    def uuid(self, key: str, **config):
+        """
+        creates uuid Field Spec and adds to Data Spec
+
+        :param key: name of ref/field
+        :param config: in **kwargs format
+        """
+        return self._add_field_spec(key, uuid(**config))
+
+    def char_class(self, key: str, data: Union[str, List[str]], **config):
+        """
+        creates char_class Field Spec and adds to Data Spec
+
+        :param key: name of ref/field
+        :param data: either known character class or set of characters to use for sampling from
+        :param config: in **kwargs format
+        """
+        return self._add_field_spec(key, char_class(data, **config))
+
+    def char_class_abbrev(self, key: str, cc_abbrev: str, **config):
+        """
+        creates char_class_abbrev Field Spec and adds to Data Spec
+
+        :param key: name of ref/field
+        :param cc_abbrev: alternative type abbreviation i.e. ascii, cc-ascii, visible, cc-visible
+        :param config: in **kwargs format
+        """
+        return self._add_field_spec(key, char_class_abbrev(cc_abbrev, **config))
+
+    def unicode_range(self, key: str, data: Union[List[str], List[List[str]]], **config):
+        """
+        creates unicode_range Field Spec and adds to Data Spec
+
+        :param key: name of ref/field
+        :param data: hex start and end unicode ranges or lists of these
+        :param config: in **kwargs format
+        """
+        return self._add_field_spec(key, unicode_range(data, **config))
+
+    def geo_lat(self, key: str, **config):
+        """
+        creates geo.lat Field Spec and adds to Data Spec
+
+        :param key: name of ref/field
+        :param config: in **kwargs format
+        """
+        return self._add_field_spec(key, geo_lat(**config))
+
+    def geo_long(self, key: str, **config):
+        """
+        creates geo.long Field Spec and adds to Data Spec
+
+        :param key: name of ref/field
+        :param config: in **kwargs format
+        """
+        return self._add_field_spec(key, geo_long(**config))
+
+    def geo_pair(self, key: str, **config):
+        """
+        creates geo.pair Field Spec and adds to Data Spec
+
+        :param key: name of ref/field
+        :param config: in **kwargs format
+        """
+        return self._add_field_spec(key, geo_pair(**config))
+
+    def ip(self, key: str, **config):
+        """
+        creates ip Field Spec and adds to Data Spec
+
+        :param key: name of ref/field
+        :param config: in **kwargs format
+        """
+        return self._add_field_spec(key, ip(**config))
+
+    def ipv4(self, key: str, **config):
+        """
+        creates ipv4 Field Spec and adds to Data Spec
+
+        :param key: name of ref/field
+        :param config: in **kwargs format
+        """
+        return self._add_field_spec(key, ipv4(**config))
+
+    def ip_precise(self, key: str, **config):
+        """
+        creates ip.precise Field Spec and adds to Data Spec
+
+        :param key: name of ref/field
+        :param config: in **kwargs format
+        """
+        return self._add_field_spec(key, ip_precise(**config))
+
+    def weightedref(self, key: str, data: Dict[str, float], **config):
+        """
+        creates weightedref Field Spec and adds to Data Spec
+
+        :param key: name of ref/field
+        :param data: Mapping of ref name to weight
+        :param config: in **kwargs format
+        """
+        return self._add_field_spec(key, weightedref(data, **config))
+
+    def select_list_subset(self, key: str, data: List[Any] = None, ref: str = None, **config):
+        """
+        creates select_list_subset Field Spec and adds to Data Spec
+
+        :param key: name of ref/field
+        :param data: to select from
+        :param ref: that contains data to select from
+        :param config: in **kwargs format
+        """
+        return self._add_field_spec(key, select_list_subset(data, ref, **config))
+
+    def csv(self, key: str, **config):
+        """
+        creates csv Field Spec and adds to Data Spec
+
+        :param key: name of ref/field
+        :param config: in **kwargs format
+        """
+        return self._add_field_spec(key, csv(**config))
+
+    def csv_select(self, key: str, data: Dict[str, int] = None, **config):
+        """
+        creates csv_select Field Spec and adds to Data Spec
+
+        :param key: name of ref/field
+        :param data: param data: Mapping of field name to one based column number
+        :param config: in **kwargs format
+        """
+        return self._add_field_spec(key, csv_select(data, **config))
+
+    def nested(self, key: str, fields: Union[DataSpec, Dict[str, Dict]], **config):
+        """
+        creates nested Field Spec and adds to Data Spec
+
+        :param key: name of ref/field
+        :param fields: sub field specifications
+        :param config: in **kwargs format
+        """
+        return self._add_field_spec(key, nested(fields, **config))
+
+    def configref(self, key: str, **config):
+        """
+        Adds the configref to the refs for this spec
+
+        :param key: name for configref
+        :param config: for configref
+        :return: None
+        """
+        # this must be a refs instance
+        if self.refs_builder is None:
+            self.add_field(key, configref(**config))
+        else:
+            self.add_ref(key, configref(**config))
+
+    def _add_field_spec(self, key, spec):
+        """ adds the fieldspec and creates a FieldInfo object """
+        self.add_field(key, spec)
+        return FieldInfo(key, spec['type'], self)
+
+    def add_fields(self, **kwargs):
+        """
+        Add all fields to the spec. kwargs format should be:
+
+        builder = dataspec.builder.Builder()
+        builder.add_fields(
+            FIELDNAME1=builder.some_spec(with_args),
+            FIELDNAME2=builder.another_spec(with_args)
+        )
+
+        :param kwargs: where key is field name and value is a generated spec
+        :return: self for chaining invocations
+        """
+        for key, spec in kwargs.items():
+            self.add_field(key, spec)
+        return self
+
+    def add_field(self, key: Union[str, FieldInfo], spec: dict):
+        """
+        Add single field to the spec.
+
+        builder = dataspec.builder.Builder()
+        builder.add_field("field1", builder.some_spec(with_args)) \
+               .add_field("field2", builder.another_spec(with_args))
+
+        :param key: field name
+        :param spec: the generate spec for this field
+        :return: self for chaining invocations
+        """
+        if key in self.keys:
+            log.warning('%s key already defined, overwriting with %s',
+                        key, json.dumps(spec))
+        if isinstance(key, FieldInfo):
+            key = key.key
+        self.keys.add(key)
+        self.fields[key] = spec
+        return self
+
+    def add_refs(self, **kwargs):
+        """
+        Add all refs to the spec. kwargs format should be:
+
+        builder = dataspec.builder.Builder()
+        builder.add_refs(
+            REFNAME1=builder.some_spec(with_args),
+            REFNAME2=builder.another_spec(with_args)
+        )
+
+        :param kwargs: where key is ref name and value is a generated spec
+        :return: self for chaining invocations
+        """
+        for key, spec in kwargs.items():
+            self.add_ref(key, spec)
+        return self
+
+    def add_ref(self, key: str, spec: dict):
+        """
+        Add single ref to the spec.
+
+        builder = dataspec.builder.Builder()
+        builder.add_ref("ref1", builder.some_spec(with_args)) \
+               .add_ref("ref2", builder.another_spec(with_args))
+
+        :param key: ref name
+        :param spec: the generate spec for this ref
+        :return: self for chaining invocations
+        """
+        if key in self.keys:
+            log.warning('%s key already defined, overwriting with %s', key, json.dumps(spec))
+        self.keys.add(key)
+        self.refs_builder.add_field(key, spec)
+        return self
+
+    def weighted_field_group(self, key: str, fields: List[str], weight: float):
+        """
+        Creates a weighted field group for a single key and add to Spec
+        :param key: the name of the field group
+        :param fields: the fields in the group
+        :param weight: the weight for this group
+        :return: FieldInfo
+        """
+        field_group = {
+            key: {
+                "weight": weight,
+                "fields": fields
+            }
+        }
+        self.field_groups.append(field_group)
+        return FieldInfo(key, 'field_group')
+
+    def named_field_group(self, key: str, fields: List[str]):
+        """
+        Create a named field group for a single key and add to Spec
+        :param key: the name of the field group
+        :param fields: the fields in the group
+        :return: FieldInfo
+        """
+        field_group = {
+            key: fields
+        }
+        self.field_groups.append(field_group)
+        return FieldInfo(key, 'field_group')
+
+    def add_field_groups(self, field_groups: List[Union[List[str], Dict[str, Dict]]]):
+        """
+        Add a single field group
+        :param field_groups: to add
+        :return: self for chaining invocations
+        """
+        for field_group in field_groups:
+            self.add_field_group(field_group)
+        return self
+
+    def add_field_group(self, field_group: Union[List[str], Dict[str, Dict]]):
+        """
+        Add a single field group
+        :param field_group: to add
+        :return: self for chaining invocations
+        """
+        self.field_groups.append(field_group)
+        return self
+
+    def build(self) -> DataSpec:
+        """
+        Generates the spec from the provided fields, refs, and field_groups
+        :return: The built spec
+        """
+        spec = {}
+        spec.update(self.fields)
+        if len(self.refs_builder.fields) > 0:
+            spec['refs'] = self.refs_builder.fields
+        if len(self.field_groups) > 0:
+            self._configure_field_groups(spec)
+
+        return DataSpecImpl(spec)
+
+    def _configure_field_groups(self, spec):
+        """
+        Adds the field_groups element to the spec if needed and defined
+        """
+        all_dict = all(isinstance(entry, dict) for entry in self.field_groups)
+        if all_dict:
+            flattened = {}
+            for entry in self.field_groups:
+                flattened.update(entry)
+            spec['field_groups'] = flattened
+        all_list = all(isinstance(entry, list) for entry in self.field_groups)
+        if all_list:
+            spec['field_groups'] = self.field_groups
+
+
+def single_field(name: str, spec):
+    """ Creates Builder for single field and spec """
+    return Builder().add_field(name, spec)
+
+
+def values(data: Union[int, float, str, bool, List, Dict[str, float]], **config):
+    """
+    Constructs a values Field Spec
+
+    :param data: to use to supply values
+    :param config: in **kwargs format
+    :return: the values spec
+    """
+
+    spec = {
+        "type": "values",
+        "data": data
+    }
+
+    if len(config) > 0:
+        spec['config'] = config
+    return spec
+
+
+def combine(refs: Union[List[str], List[FieldInfo]] = None,
+            fields: Union[List[str], List[FieldInfo]] = None,
+            **config):
+    """
+    Constructs a combine Field Spec
+
+    :param refs: refs to combine
+    :param fields: fields to combine
+    :param config: in **kwargs format
+    :return: the combine spec
+    """
+
+    spec = {
+        "type": "combine"
+    }  # type: Dict[str, Any]
+    if refs is not None:
+        spec['refs'] = _create_key_list(refs)
+    if fields is not None:
+        spec['fields'] = _create_key_list(fields)
+
+    if len(config) > 0:
+        spec['config'] = config
+    return spec
+
+
+def combine_list(refs: List[Union[List[str], List[FieldInfo]]] = None, **config):
+    """
+    Constructs a combine-list Field Spec
+
+    :param refs: lists of lists of refs to combine
+    :param config: in **kwargs format
+    :return: the combine-list spec
+    """
+    if refs is None:
+        refs = []
+    spec = {
+        "type": "combine-list",
+        "refs": [_create_key_list(ref_list) for ref_list in refs]
+    }  # type: Dict[str, Any]
+
+    if len(config) > 0:
+        spec['config'] = config
+    return spec
+
+
+def range_spec(data: list, **config):
+    """
+    Constructs a range Field Spec
+
+    :param data: with start, end, and optional step
+    :param config: in **kwargs format
+    :return: the range spec
+    """
+
+    spec = {
+        "type": "range",
+        "data": data
+    }  # type: Dict[str, Any]
+
+    if len(config) > 0:
+        spec['config'] = config
+    return spec
+
+
+def rand_range(data: list, **config):
+    """
+    Constructs a rand_range Field Spec
+
+    :param data: with start, end
+    :param config: in **kwargs format
+    :return: the rand_range spec
+    """
+
+    spec = {
+        "type": "rand_range",
+        "data": data
+    }  # type: Dict[str, Any]
+
+    if len(config) > 0:
+        spec['config'] = config
+    return spec
+
+
+def date(**config):
+    """
+    Constructs a date Field Spec
+
+    :param config: in **kwargs format
+    :return: the date spec
+    """
+
+    spec = {
+        "type": "date"
+    }  # type: Dict[str, Any]
+
+    if len(config) > 0:
+        spec['config'] = config
+    return spec
+
+
+def date_iso(**config):
+    """
+    Constructs a date.iso Field Spec
+
+    :param config: in **kwargs format
+    :return: the date.iso spec
+    """
+
+    spec = {
+        "type": "date.iso"
+    }  # type: Dict[str, Any]
+
+    if len(config) > 0:
+        spec['config'] = config
+    return spec
+
+
+def date_iso_us(**config):
+    """
+    Constructs a date.iso.us Field Spec
+
+    :param config: in **kwargs format
+    :return: the date.iso.us spec
+    """
+
+    spec = {
+        "type": "date.iso.us"
+    }  # type: Dict[str, Any]
+
+    if len(config) > 0:
+        spec['config'] = config
+    return spec
+
+
+def uuid(**config):
+    """
+    Constructs a uuid Field Spec
+
+    :param config: in **kwargs format
+    :return: the uuid spec
+    """
+
+    spec = {
+        "type": "uuid"
+    }  # type: Dict[str, Any]
+
+    if len(config) > 0:
+        spec['config'] = config
+    return spec
+
+
+def char_class(data: Union[str, List[str]], **config):
+    """
+    Constructs a char_class Field Spec
+
+    :param data: either known character class or set of characters to use for sampling from
+    :param config: in **kwargs format
+    :return: the char_class spec
+    """
+
+    spec = {
+        "type": "char_class",
+        "data": data
+    }  # type: Dict[str, Any]
+
+    if len(config) > 0:
+        spec['config'] = config
+    return spec
+
+
+def char_class_abbrev(cc_abbrev: str, **config):
+    """
+    Constructs a char_class spec
+
+    :param cc_abbrev: alternative type abbreviation i.e. ascii, cc-ascii, visible, cc-visible
+    :param config: in **kwargs format
+    :return: the char_class spec
+    """
+    spec = {
+        "type": 'char_class_abbrev'
+    }  # type: Dict[str, Any]
+
+    if cc_abbrev.startswith('cc-'):
+        spec['type'] = cc_abbrev
+    else:
+        spec['type'] = 'cc-' + cc_abbrev
+
+    if len(config) > 0:
+        spec['config'] = config
+    return spec
+
+
+def unicode_range(data: Union[List[str], List[List[str]]], **config):
+    """
+    Constructs a unicode_range spec
+
+    :param data: hex start and end unicode ranges or lists of these
+    :param config: in **kwargs format
+    :return: the unicode_range spec
+    """
+    spec = {
+        "type": "unicode_range",
+        "data": data
+    }  # type: Dict[str, Any]
+
+    if len(config) > 0:
+        spec['config'] = config
+    return spec
+
+
+def geo_lat(**config):
+    """
+    Constructs a geo_lat spec
+
+    :param config: in **kwargs format
+    :return: the geo_lat spec
+    """
+    spec = {
+        "type": "geo.lat"
+    }  # type: Dict[str, Any]
+
+    if len(config) > 0:
+        spec['config'] = config
+    return spec
+
+
+def geo_long(**config):
+    """
+    Constructs a geo_long spec
+
+    :param config: in **kwargs format
+    :return: the geo_long spec
+    """
+    spec = {
+        "type": "geo.long"
+    }  # type: Dict[str, Any]
+
+    if len(config) > 0:
+        spec['config'] = config
+    return spec
+
+
+def geo_pair(**config):
+    """
+    Constructs a geo_pair spec
+
+    :param config: in **kwargs format
+    :return: the geo_pair spec
+    """
+    spec = {
+        "type": "geo.pair"
+    }  # type: Dict[str, Any]
+
+    if len(config) > 0:
+        spec['config'] = config
+    return spec
+
+
+def ip(**config):
+    """
+    Constructs a ip spec
+
+    :param config: in **kwargs format
+    :return: the ip spec
+    """
+    spec = {
+        "type": "ip"
+    }  # type: Dict[str, Any]
+
+    if len(config) > 0:
+        spec['config'] = config
+    return spec
+
+
+def ipv4(**config):
+    """
+    Constructs a ipv4 spec
+
+    :param config: in **kwargs format
+    :return: the ipv4 spec
+    """
+    spec = {
+        "type": "ipv4"
+    }  # type: Dict[str, Any]
+
+    if len(config) > 0:
+        spec['config'] = config
+    return spec
+
+
+def ip_precise(**config):
+    """
+    Constructs a ip_precise spec
+
+    :param config: in **kwargs format
+    :return: the ip_precise spec
+    """
+    spec = {
+        "type": "ip.precise"
+    }  # type: Dict[str, Any]
+
+    if len(config) > 0:
+        spec['config'] = config
+    return spec
+
+
+def weightedref(data: Dict[str, float], **config):
+    """
+    Constructs a weightedref spec
+
+    :param data: Mapping of ref name to weight
+    :param config: in **kwargs format
+    :return: the weightedref spec
+    """
+    spec = {
+        "type": "weightedref",
+        "data": data
+    }  # type: Dict[str, Any]
+
+    if len(config) > 0:
+        spec['config'] = config
+    return spec
+
+
+def select_list_subset(data: List[Any] = None, ref: str = None, **config):
+    """
+    Constructs a select_list_subset spec
+
+    :param data: to select from
+    :param ref: that contains data to select from
+    :param config: in **kwargs format
+    :return: the select_list_subset spec
+    """
+    spec = {
+        "type": "select_list_subset"
+    }  # type: Dict[str, Any]
+
+    if data is not None:
+        spec['data'] = data
+    if ref is not None:
+        spec['ref'] = ref
+
+    if len(config) > 0:
+        spec['config'] = config
+    return spec
+
+
+def csv(**config):
+    """
+    Constructs a csv spec
+
+    :param config: in **kwargs format
+    :return: the csv spec
+    """
+    spec = {
+        "type": "csv"
+    }  # type: Dict[str, Any]
+
+    if len(config) > 0:
+        spec['config'] = config
+    return spec
+
+
+def csv_select(data: Dict[str, int] = None, **config):
+    """
+    Constructs a csv_select spec
+
+    :param data: Mapping of field name to one based column number
+    :param config: in **kwargs format
+    :return: the csv_select spec
+    """
+    spec = {
+        "type": "csv_select"
+    }  # type: Dict[str, Any]
+
+    if data is not None:
+        spec['data'] = data
+
+    if len(config) > 0:
+        spec['config'] = config
+    return spec
+
+
+def nested(fields: Union[Dict[str, Dict], DataSpec], **config):
+    """
+    Constructs a nested spec
+
+    :param fields: sub field specifications
+    :param config: in **kwargs format
+    :return: the nested spec
+    """
+    spec = {
+        "type": "nested",
+        "fields": utils.get_raw_spec(fields)
+    }  # type: Dict[str, Any]
+
+    if len(config) > 0:
+        spec['config'] = config
+    return spec
+
+
+def configref(**config):
+    """
+    Constructs a configref spec
+
+    :param config: in **kwargs format
+    :return: the configref spec
+    """
+    spec = {
+        "type": "configref"
+    }  # type: Dict[str, Any]
+
+    if len(config) > 0:
+        spec['config'] = config
+    return spec
+
+
+def _create_key_list(entries):
+    """
+    Checks if entries are from FieldInfo objects and extracts keys
+
+    :param entries: to create key list from
+    :return: the list of keys
+    """
+    if len(entries) == 0:
+        return []
+    if all(isinstance(entry, FieldInfo) for entry in entries):
+        return [entry.key for entry in entries]
+    # this should be a regular list of strings
+    return entries
+
+
+def generator(raw_spec: Dict[str, Dict], iterations: int, **kwargs):
+    """
+    Creates a generator for the raw spec for the specified iterations
+
+    :param raw_spec: to create generator for
+    :param iterations: number of iterations before max
+    :param kwargs: args to generator i.e. enforce_schema, ect.
+    :return: the generator for the provided spec
+    """
+    return DataSpecImpl(raw_spec).generator(iterations, **kwargs)
+
+
+class DataSpecImpl(DataSpec):
+    """
+    Implementation for DataSpec
+    """
+
+    def generator(self, iterations: int, **kwargs):
+        template = kwargs.get('template')
+        data_dir = kwargs.get('data_dir', '.')
+        enforce_schema = kwargs.get('enforce_schema', False)
+        exclude_internal = kwargs.get('exclude_internal', False)
+        output = kwargs.get('output', None)
+        loader = Loader(self.raw_spec, data_dir=data_dir, enforce_schema=enforce_schema)
+
+        if template is not None:
+            if isinstance(template, Path):
+                engine = template_engines.for_file(template)
+            else:
+                engine = template_engines.string(template)
+
+        key_provider = key_providers.from_spec(loader.specs)
+
+        for i in range(0, iterations):
+            group, keys = key_provider.get()
+            record = {}
+            for key in keys:
+                value = loader.get(key).next(i)
+                if output:
+                    output.handle(key, value)
+                record[key] = value
+            if output:
+                output.finished_record(i, group, exclude_internal)
+
+            if template is not None:
+                yield engine.process(record)
+            else:
+                yield record
diff --git a/dataspec/defaults.py b/dataspec/defaults.py
new file mode 100644
index 0000000..a9ed48b
--- /dev/null
+++ b/dataspec/defaults.py
@@ -0,0 +1,60 @@
+"""
+Module for storing default settings across package
+"""
+from . import registry
+
+
+@registry.defaults('sample_mode')
+def default_list_sample_mode():
+    """ By default we do not sample from lists backed types """
+    return False
+
+
+@registry.defaults('char_class_join_with')
+@registry.defaults('combine_join_with')
+def default_char_class_join_with():
+    """ default join for char_class and combine types """
+    return ''
+
+
+@registry.defaults('combine_as_list')
+@registry.defaults('geo_as_list')
+def default_as_list_false():
+    """ default as list for combine and geo types """
+    return False
+
+
+@registry.defaults('geo_lat_first')
+def default_geo_lat_first():
+    """ default lat first for geo types """
+    return False
+
+
+@registry.defaults('geo_join_with')
+def default_geo_join_with():
+    """ default join for geo types """
+    return ','
+
+
+@registry.defaults('date_stddev_days')
+def default_date_stddev_days():
+    """ default date stddev days """
+    return 15
+
+
+@registry.defaults('date_format')
+def default_date_format():
+    """ default date format """
+    return "%d-%m-%Y"
+
+
+@registry.defaults('geo_precision')
+def default_geo_type_precision():
+    """ default digits after decimal for geo types """
+    return 4
+
+
+@registry.defaults('json_indent')
+def default_json_indent():
+    """ default spaces to indent when using json-pretty formatter """
+    return 4
diff --git a/dataspec/distributions.py b/dataspec/distributions.py
new file mode 100644
index 0000000..a554fa5
--- /dev/null
+++ b/dataspec/distributions.py
@@ -0,0 +1,142 @@
+"""
+Module for numeric distributions such as uniform or gaussian
+"""
+import random
+import inspect
+import dataspec
+from dataspec.model import Distribution
+
+
+class UniformDistribution(Distribution):
+    """
+    Class that samples values from a uniform distribution between the start and end points
+    """
+
+    def __init__(self, start: float, end: float):
+        self.start = start
+        self.end = end
+
+    def next_value(self):
+        return random.uniform(self.start, self.end)
+
+
+class GaussDistribution(Distribution):
+    """
+    Class that samples values from a normal distribution with provided mean and standard deviation
+    """
+
+    def __init__(self, mean: float, stddev: float):
+        self.mean = mean
+        self.stddev = stddev
+
+    def next_value(self):
+        return random.gauss(self.mean, self.stddev)
+
+
+class BoundedDistribution(Distribution):
+    """
+    Class bounds another distribution
+    """
+
+    def __init__(self,
+                 distribution: Distribution,
+                 min_val: float = 0.0,
+                 max_val: float = None):
+        self.distribution = distribution
+        self.min = min_val
+        self.max = max_val
+
+    def next_value(self):
+        value = self.distribution.next_value()
+        if self.min and value < self.min:
+            return self.min
+        if self.max and value > self.max:
+            return self.max
+        return value
+
+
+@dataspec.registry.distribution('uniform')
+def uniform(start, end):
+    """ uniform distribution for from start to end """
+    return UniformDistribution(start, end)
+
+
+@dataspec.registry.distribution('normal')
+def normal(mean, stddev, **kwargs):
+    """ normal distribution for normal keyword """
+    return _gaussian_distribution(mean, stddev, **kwargs)
+
+
+@dataspec.registry.distribution('gauss')
+def gauss(mean, stddev, **kwargs):
+    """ normal distribution for gauss keyword """
+    return _gaussian_distribution(mean, stddev, **kwargs)
+
+
+@dataspec.registry.distribution('gaussian')
+def gaussian(mean, stddev, **kwargs):
+    """ normal distribution for gaussian keyword """
+    return _gaussian_distribution(mean, stddev, **kwargs)
+
+
+def _gaussian_distribution(mean, stddev, **kwargs):
+    """ normal distribution for mean and standard deviation """
+    distribution = GaussDistribution(mean, stddev)
+    if 'min' in kwargs or 'max' in kwargs:
+        return BoundedDistribution(distribution, kwargs.get('min'), kwargs.get('max'))
+    return distribution
+
+
+def from_string(dist_func_str: str) -> Distribution:
+    """
+    Uses a function form of the distribution to look up and configure it
+
+    >>> distribution = dataspec.distributions.from_string("uniform(start=10, end=25)")
+
+    Distribution params need to use key=value format
+    :param dist_func_str:
+    :return: the specified distribution if registered
+    """
+    try:
+        open_paren = dist_func_str.index('(')
+        close_paren = dist_func_str.index(')')
+    except ValueError as value_error:
+        raise ValueError('Invalid function format: ' + dist_func_str) from value_error
+
+    if open_paren == 0 or close_paren < open_paren or close_paren != len(dist_func_str.strip()) - 1:
+        raise ValueError('Invalid function format: ' + dist_func_str)
+
+    name = dist_func_str[0:open_paren]
+    dist_func = dataspec.registry.distribution.get(name)
+
+    args = dist_func_str[open_paren + 1:close_paren]
+    kwargs = _convert_to_kwargs(args)
+
+    if kwargs is None or _invalid_args_for_func(dist_func, kwargs):
+        raise ValueError('Invalid args for function: ' + dist_func_str)
+
+    return dist_func(**kwargs)
+
+
+def _invalid_args_for_func(dist_func, kwargs):
+    """ verifies the args match """
+    argspec = inspect.getfullargspec(dist_func)
+    expected_args = argspec.args
+    actual_args = list(set(kwargs.keys()) - {'min', 'max'})
+    return sorted(actual_args) != sorted(expected_args)
+
+
+def _convert_to_kwargs(args):
+    """
+    converts string of key=val, ..., key=val to dictionary
+    :param args: to convert
+    :return: dictionary of key values
+    """
+    parts = args.split(',')
+    kwargs = {}
+    for part in parts:
+        keyval = part.split('=')
+        if len(keyval) != 2:
+            return None
+        kwargs[keyval[0].strip()] = float(keyval[1].strip())
+    return kwargs
diff --git a/dataspec/key_providers.py b/dataspec/key_providers.py
index beef3fe..b2c9728 100644
--- a/dataspec/key_providers.py
+++ b/dataspec/key_providers.py
@@ -8,7 +8,8 @@
 """
 from typing import List, Tuple, Union, Dict
 import json
-from dataspec import suppliers, ValueSupplierInterface
+from . import suppliers, ValueSupplierInterface
+from .model import DataSpec
 from .exceptions import SpecException
 
 ROOT_KEYS = ['refs', 'field_groups']
@@ -17,14 +18,14 @@
 class KeyProviderInterface:
     """ Interface for KeyProviders """
 
-    def get(self) -> (str, List[str]):
+    def get(self) -> Tuple[str, List[str]]:
         """
         get the next set of field names to process
         :return: key_group_name, key_list_for_group_name
         """
 
 
-def from_spec(specs: dict) -> KeyProviderInterface:
+def from_spec(specs: Union[dict, DataSpec]) -> KeyProviderInterface:
     """
     creates the appropriate key provider for the fields from the supplied spec
 
@@ -52,8 +53,12 @@ def from_spec(specs: dict) -> KeyProviderInterface:
       "groupB": ["one", "two", "three"], ...
     }
     """
-    if 'field_groups' in specs:
-        field_groups = specs['field_groups']
+    if isinstance(specs, DataSpec):
+        raw_spec = specs.raw_spec
+    else:
+        raw_spec = specs
+    if 'field_groups' in raw_spec:
+        field_groups = raw_spec['field_groups']
         if isinstance(field_groups, dict):
             if isinstance(list(field_groups.values())[0], list):
                 return _create_rotating_lists_key_provider(field_groups)
@@ -61,7 +66,7 @@ def from_spec(specs: dict) -> KeyProviderInterface:
         if isinstance(field_groups, list):
             return _create_rotating_lists_key_provider(field_groups)
     # default when no field groups specified
-    keys = [key for key in specs.keys() if key not in ROOT_KEYS]
+    keys = [key for key in raw_spec.keys() if key not in ROOT_KEYS]
     return KeyListProvider(keys)
 
 
diff --git a/dataspec/loader.py b/dataspec/loader.py
index 22190d0..793d6f4 100644
--- a/dataspec/loader.py
+++ b/dataspec/loader.py
@@ -4,9 +4,12 @@
 """
 
 import json
+from typing import Union, Dict
+from . import suppliers
+from . import utils
+from .model import DataSpec
 from .types import lookup_type, lookup_schema, registry
 from .schemas import validate_schema_for_spec
-from . import suppliers
 from .exceptions import SpecException
 
 
@@ -32,9 +35,10 @@ class Loader:
     """
     RESERVED = ['type', 'data', 'ref', 'refs', 'config']
 
-    def __init__(self, data_spec, datadir='./data', enforce_schema=False):
-        self.specs = preprocess_spec(data_spec)
-        self.datadir = datadir
+    def __init__(self, data_spec, data_dir='./data', enforce_schema=False):
+        raw_spec = utils.get_raw_spec(data_spec)
+        self.specs = preprocess_spec(raw_spec)
+        self.datadir = data_dir
         self.enforce_schema = enforce_schema
         self.cache = {}
         self.refs = Refs(self.specs.get('refs'))
@@ -72,6 +76,8 @@ def get_from_spec(self, field_spec):
         if spec_type == 'configref':
             raise SpecException(f'Cannot use configref as source of data: {json.dumps(field_spec)}')
         if spec_type is None or spec_type == 'values':
+            if self.enforce_schema:
+                _validate_schema_for_spec(spec_type, field_spec)
             supplier = suppliers.values(field_spec, self)
         else:
             handler = lookup_type(spec_type)
@@ -84,6 +90,8 @@ def get_from_spec(self, field_spec):
             supplier = suppliers.cast_supplier(supplier, field_spec)
         if suppliers.is_decorated(field_spec):
             supplier = suppliers.decorated(field_spec, supplier)
+        if suppliers.is_buffered(field_spec):
+            supplier = suppliers.buffered(supplier, field_spec)
         return supplier
 
     def get_ref_spec(self, key):
@@ -98,12 +106,13 @@ def _validate_schema_for_spec(spec_type, field_spec):
     validate_schema_for_spec(spec_type, field_spec, type_schema)
 
 
-def preprocess_spec(raw_spec):
+def preprocess_spec(data_spec: Union[Dict[str, Dict], DataSpec]):
     """
     Uses the registered preprocessors to cumulatively update the spec
-    :param raw_spec: to preprocesss
+    :param data_spec: to preprocess
     :return: updated version of the spec after all preprocessors have run on it
     """
+    raw_spec = utils.get_raw_spec(data_spec)
     updated = dict(raw_spec)
     preprocessors = registry.preprocessors.get_all()
     for name in preprocessors:
diff --git a/dataspec/model.py b/dataspec/model.py
new file mode 100644
index 0000000..bad3bab
--- /dev/null
+++ b/dataspec/model.py
@@ -0,0 +1,83 @@
+"""
+Module to hold models for core data structures and classes
+"""
+from pathlib import Path
+from typing import Union
+
+
+class DataSpec(dict):
+    """
+    Class representing a DataSpec object
+    """
+
+    def __delitem__(self, key):
+        return self.raw_spec.__delitem__(key)
+
+    def __setitem__(self, key, value):
+        return self.raw_spec.__setitem__(key, value)
+
+    def __getitem__(self, key):
+        return self.raw_spec.__getitem__(key)
+
+    def __contains__(self, key):
+        return self.raw_spec.__contains__(key)
+
+    def __repr__(self):
+        return f"{type(self.raw_spec).__name__}({super().__repr__()})"
+
+    def pop(self, key):
+        return self.raw_spec.pop(key)
+
+    def __init__(self, raw_spec):
+        self.raw_spec = raw_spec
+
+    def generator(self, iterations: int, **kwargs):
+        """
+        Creates a generator that will produce records or render the template for each record
+
+        Usage:
+
+        >>> import dataspec
+
+        >>> builder = dataspec.Builder()
+        >>> builder.values(['bob', 'bobby', 'robert', 'bobo']))
+
+        >>> spec = builder.build()
+
+        >>> template = 'Name: {{ name }}'
+
+        >>> generator = spec.generator(
+        >>>     iterations=4,
+        >>>     template=template)
+
+        >>>  for i in range(5):
+        >>>      try:
+        >>>          record = next(generator)
+        >>>          print(record)
+        >>>      except StopIteration:
+        >>>          pass
+
+        :param iterations: the number of iterations to run
+        :return: record generator
+        """
+
+
+class Distribution:
+    """
+    Interface Class for a numeric distribution such as a Uniform or Gaussian distribution
+    """
+
+    def next_value(self) -> float:
+        """ get the next value for this distribution """
+
+
+class ValueSupplierInterface:
+    """
+    Interface for Classes that supply values
+    """
+    def next(self, iteration):
+        """
+        Produces the next value for the given iteration
+        :param iteration: current iteration
+        :return: the next value
+        """
\ No newline at end of file
diff --git a/dataspec/outputs.py b/dataspec/outputs.py
index 9cbde31..66de593 100644
--- a/dataspec/outputs.py
+++ b/dataspec/outputs.py
@@ -3,9 +3,12 @@
 """
 import os
 import json
+import logging
 import dataspec
 from .types import registry
 
+log = logging.getLogger(__name__)
+
 
 @dataspec.registry.formats('json')
 def format_json(record):
@@ -16,7 +19,7 @@ def format_json(record):
 @dataspec.registry.formats('json-pretty')
 def format_json_pretty(record):
     """ pretty prints the record as json """
-    return json.dumps(record, indent=4)
+    return json.dumps(record, indent=dataspec.types.get_default('json_indent'))
 
 
 @dataspec.registry.formats('csv')
@@ -115,10 +118,32 @@ class StdOutWriter(WriterInterface):
     Writes values to stdout
     """
 
-    def write(self, value):
+    def write(self, value: str):
         print(value)
 
 
+class SingleFileWriter(WriterInterface):
+    """
+    Writes all values to same file
+    """
+
+    def __init__(self, outdir: str, outname: str, overwrite: bool):
+        self.outdir = outdir
+        self.outname = outname
+        self.overwrite = overwrite
+
+    def write(self, value):
+        outfile = os.path.join(self.outdir, self.outname)
+        if self.overwrite:
+            mode = 'w'
+        else:
+            mode = 'a'
+        with open(outfile, mode) as handle:
+            handle.write(value)
+            handle.write('\n')
+        log.info('Wrote data to %s', outfile)
+
+
 class FileWriter(WriterInterface):
     """
     Writes processed output to disk
diff --git a/dataspec/preprocessor.py b/dataspec/preprocessor.py
index d9fd029..6f44b2b 100644
--- a/dataspec/preprocessor.py
+++ b/dataspec/preprocessor.py
@@ -22,18 +22,17 @@ def preprocess_spec(raw_spec):
     """
     updated_specs = {}
     for key, spec in raw_spec.items():
-        if key in ['refs', 'field_groups']:
+        if key == 'refs':
+            updated_specs[key] = preprocess_spec(raw_spec[key])
+            continue
+        if key == 'field_groups':
             updated_specs[key] = spec
             continue
         if '?' not in key:
             _update_no_params(key, spec, updated_specs)
         else:
             _update_with_params(key, spec, updated_specs)
-    if 'refs' in raw_spec:
-        if 'refs' in updated_specs:
-            updated_specs['refs'].update(preprocess_spec(raw_spec['refs']))
-        else:
-            updated_specs['refs'] = preprocess_spec(raw_spec['refs'])
+
     return updated_specs
 
 
diff --git a/dataspec/schema/char_class.schema.json b/dataspec/schema/char_class.schema.json
index d1c746d..bb094b7 100644
--- a/dataspec/schema/char_class.schema.json
+++ b/dataspec/schema/char_class.schema.json
@@ -12,10 +12,32 @@
         "suffix": {"$ref": "#/definitions/suffix"},
         "quote": {"$ref": "#/definitions/quote"},
         "count": {"$ref": "#/definitions/count"},
-        "min": {"$ref": "#/definitions/min"},
-        "max": {"$ref": "#/definitions/max"},
-        "mean": {"$ref": "#/definitions/mean"},
-        "stddev": {"$ref": "#/definitions/stddev"}
+        "min": {
+          "type": "integer",
+          "description": "minimum number of characters in string",
+          "default": null,
+          "examples": [1, 7, 2255],
+          "minimum": 1
+        },
+        "max": {
+          "type": "integer",
+          "description": "maximum number of characters in string",
+          "default": null,
+          "examples": [1, 7, 2255],
+          "minimum": 1
+        },
+        "mean": {
+          "type": "number",
+          "description": "mean number of characters in string",
+          "default": null,
+          "examples": [3, 5, 7.5]
+        },
+        "stddev": {
+          "type": "number",
+          "description": "standard deviation from mean for number of characters in string",
+          "default": null,
+          "examples": [0.5, 3, 7]
+        }
       },
       "not": {
         "description": "count should not be present if min max or mean are present",
diff --git a/dataspec/schema/date.schema.json b/dataspec/schema/date.schema.json
index 0b7fd81..4a21f87 100644
--- a/dataspec/schema/date.schema.json
+++ b/dataspec/schema/date.schema.json
@@ -13,22 +13,92 @@
         "quote": {"$ref": "#/definitions/quote"},
         "count": {"$ref": "#/definitions/count"},
         "format": {
-          "description": "Valid date format string",
+          "description": "Valid datetime format string",
+          "default": "%d-%m-%Y",
+          "examples": [
+            "%Y%m%d",
+            "%m/%d/%Y",
+            "%H:%M:%S"
+          ],
           "type": "string"
         },
-        "delta_days": {
+        "duration_days": {
+          "description": "The number of days from the start date to create date strings for",
+          "default": 30,
+          "examples": [
+            1, 30, 90, 9999
+          ],
           "oneOf": [
-            {"type": "integer"},
             {
-              "type": "array",
-              "minItems": 2,
-              "maxItems": 2,
-              "items": { "type": "integer" }
+              "type": "number",
+              "minimum": 0
+            },
+            {
+              "type": "string",
+              "pattern": "[1-9][0-9]*"
             }
           ]
         },
-        "anchor": {"type": "string"},
-        "offset": {"type": "integer"}
+        "start": {
+          "type": "string",
+          "default": null,
+          "description": "date string matching format or default format to use for start date",
+          "examples": [
+            "22-02-2022", "02/22/1972", "2009-09-01T08:08.000Z"
+          ]
+        },
+        "end": {
+          "type": "string",
+          "default": null,
+          "description": "date string matching format or default format to use for end date",
+          "examples": [
+            "22-02-2022", "02/22/1972", "2009-09-01T08:08.000Z"
+          ]
+        },
+        "offset": {
+          "type": "integer",
+          "description": "number of days to shift base date by, positive means shift backwards, negative means forward",
+          "default": 0,
+          "examples": [
+            30, -30, 365, 730
+          ]
+        },
+        "center_date": {
+          "type": "string",
+          "default": null,
+          "description": "date string matching format or default format to use for center date",
+          "examples": [
+            "22-02-2022", "02/22/1972", "2009-09-01T08:08.000Z"
+          ]
+        },
+        "stddev_days": {
+          "description": "The standard deviation in days from the center date that dates should be distributed",
+          "default": 15,
+          "examples": [
+            1, 12, 720
+          ],
+          "oneOf": [
+            {
+              "type": "number",
+              "minimum": 0
+            },
+            {
+              "type": "string",
+              "pattern": "[1-9][0-9]*"
+            }
+          ]
+        }
+      },
+      "not": {
+        "description": "center_date and stddev_days should not appear with start, end, or duration_days",
+        "anyOf": [
+          {"required": ["center_date", "start"]},
+          {"required": ["center_date", "end"]},
+          {"required": ["center_date", "duration_days"]},
+          {"required": ["stddev_days", "start"]},
+          {"required": ["stddev_days", "end"]},
+          {"required": ["stddev_days", "duration_days"]}
+        ]
       }
     },
     "additionalProperties": false
diff --git a/dataspec/schema/definitions.json b/dataspec/schema/definitions.json
index e6085b3..cc30278 100644
--- a/dataspec/schema/definitions.json
+++ b/dataspec/schema/definitions.json
@@ -2,10 +2,30 @@
   "$schema": "http://json-schema.org/draft-07/schema#",
   "$id": "https://github.com/bbux-dev/dataspec/schemas/definitions.json",
   "definitions": {
-    "prefix": {"type": "string"},
-    "suffix": {"type": "string"},
-    "quote": {"type": "string"},
-    "join_with": {"type": "string"},
+    "prefix": {
+      "type": "string",
+      "description": "Prefix string pre-pended to all values for field",
+      "default": null,
+      "examples": ["@", "www.", "<h1>", "#"]
+    },
+    "suffix": {
+      "type": "string",
+      "description": "Suffix string appended to all values for field",
+      "default": null,
+      "examples": [".com", "</h1>", "@"]
+    },
+    "quote": {
+      "type": "string",
+      "description": "String to surround all values for field with",
+      "default": null,
+      "examples": ["\"", "'", "||"]
+    },
+    "join_with": {
+      "type": "string",
+      "description": "String or character to join multiple values together with",
+      "default": null,
+      "examples": [",", "@", " OR ", " && "]
+    },
     "affirmative_check": {
       "description": "Either true, false, 'on', 'off', 'yes', 'no', 'true', 'false', case insensitive",
       "oneOf": [
@@ -13,9 +33,18 @@
         {"type": "string", "pattern": "[t|T][r|R][u|U][e|E]|[f|F][a|A][l|L][s|S][e|E]|[y|Y][e|E][s|S]|[n|N][o|O]|[o|O][n|N]|[o|O][f|F][f|F]"}
       ]
     },
-    "as_list": {"$ref": "#/definitions/affirmative_check"},
-    "cast": {"type": "string", "enum": ["i", "int", "f", "float", "s", "string", "string", "h", "hex"]},
+    "as_list": {
+      "description": "If the values should be returned as a list. Either true, false, 'on', 'off', 'yes', 'no', 'true', 'false', case insensitive",
+      "$ref": "#/definitions/affirmative_check"
+    },
+    "cast": {
+      "type": "string",
+      "description": "Type to cast values to for field",
+      "examples": ["i", "int", "f", "float", "s", "str", "string", "h", "hex"],
+      "enum": ["i", "int", "f", "float", "s", "str", "string", "h", "hex"]
+    },
     "count": {
+      "description": "Number of values to include, constant, list, or weighted values spec",
       "oneOf": [
         {"type": "integer"},
         {
@@ -34,14 +63,17 @@
         }
       ]
     },
-    "min": {"type": "number"},
-    "max": {"type": "number"},
-    "mean": {"type": "number"},
-    "stddev": {"type": "number"},
-    "precision": {"type": "integer"},
+    "precision": {
+      "type": "integer",
+      "description": "How many digits after decimal point to include in values",
+      "default": null,
+      "examples": [0, 2, 7, 12],
+      "minimum": 0
+    },
     "bbox": {
-      "description": "Bounding box for geo related points, describes lower left and upper right coordinates with longitude first",
       "type": "array",
+      "description": "Bounding box for geo related points, describes lower left and upper right coordinates with longitude first",
+      "default": null,
       "minItems": 4,
       "maxItems": 4,
       "items":[
diff --git a/dataspec/schema/range.schema.json b/dataspec/schema/range.schema.json
index ee6417a..f675484 100644
--- a/dataspec/schema/range.schema.json
+++ b/dataspec/schema/range.schema.json
@@ -12,7 +12,8 @@
         "suffix": {"$ref": "#/definitions/suffix"},
         "quote": {"$ref": "#/definitions/quote"},
         "count": {"$ref": "#/definitions/count"},
-        "cast": {"$ref": "#/definitions/cast"}
+        "cast": {"$ref": "#/definitions/cast"},
+        "precision": {"$ref": "#/definitions/precision"}
       }
     },
     "data": {
diff --git a/dataspec/schema/select_list_subset.schema.json b/dataspec/schema/select_list_subset.schema.json
new file mode 100644
index 0000000..d79b6ce
--- /dev/null
+++ b/dataspec/schema/select_list_subset.schema.json
@@ -0,0 +1,75 @@
+{
+  "$schema": "http://json-schema.org/draft-07/schema#",
+  "$id": "https://github.com/bbux-dev/dataspec/schemas/select_list_subset.schema.json",
+  "type": "object",
+  "required": ["type"],
+  "oneOf": [
+    {"required": ["ref"]},
+    {"required": ["data"]}
+  ],
+  "properties": {
+    "type": {"type": "string", "pattern": "^select_list_subset$"},
+    "config": {
+      "type": "object",
+      "oneOf": [
+        {"required": ["count"]},
+        {"required": ["mean"]}
+      ],
+      "properties": {
+        "prefix": {"$ref": "#/definitions/prefix"},
+        "suffix": {"$ref": "#/definitions/suffix"},
+        "quote": {"$ref": "#/definitions/quote"},
+        "min": {
+          "type": "integer",
+          "description": "minimum number of items to select from list",
+          "default": null,
+          "examples": [1, 7, 2255],
+          "minimum": 1
+        },
+        "max": {
+          "type": "integer",
+          "description": "maximum number of items to select from list",
+          "default": null,
+          "examples": [1, 7, 2255],
+          "minimum": 1
+        },
+        "mean": {
+          "type": "number",
+          "description": "mean number of items to select from list",
+          "default": null,
+          "examples": [3, 5, 7.5]
+        },
+        "stddev": {
+          "type": "number",
+          "description": "standard deviation from mean for number of items to select from list",
+          "default": null,
+          "examples": [0.5, 3, 7]
+        },
+        "count": {
+          "type": "number",
+          "description": "exact number of items to select from list",
+          "default": null,
+          "minimum": 1,
+          "examples": [3, 5, 7]
+        }
+      },
+      "not": {
+        "description": "count should not be present if min max or mean are present",
+        "anyOf": [
+          {"required": ["count", "min"]},
+          {"required": ["count", "max"]},
+          {"required": ["count", "mean"]}
+        ]
+      }
+    },
+    "ref": {
+      "type": "string",
+      "description": "The valid name of a spec from the ref section the contains a list as the data element"
+    },
+    "data": {
+      "type": "array",
+      "description": "The data to select from",
+      "minItems": 1
+    }
+  }
+}
\ No newline at end of file
diff --git a/dataspec/schema/unicode_range.schema.json b/dataspec/schema/unicode_range.schema.json
index da30c07..f7da253 100644
--- a/dataspec/schema/unicode_range.schema.json
+++ b/dataspec/schema/unicode_range.schema.json
@@ -12,10 +12,32 @@
         "suffix": {"$ref": "#/definitions/suffix"},
         "quote": {"$ref": "#/definitions/quote"},
         "count": {"$ref": "#/definitions/count"},
-        "min": {"$ref": "#/definitions/min"},
-        "max": {"$ref": "#/definitions/max"},
-        "mean": {"$ref": "#/definitions/mean"},
-        "stddev": {"$ref": "#/definitions/stddev"}
+        "min": {
+          "type": "integer",
+          "description": "minimum number of characters in string",
+          "default": null,
+          "examples": [1, 7, 2255],
+          "minimum": 1
+        },
+        "max": {
+          "type": "integer",
+          "description": "maximum number of characters in string",
+          "default": null,
+          "examples": [1, 7, 2255],
+          "minimum": 1
+        },
+        "mean": {
+          "type": "number",
+          "description": "mean number of characters in string",
+          "default": null,
+          "examples": [3, 5, 7.5]
+        },
+        "stddev": {
+          "type": "number",
+          "description": "standard deviation from mean for number of characters in string",
+          "default": null,
+          "examples": [0.5, 3, 7]
+        }
       },
       "not": {
         "description": "count should not be present if min max or mean are present",
diff --git a/dataspec/schemas.py b/dataspec/schemas.py
index f79ed44..a4b5660 100644
--- a/dataspec/schemas.py
+++ b/dataspec/schemas.py
@@ -1,8 +1,7 @@
-""" module for dealing with schemas """
 import json
 import logging
 import importlib.resources as pkg_resources
-from jsonschema import Draft7Validator
+from jsonschema import Draft7Validator  # type: ignore
 from dataspec import schema
 from .exceptions import ResourceError, SpecException
 
diff --git a/dataspec/supplier/combine.py b/dataspec/supplier/combine.py
deleted file mode 100644
index 2792800..0000000
--- a/dataspec/supplier/combine.py
+++ /dev/null
@@ -1,24 +0,0 @@
-"""
-Module for combine values supplier implementation
-"""
-from .value_supplier import ValueSupplierInterface
-
-
-class CombineValuesSupplier(ValueSupplierInterface):
-    """
-    Class for combining the values from the output of two or more value suppliers
-    """
-
-    def __init__(self, suppliers, config=None):
-        self.suppliers = suppliers
-        self.as_list = config.get('as_list', False) if config else False
-        if config and 'join_with' in config:
-            self.joiner = config.get('join_with')
-        else:
-            self.joiner = ''
-
-    def next(self, iteration):
-        values = [str(supplier.next(iteration)) for supplier in self.suppliers]
-        if self.as_list:
-            return values
-        return self.joiner.join(values)
diff --git a/dataspec/supplier/common.py b/dataspec/supplier/common.py
new file mode 100644
index 0000000..0f80667
--- /dev/null
+++ b/dataspec/supplier/common.py
@@ -0,0 +1,148 @@
+"""
+Module for holding classes for common supporting Value Suppliers.  These are not core suppliers, but are used to
+modify or wrap the functionality of the core suppliers.
+
+"""
+import random
+from collections import deque
+
+import dataspec
+
+
+class SingleValue(dataspec.ValueSupplierInterface):
+    """
+    Encapsulates supplier that only returns a static value
+    """
+
+    def __init__(self, data):
+        self.data = data
+
+    def next(self, _):
+        return self.data
+
+
+class MultipleValueSupplier(dataspec.ValueSupplierInterface):
+    """
+    Supplier that generates list of values based on count parameter
+    """
+
+    def __init__(self, wrapped: dataspec.ValueSupplierInterface, count_supplier: dataspec.ValueSupplierInterface):
+        self.wrapped = wrapped
+        self.count_supplier = count_supplier
+
+    def next(self, iteration):
+        count = self.count_supplier.next(iteration)
+        return [self.wrapped.next(iteration + i) for i in range(count)]
+
+
+class RotatingSupplierList(dataspec.ValueSupplierInterface):
+    """
+    Class that rotates through a list of suppliers incrementally to provide the next value
+    """
+
+    def __init__(self, suppliers, modulate_iteration):
+        """
+        :param suppliers: list of suppliers to rotate through
+        :param modulate_iteration: if the iteration should be split evenly across all suppliers
+        """
+        self.suppliers = suppliers
+        self.modulate_iteration = modulate_iteration
+
+    def next(self, iteration):
+        idx = iteration % len(self.suppliers)
+        if self.modulate_iteration:
+            modulated_iteration = int(iteration / len(self.suppliers))
+            return self.suppliers[idx].next(modulated_iteration)
+        return self.suppliers[idx].next(iteration)
+
+
+class DecoratedSupplier(dataspec.ValueSupplierInterface):
+    """
+    Class used to add additional data to other suppliers output, such as a
+    prefix or suffix or to surround the output with quotes
+    """
+
+    def __init__(self, config, supplier):
+        self.prefix = config.get('prefix', '')
+        self.suffix = config.get('suffix', '')
+        self.quote = config.get('quote', '')
+        self.wrapped = supplier
+
+    def next(self, iteration):
+        value = self.wrapped.next(iteration)
+        # todo: cache mapping for efficiency?
+        return f'{self.quote}{self.prefix}{value}{self.suffix}{self.quote}'
+
+
+class CastingSupplier(dataspec.ValueSupplierInterface):
+    """
+    Class that just casts the results of other suppliers
+    """
+
+    def __init__(self, wrapped, caster):
+        self.wrapped = wrapped
+        self.caster = caster
+
+    def next(self, iteration):
+        return self.caster.cast(self.wrapped.next(iteration))
+
+
+class RandomRangeSupplier(dataspec.ValueSupplierInterface):
+    """
+    Class that supplies values uniformly selected from specified bounds
+    """
+
+    def __init__(self, start, end, precision, count_supplier: dataspec.ValueSupplierInterface):
+        self.start = float(start)
+        self.end = float(end)
+        self.precision = precision
+        self.format_str = '{: .' + str(precision) + 'f}'
+        self.count_supplier = count_supplier
+
+    def next(self, iteration):
+        count = self.count_supplier.next(iteration)
+        next_nums = [random.uniform(self.start, self.end) for _ in range(count)]
+        if self.precision is not None:
+            next_nums = [self.format_str.format(next_num) for next_num in next_nums]
+        if count == 1:
+            return next_nums[0]
+        return next_nums
+
+
+class DistributionBackedSupplier(dataspec.ValueSupplierInterface):
+    """
+    Class that supplies values selected from a distribution such as a Guassian or Uniform distribution.
+
+    @see dataspec.distributions
+    """
+
+    def __init__(self, distribution: dataspec.Distribution):
+        self.distribution = distribution
+
+    def next(self, _):
+        return self.distribution.next_value()
+
+
+class BufferedValueSuppier(dataspec.ValueSupplierInterface):
+    """
+    Class for buffering the values from other suppliers. This allows the interaction
+    of one supplier with the previous values of another supplier
+    """
+
+    def __init__(self, wrapped: dataspec.ValueSupplierInterface, buffer_size: int):
+        self.wrapped = wrapped
+        self.buffer: deque = deque(maxlen=buffer_size)
+        self.current = -1
+        self.buffer_size = buffer_size
+
+    def next(self, iteration):
+        if iteration > self.current:
+            value = self.wrapped.next(iteration)
+            self.buffer.append(value)
+            self.current = iteration
+            return value
+
+        idx = len(self.buffer) - (self.current - iteration) - 1
+        if idx < 0:
+            raise ValueError('Buffer index out of range')
+        return self.buffer[idx]
\ No newline at end of file
diff --git a/dataspec/supplier/core/__init__.py b/dataspec/supplier/core/__init__.py
new file mode 100644
index 0000000..797f297
--- /dev/null
+++ b/dataspec/supplier/core/__init__.py
@@ -0,0 +1,14 @@
+__all__ = ['char_class',
+           'combine',
+           'configref',
+           'csv',
+           'date',
+           'geo',
+           'ip_addresses',
+           'nested',
+           'range_suppliers',
+           'select_list_subset',
+           'unicode_range',
+           'uuid_handler',
+           'values',
+           'weighted_refs',]
\ No newline at end of file
diff --git a/dataspec/type_handlers/char_class_handler.py b/dataspec/supplier/core/char_class.py
similarity index 74%
rename from dataspec/type_handlers/char_class_handler.py
rename to dataspec/supplier/core/char_class.py
index 3e6c70a..f5a834a 100644
--- a/dataspec/type_handlers/char_class_handler.py
+++ b/dataspec/supplier/core/char_class.py
@@ -3,10 +3,8 @@
 """
 import json
 import string
-from dataspec import registry, SpecException
-from dataspec.utils import any_key_exists
-from dataspec.suppliers import list_count_sampler, list_stat_sampler
-import dataspec.schemas as schemas
+
+import dataspec
 
 CHAR_CLASS_KEY = 'char_class'
 _UNDER_SCORE = '_'
@@ -28,17 +26,24 @@
 }
 
 
-@registry.schemas(CHAR_CLASS_KEY)
+@dataspec.registry.schemas(CHAR_CLASS_KEY)
 def get_char_class_schema():
     """ get the schema for the char_class type """
-    return schemas.load(CHAR_CLASS_KEY)
+    return dataspec.schemas.load(CHAR_CLASS_KEY)
+
+
+for key in _CLASS_MAPPING.keys():
+    @dataspec.registry.schemas("cc-" + key)
+    def get_char_class_alias_schema():
+        """ get the schema for the char_class type """
+        return dataspec.schemas.load(CHAR_CLASS_KEY)
 
 
-@registry.types(CHAR_CLASS_KEY)
+@dataspec.registry.types(CHAR_CLASS_KEY)
 def configure_supplier(spec, _):
     """ configure the supplier for char_class types """
     if 'data' not in spec:
-        raise SpecException(f'Data is required field for char_class type: {json.dumps(spec)}')
+        raise dataspec.SpecException(f'Data is required field for char_class type: {json.dumps(spec)}')
     config = spec.get('config', {})
     data = spec['data']
     if isinstance(data, str) and data in _CLASS_MAPPING:
@@ -50,97 +55,97 @@ def configure_supplier(spec, _):
         for char_to_exclude in config.get('exclude'):
             data = data.replace(char_to_exclude, '')
     if 'join_with' not in config:
-        config['join_with'] = ''
-    if any_key_exists(config, ['mean', 'stddev']):
-        return list_stat_sampler(data, config)
-    return list_count_sampler(data, config)
+        config['join_with'] = dataspec.types.get_default('char_class_join_with')
+    if dataspec.utils.any_key_exists(config, ['mean', 'stddev']):
+        return dataspec.suppliers.list_stat_sampler(data, config)
+    return dataspec.suppliers.list_count_sampler(data, config)
 
 
-@registry.types('cc-ascii')
+@dataspec.registry.types('cc-ascii')
 def configure_ascii_supplier(spec, loader):
     """ configure the supplier for char_class types """
     spec['data'] = 'ascii'
     return configure_supplier(spec, loader)
 
 
-@registry.types('cc-lower')
+@dataspec.registry.types('cc-lower')
 def configure_lower_supplier(spec, loader):
     """ configure the supplier for lower char_class types """
     spec['data'] = 'lower'
     return configure_supplier(spec, loader)
 
 
-@registry.types('cc-upper')
+@dataspec.registry.types('cc-upper')
 def configure_upper_supplier(spec, loader):
     """ configure the supplier for upper char_class types """
     spec['data'] = 'upper'
     return configure_supplier(spec, loader)
 
 
-@registry.types('cc-letters')
+@dataspec.registry.types('cc-letters')
 def configure_letters_supplier(spec, loader):
     """ configure the supplier for letters char_class types """
     spec['data'] = 'letters'
     return configure_supplier(spec, loader)
 
 
-@registry.types('cc-word')
+@dataspec.registry.types('cc-word')
 def configure_word_supplier(spec, loader):
     """ configure the supplier for char_class types """
     spec['data'] = 'word'
     return configure_supplier(spec, loader)
 
 
-@registry.types('cc-printable')
+@dataspec.registry.types('cc-printable')
 def configure_printable_supplier(spec, loader):
     """ configure the supplier for char_class types """
     spec['data'] = 'printable'
     return configure_supplier(spec, loader)
 
 
-@registry.types('cc-visible')
+@dataspec.registry.types('cc-visible')
 def configure_visible_supplier(spec, loader):
     """ configure the supplier for visible char_class types """
     spec['data'] = 'visible'
     return configure_supplier(spec, loader)
 
 
-@registry.types('cc-punctuation')
+@dataspec.registry.types('cc-punctuation')
 def configure_punctuation_supplier(spec, loader):
     """ configure the supplier for char_class types """
     spec['data'] = 'punctuation'
     return configure_supplier(spec, loader)
 
 
-@registry.types('cc-special')
+@dataspec.registry.types('cc-special')
 def configure_special_supplier(spec, loader):
     """ configure the supplier for special char_class types """
     spec['data'] = 'special'
     return configure_supplier(spec, loader)
 
 
-@registry.types('cc-digits')
+@dataspec.registry.types('cc-digits')
 def configure_digits_supplier(spec, loader):
     """ configure the supplier for digits char_class types """
     spec['data'] = 'digits'
     return configure_supplier(spec, loader)
 
 
-@registry.types('cc-hex')
+@dataspec.registry.types('cc-hex')
 def configure_hex_supplier(spec, loader):
     """ configure the supplier for hex char_class types """
     spec['data'] = 'hex'
     return configure_supplier(spec, loader)
 
 
-@registry.types('cc-hex-lower')
+@dataspec.registry.types('cc-hex-lower')
 def configure_hex_lower_supplier(spec, loader):
     """ configure the supplier for hex-lower char_class types """
     spec['data'] = 'hex-lower'
     return configure_supplier(spec, loader)
 
 
-@registry.types('cc-hex-upper')
+@dataspec.registry.types('cc-hex-upper')
 def configure_hex_upper_supplier(spec, loader):
     """ configure the supplier for hex-upper char_class types """
     spec['data'] = 'hex-upper'
diff --git a/dataspec/type_handlers/combine.py b/dataspec/supplier/core/combine.py
similarity index 58%
rename from dataspec/type_handlers/combine.py
rename to dataspec/supplier/core/combine.py
index 28b94f0..409415b 100644
--- a/dataspec/type_handlers/combine.py
+++ b/dataspec/supplier/core/combine.py
@@ -13,32 +13,49 @@
   }
 }
 """
+from typing import List
 import json
-from dataspec import registry, suppliers, SpecException
-from dataspec.suppliers import from_list_of_suppliers
-import dataspec.schemas as schemas
+
+import dataspec
 
 COMBINE_KEY = 'combine'
 COMBINE_LIST_KEY = 'combine-list'
 
 
-@registry.schemas(COMBINE_KEY)
+class CombineValuesSupplier(dataspec.ValueSupplierInterface):
+    """
+    Class for combining the values from the output of two or more value suppliers
+    """
+
+    def __init__(self, suppliers: List[dataspec.ValueSupplierInterface], config: dict):
+        self.suppliers = suppliers
+        self.as_list = config.get('as_list', dataspec.types.get_default('combine_as_list'))
+        self.joiner = config.get('join_with', dataspec.types.get_default('combine_join_with'))
+
+    def next(self, iteration):
+        values = [str(supplier.next(iteration)) for supplier in self.suppliers]
+        if self.as_list:
+            return values
+        return self.joiner.join(values)
+
+
+@dataspec.registry.schemas(COMBINE_KEY)
 def get_combine_schema():
     """ get the schema for the combine type """
-    return schemas.load(COMBINE_KEY)
+    return dataspec.schemas.load(COMBINE_KEY)
 
 
-@registry.schemas(COMBINE_LIST_KEY)
+@dataspec.registry.schemas(COMBINE_LIST_KEY)
 def get_combine_list_schema():
     """ get the schema for the combine_list type """
-    return schemas.load(COMBINE_LIST_KEY)
+    return dataspec.schemas.load(COMBINE_LIST_KEY)
 
 
-@registry.types(COMBINE_KEY)
+@dataspec.registry.types(COMBINE_KEY)
 def configure_combine_supplier(field_spec, loader):
     """ configures supplier for combine type """
     if 'refs' not in field_spec and 'fields' not in field_spec:
-        raise SpecException('Must define one of fields or refs. %s' % json.dumps(field_spec))
+        raise dataspec.SpecException('Must define one of fields or refs. %s' % json.dumps(field_spec))
 
     if 'refs' in field_spec:
         supplier = _load_from_refs(field_spec, loader)
@@ -47,22 +64,23 @@ def configure_combine_supplier(field_spec, loader):
     return supplier
 
 
-@registry.types(COMBINE_LIST_KEY)
+@dataspec.registry.types(COMBINE_LIST_KEY)
 def configure_combine_list_supplier(field_spec, loader):
     """ configures supplier for combine-list type """
     if 'refs' not in field_spec:
-        raise SpecException('Must define refs for combine-list type. %s' % json.dumps(field_spec))
+        raise dataspec.SpecException('Must define refs for combine-list type. %s' % json.dumps(field_spec))
 
     refs_list = field_spec['refs']
     if len(refs_list) < 1 or not isinstance(refs_list[0], list):
-        raise SpecException('refs pointer must be list of lists: i.e [["ONE", "TWO"]]. %s' % json.dumps(field_spec))
+        raise dataspec.SpecException(
+            'refs pointer must be list of lists: i.e [["ONE", "TWO"]]. %s' % json.dumps(field_spec))
 
     suppliers_list = []
     for ref in refs_list:
         spec = dict(field_spec)
         spec['refs'] = ref
         suppliers_list.append(_load_from_refs(spec, loader))
-    return from_list_of_suppliers(suppliers_list, True)
+    return dataspec.suppliers.from_list_of_suppliers(suppliers_list, True)
 
 
 def _load_from_refs(combine_field_spec, loader):
@@ -83,4 +101,5 @@ def _load(combine_field_spec, keys, loader):
     for key in keys:
         supplier = loader.get(key)
         to_combine.append(supplier)
-    return suppliers.combine(to_combine, combine_field_spec.get('config'))
+    config = combine_field_spec.get('config', {})
+    return CombineValuesSupplier(to_combine, config)
diff --git a/dataspec/type_handlers/configref.py b/dataspec/supplier/core/configref.py
similarity index 100%
rename from dataspec/type_handlers/configref.py
rename to dataspec/supplier/core/configref.py
diff --git a/dataspec/type_handlers/csv_handler.py b/dataspec/supplier/core/csv.py
similarity index 86%
rename from dataspec/type_handlers/csv_handler.py
rename to dataspec/supplier/core/csv.py
index e9b21c1..b17a5e0 100644
--- a/dataspec/type_handlers/csv_handler.py
+++ b/dataspec/supplier/core/csv.py
@@ -1,23 +1,20 @@
 """
 Module for handling csv related data, deals with data typed as 'csv'
 """
-import os
+from typing import Dict
 import csv
 import json
+import os
 import random
 from typing import Union
-from dataspec import registry, ValueSupplierInterface, SpecException
-from dataspec.suppliers import count_supplier_from_data
-from dataspec.utils import load_config, is_affirmative
+
+import dataspec
 
 # 250 MB
 ONE_MB = 1024 * 1024
 SMALL_ENOUGH_THRESHOLD = 250 * ONE_MB
 _DEFAULT_BUFFER_SIZE = 1000000
 
-# to keep from reloading the same CsvData
-_csv_data_cache = {}
-
 
 class CsvDataBase:
     """
@@ -62,7 +59,7 @@ def _get_column_index(self, field: Union[int, str]):
         # if we had headers we can use that name, otherwise field should be one based index
         colidx = self.mapping.get(field)
         if colidx is None and not str(field).isdigit():
-            raise SpecException(f'Invalid field name: {field} for csv, known keys: {self.valid_keys}')
+            raise dataspec.SpecException(f'Invalid field name: {field} for csv, known keys: {self.valid_keys}')
         if colidx is None:
             colidx = int(field) - 1
         return colidx
@@ -109,7 +106,7 @@ def __init__(self, csv_path: str, delimiter: str, quotechar: str, has_headers: b
         self.quotechar = quotechar
         self.size = buffer_size
         self.increment = -1
-        self.data = None
+        self.data = None  # type: ignore
         self.fill_buffer(0)
         # need to populate data before super constructor call
         super().__init__(has_headers)
@@ -143,24 +140,24 @@ def fill_buffer(self, iteration: int) -> None:
 
     def next(self, field, iteration, sample, count):
         if sample:
-            raise SpecException('Large CSV files do not support sample mode')
+            raise dataspec.SpecException('Large CSV files do not support sample mode')
         if count > 1:
-            raise SpecException('Large CSV files only support count of 1')
+            raise dataspec.SpecException('Large CSV files only support count of 1')
         self.fill_buffer(iteration)
         colidx = self._get_column_index(field)
 
         idx = iteration % self.size
         if idx >= len(self.data):
-            raise SpecException("Exceeded end of CSV data, unable to proceed with large CSV files")
+            raise dataspec.SpecException("Exceeded end of CSV data, unable to proceed with large CSV files")
         return self.data[idx][colidx]
 
 
-class CsvSupplier(ValueSupplierInterface):
+class CsvSupplier(dataspec.ValueSupplierInterface):
     """
     Class for supplying data from a specific field in a csv file
     """
 
-    def __init__(self, csv_data, field_name, sample, count_supplier: ValueSupplierInterface):
+    def __init__(self, csv_data, field_name, sample, count_supplier: dataspec.ValueSupplierInterface):
         self.csv_data = csv_data
         self.field_name = field_name
         self.sample = sample
@@ -171,14 +168,18 @@ def next(self, iteration):
         return self.csv_data.next(self.field_name, iteration, self.sample, count)
 
 
-@registry.types('csv')
+# to keep from reloading the same CsvData
+_csv_data_cache: Dict[str, CsvDataBase] = {}
+
+
+@dataspec.registry.types('csv')
 def configure_csv(field_spec, loader):
     """ Configures the csv value supplier for this field """
-    config = load_config(field_spec, loader)
+    config = dataspec.utils.load_config(field_spec, loader)
 
     field_name = config.get('column', 1)
-    sample = is_affirmative('sample', config)
-    count_supplier = count_supplier_from_data(config.get('count', 1))
+    sample = dataspec.utils.is_affirmative('sample', config)
+    count_supplier = dataspec.suppliers.count_supplier_from_data(config.get('count', 1))
 
     csv_data = _load_csv_data(field_spec, config, loader.datadir)
     return CsvSupplier(csv_data, field_name, sample, count_supplier)
@@ -198,14 +199,14 @@ def _load_csv_data(field_spec, config, datadir):
         return _csv_data_cache.get(csv_path)
 
     if not os.path.exists(csv_path):
-        raise SpecException(f'Unable to locate data file: {datafile} in data dir: {datadir} for spec: '
+        raise dataspec.SpecException(f'Unable to locate data file: {datafile} in data dir: {datadir} for spec: '
                             + json.dumps(field_spec))
     delimiter = config.get('delimiter', ',')
     # in case tab came in as string
     if delimiter == '\\t':
         delimiter = '\t'
     quotechar = config.get('quotechar', '"')
-    has_headers = is_affirmative('headers', config)
+    has_headers = dataspec.utils.is_affirmative('headers', config)
 
     size_in_bytes = os.stat(csv_path).st_size
     if size_in_bytes <= SMALL_ENOUGH_THRESHOLD:
diff --git a/dataspec/supplier/core/date.py b/dataspec/supplier/core/date.py
new file mode 100644
index 0000000..a1cb15e
--- /dev/null
+++ b/dataspec/supplier/core/date.py
@@ -0,0 +1,172 @@
+"""
+Module for handling date related types
+
+The date Field Spec structure is:
+
+{
+  "<field name>": {
+    "type": "date",
+    OR,
+    "type": "date.iso",
+    OR,
+    "type": "date.iso.us",
+    "config": {"...": "..."}
+  }
+}
+
+"""
+import datetime
+import json
+
+import dataspec
+import dataspec.model
+
+DATE_KEY = 'date'
+DATE_ISO_KEY = 'date.iso'
+DATE_ISO_US_KEY = 'date.iso.us'
+
+ISO_FORMAT_NO_MICRO = '%Y-%m-%dT%H:%M:%S'
+ISO_FORMAT_WITH_MICRO = '%Y-%m-%dT%H:%M:%S.%f'
+
+SECONDS_IN_DAY = 24.0 * 60.0 * 60.0
+
+
+@dataspec.registry.schemas(DATE_KEY)
+def get_date_schema():
+    """ returns the schema for date types """
+    return dataspec.schemas.load(DATE_KEY)
+
+
+@dataspec.registry.schemas(DATE_ISO_KEY)
+def get_date_iso_schema():
+    """ returns the schema for date.iso types """
+    # NOTE: These all share a schema
+    return dataspec.schemas.load(DATE_KEY)
+
+
+@dataspec.registry.schemas(DATE_ISO_US_KEY)
+def get_date_iso_us_schema():
+    """ returns the schema for date.iso.us types """
+    # NOTE: These all share a schema
+    return dataspec.schemas.load(DATE_KEY)
+
+
+class DateSupplier(dataspec.ValueSupplierInterface):
+    """
+    Value Supplier implementation for dates
+    """
+
+    def __init__(self,
+                 timestamp_distribution: dataspec.model.Distribution,
+                 date_format_string: str):
+        self.date_format = date_format_string
+        self.timestamp_distribution = timestamp_distribution
+
+    def next(self, iteration):
+        random_seconds = self.timestamp_distribution.next_value()
+        next_date = datetime.datetime.fromtimestamp(random_seconds)
+        if self.date_format:
+            return next_date.strftime(self.date_format)
+        return next_date.replace(microsecond=0).isoformat()
+
+
+def uniform_date_timestamp(
+        start: str,
+        end: str,
+        offset: int,
+        duration: int,
+        date_format_string: str):
+    offset_date = datetime.timedelta(days=offset)
+    if start:
+        start_date = datetime.datetime.strptime(start, date_format_string) - offset_date
+    else:
+        start_date = datetime.datetime.now() - offset_date
+    if end:
+        # buffer end date by one to keep inclusive
+        end_date = datetime.datetime.strptime(end, date_format_string) \
+                   + datetime.timedelta(days=1) - offset_date
+    else:
+        # start date already include offset, don't include it here
+        end_date = start_date + datetime.timedelta(days=abs(int(duration)), seconds=1)
+
+    start_ts = int(start_date.timestamp())
+    end_ts = int(end_date.timestamp())
+    if end_ts < start_ts:
+        return None
+    return dataspec.distributions.uniform(start=start_ts, end=end_ts)
+
+
+def gauss_date_timestamp(
+        center_date_str: str,
+        stddev_days: float,
+        date_format_string: str):
+    if center_date_str:
+        center_date = datetime.datetime.strptime(center_date_str, date_format_string)
+    else:
+        center_date = datetime.datetime.now()
+    mean = center_date.timestamp()
+    stddev = stddev_days * SECONDS_IN_DAY
+    return dataspec.distributions.normal(mean=mean, stddev=stddev)
+
+
+@dataspec.registry.types(DATE_KEY)
+def configure_supplier(field_spec: dict, loader: dataspec.Loader):
+    """ configures the date value supplier """
+    config = dataspec.utils.load_config(field_spec, loader)
+    if 'center_date' in config or 'stddev_days' in config:
+        return _create_stats_based_date_supplier(config)
+    return _create_uniform_date_supplier(config)
+
+
+def _create_stats_based_date_supplier(config):
+    center_date = config.get('center_date')
+    stddev_days = config.get('stddev_days', dataspec.types.get_default('date_stddev_days'))
+    date_format = config.get('format', dataspec.types.get_default('date_format'))
+    timestamp_distribution = gauss_date_timestamp(center_date, float(stddev_days), date_format)
+    return DateSupplier(timestamp_distribution, date_format)
+
+
+def _create_uniform_date_supplier(config):
+    duration_days = config.get('duration_days', 30)
+    offset = int(config.get('offset', 0))
+    start = config.get('start')
+    end = config.get('end')
+    date_format = config.get('format', dataspec.types.get_default('date_format'))
+    timestamp_distribution = uniform_date_timestamp(start, end, offset, duration_days, date_format)
+    if timestamp_distribution is None:
+        raise dataspec.SpecException(f'Unable to generate timestamp supplier from config: {json.dumps(config)}')
+    return DateSupplier(timestamp_distribution, date_format)
+
+
+@dataspec.registry.types(DATE_ISO_KEY)
+def configure_supplier_iso(field_spec: dict, loader: dataspec.Loader):
+    """ configures the date.iso value supplier """
+    return _configure_supplier_iso_date(field_spec, loader, ISO_FORMAT_NO_MICRO)
+
+
+@dataspec.registry.types(DATE_ISO_US_KEY)
+def configure_supplier_iso_microseconds(field_spec: dict, loader: dataspec.Loader):
+    """ configures the date.iso.us value supplier """
+    return _configure_supplier_iso_date(field_spec, loader, ISO_FORMAT_WITH_MICRO)
+
+
+def _configure_supplier_iso_date(field_spec, loader, iso_date_format):
+    """ configures an iso based date supplier using the provided date format """
+    config = dataspec.utils.load_config(field_spec, loader)
+
+    # make sure the start and end dates match the ISO format we are using
+    start = config.get('start')
+    end = config.get('end')
+    date_format = config.get('format', )
+    if start:
+        start_date = datetime.datetime.strptime(start, date_format)
+        config['start'] = start_date.strftime(iso_date_format)
+    if end:
+        end_date = datetime.datetime.strptime(end, date_format)
+        config['end'] = end_date.strftime(iso_date_format)
+    config['format'] = iso_date_format
+    # End fixes to support iso
+
+    if 'center_date' in config or 'stddev_days' in config:
+        return _create_stats_based_date_supplier(config)
+    return _create_uniform_date_supplier(config)
diff --git a/dataspec/type_handlers/geo_handler.py b/dataspec/supplier/core/geo.py
similarity index 58%
rename from dataspec/type_handlers/geo_handler.py
rename to dataspec/supplier/core/geo.py
index f0e74ce..d741077 100644
--- a/dataspec/type_handlers/geo_handler.py
+++ b/dataspec/supplier/core/geo.py
@@ -3,16 +3,14 @@
 """
 import json
 
-from dataspec import registry, suppliers, ValueSupplierInterface, SpecException
-import dataspec.schemas as schemas
-from dataspec.utils import load_config, is_affirmative
+import dataspec
 
 GEO_LAT_KEY = 'geo.lat'
 GEO_LONG_KEY = 'geo.long'
 GEO_PAIR_KEY = 'geo.pair'
 
 
-class GeoSupplier(ValueSupplierInterface):
+class GeoSupplier(dataspec.ValueSupplierInterface):
     """
     Default implementation for generating geo related values
     """
@@ -25,49 +23,49 @@ def next(self, iteration):
         return value
 
 
-@registry.schemas(GEO_LAT_KEY)
+@dataspec.registry.schemas(GEO_LAT_KEY)
 def get_geo_lat_schema():
-    return schemas.load(GEO_LAT_KEY)
+    return dataspec.schemas.load(GEO_LAT_KEY)
 
 
-@registry.schemas(GEO_LONG_KEY)
+@dataspec.registry.schemas(GEO_LONG_KEY)
 def get_geo_long_schema():
-    return schemas.load(GEO_LONG_KEY)
+    return dataspec.schemas.load(GEO_LONG_KEY)
 
 
-@registry.schemas(GEO_PAIR_KEY)
+@dataspec.registry.schemas(GEO_PAIR_KEY)
 def get_geo_pair_schema():
-    return schemas.load(GEO_PAIR_KEY)
+    return dataspec.schemas.load(GEO_PAIR_KEY)
 
 
-@registry.types(GEO_LAT_KEY)
+@dataspec.registry.types(GEO_LAT_KEY)
 def configure_geo_lat(field_spec, loader):
     """ configures value supplier for geo.lat type """
     return _configure_lat_type(field_spec, loader)
 
 
-@registry.types(GEO_LONG_KEY)
+@dataspec.registry.types(GEO_LONG_KEY)
 def configure_geo_long(field_spec, loader):
     """ configures value supplier for geo.long type """
     return _configure_long_type(field_spec, loader)
 
 
-@registry.types(GEO_PAIR_KEY)
+@dataspec.registry.types(GEO_PAIR_KEY)
 def configure_geo_pair(field_spec, loader):
     """ configures value supplier for geo.pair type """
-    config = load_config(field_spec, loader)
+    config = dataspec.utils.load_config(field_spec, loader)
     long_supplier = _configure_long_type(field_spec, loader)
     lat_supplier = _configure_lat_type(field_spec, loader)
-    join_with = config.get('join_with', ',')
-    as_list = is_affirmative('as_list', config, False)
-    lat_first = is_affirmative('lat_first', config)
+    join_with = config.get('join_with', dataspec.types.get_default('geo_join_with'))
+    as_list = dataspec.utils.is_affirmative('as_list', config, dataspec.types.get_default('geo_as_list'))
+    lat_first = dataspec.utils.is_affirmative('lat_first', config, dataspec.types.get_default('geo_lat_first'))
     combine_config = {
         'join_with': join_with,
         'as_list': as_list
     }
     if lat_first:
-        return suppliers.combine([lat_supplier, long_supplier], combine_config)
-    return suppliers.combine([long_supplier, lat_supplier], combine_config)
+        return dataspec.suppliers.combine([lat_supplier, long_supplier], combine_config)
+    return dataspec.suppliers.combine([long_supplier, lat_supplier], combine_config)
 
 
 def _configure_long_type(spec, loader):
@@ -79,19 +77,20 @@ def _configure_lat_type(spec, loader):
 
 
 def _configure_geo_type(spec, loader, default_start, default_end, suffix):
-    config = load_config(spec, loader)
-    precision = config.get('precision', 4)
+    config = dataspec.utils.load_config(spec, loader)
+    precision = config.get('precision', dataspec.types.get_default('geo_precision'))
     if not str(precision).isnumeric():
-        raise SpecException(f'precision for geo should be valid integer: {json.dumps(spec)}')
+        raise dataspec.SpecException(f'precision for geo should be valid integer: {json.dumps(spec)}')
     start, end = _get_start_end(config, default_start, default_end, suffix)
-    return GeoSupplier(suppliers.random_range(start, end, precision))
+    return GeoSupplier(dataspec.suppliers.random_range(start, end, precision))
 
 
 def _get_start_end(config, default_start, default_end, suffix):
     if 'bbox' in config:
         bbox = config['bbox']
         if not isinstance(bbox, list) or len(bbox) != 4:
-            raise SpecException('Bounding box must be list of size 4 with format: [min Longitude, min Latitude, max Longitude, max Latitude]')
+            raise dataspec.SpecException(
+                'Bounding box must be list of size 4 with format: [min Longitude, min Latitude, max Longitude, max Latitude]')
         if 'lat' in suffix:
             default_start = bbox[1]
             default_end = bbox[3]
diff --git a/dataspec/type_handlers/ip_handler.py b/dataspec/supplier/core/ip_addresses.py
similarity index 70%
rename from dataspec/type_handlers/ip_handler.py
rename to dataspec/supplier/core/ip_addresses.py
index 6d12f01..ba4b1be 100644
--- a/dataspec/type_handlers/ip_handler.py
+++ b/dataspec/supplier/core/ip_addresses.py
@@ -1,18 +1,17 @@
 """
 Module for handling ip types
 """
+import ipaddress
 import json
 import random
-import ipaddress
-from dataspec import registry, suppliers, ValueSupplierInterface, SpecException
-import dataspec.schemas as schemas
-from dataspec.utils import load_config
+
+import dataspec
 
 IP_KEY = 'ip'
 IPV4_KEY = 'ipv4'
 
 
-class IpV4Supplier(ValueSupplierInterface):
+class IpV4Supplier(dataspec.ValueSupplierInterface):
     """
     Default implementation for generating ip values, uses separate suppliers for each octet of the ip
     """
@@ -31,33 +30,34 @@ def next(self, iteration):
         return f'{first}.{second}.{third}.{fourth}'
 
 
-@registry.schemas(IP_KEY)
+@dataspec.registry.schemas(IP_KEY)
 def get_ip_schema():
-    return schemas.load(IP_KEY)
+    return dataspec.schemas.load(IP_KEY)
 
 
-@registry.schemas(IPV4_KEY)
+@dataspec.registry.schemas(IPV4_KEY)
 def get_ipv4_schema():
-    return schemas.load(IPV4_KEY)
+    # shares schema with ip
+    return dataspec.schemas.load(IP_KEY)
 
 
-@registry.types(IPV4_KEY)
+@dataspec.registry.types(IPV4_KEY)
 def configure_ipv4(field_spec, _):
     """ configures value supplier for ipv4 type """
     return configure_ip(field_spec, _)
 
 
-@registry.types(IP_KEY)
+@dataspec.registry.types(IP_KEY)
 def configure_ip(field_spec, loader):
     """ configures value supplier for ip type """
-    config = load_config(field_spec, loader)
+    config = dataspec.utils.load_config(field_spec, loader)
     if 'base' in config and 'cidr' in config:
-        raise SpecException('Must supply only one of base or cidr param: ' + json.dumps(field_spec))
+        raise dataspec.SpecException('Must supply only one of base or cidr param: ' + json.dumps(field_spec))
 
     parts = _get_base_parts(config)
     # this is the same thing as a constant
     if len(parts) == 4:
-        return suppliers.values('.'.join(parts))
+        return dataspec.suppliers.values('.'.join(parts))
     sample = config.get('sample', 'yes')
     octet_supplier_map = {
         'first': _create_octet_supplier(parts, 0, sample),
@@ -83,13 +83,13 @@ def _get_base_parts(config):
         if '/' in cidr:
             mask = cidr[cidr.index('/') + 1:]
             if not mask.isdigit():
-                raise SpecException('Invalid Mask in cidr for config: ' + json.dumps(config))
+                raise dataspec.SpecException('Invalid Mask in cidr for config: ' + json.dumps(config))
             if int(mask) not in [8, 16, 24]:
-                raise SpecException('Invalid Subnet Mask in cidr for config: ' + json.dumps(config)
+                raise dataspec.SpecException('Invalid Subnet Mask in cidr for config: ' + json.dumps(config)
                                     + ' only one of /8 /16 or /24 supported')
             ip_parts = cidr[0:cidr.index('/')].split('.')
             if len(ip_parts) < 4 or not all(part.isdigit() for part in ip_parts):
-                raise SpecException('Invalid IP in cidr for config: ' + json.dumps(config))
+                raise dataspec.SpecException('Invalid IP in cidr for config: ' + json.dumps(config))
             if mask == '8':
                 parts = ip_parts[0:1]
             if mask == '16':
@@ -97,7 +97,7 @@ def _get_base_parts(config):
             if mask == '24':
                 parts = ip_parts[0:3]
         else:
-            raise SpecException('Invalid Subnet Mask in cidr for config: ' + json.dumps(config)
+            raise dataspec.SpecException('Invalid Subnet Mask in cidr for config: ' + json.dumps(config)
                                 + ' only one of /8 /16 or /24 supported')
     return parts
 
@@ -108,17 +108,17 @@ def _create_octet_supplier(parts, index, sample):
     if len(parts) >= index + 1 and parts[index].strip() != '':
         octet = parts[index].strip()
         if not octet.isdigit():
-            raise SpecException(f'Octet: {octet} invalid for base, Invalid Input: ' + '.'.join(parts))
+            raise dataspec.SpecException(f'Octet: {octet} invalid for base, Invalid Input: ' + '.'.join(parts))
         if not 0 <= int(octet) <= 255:
-            raise SpecException(f'Each octet: {octet} must be in range of 0 to 255, Invalid Input: ' + '.'.join(parts))
-        return suppliers.values(octet)
+            raise dataspec.SpecException(f'Each octet: {octet} must be in range of 0 to 255, Invalid Input: ' + '.'.join(parts))
+        return dataspec.suppliers.values(octet)
     # need octet range at this point
     octet_range = list(range(0, 255))
     spec = {'config': {'sample': sample}, 'data': octet_range}
-    return suppliers.values(spec)
+    return dataspec.suppliers.values(spec)
 
 
-class IpV4PreciseSupplier(ValueSupplierInterface):
+class IpV4PreciseSupplier(dataspec.ValueSupplierInterface):
     """
     Class that supports precise ip address generation by specifying cidr values, much slower for large ip ranges
     """
@@ -139,14 +139,14 @@ def next(self, iteration):
         return str(self.net[idx])
 
 
-@registry.types('ip.precise')
+@dataspec.registry.types('ip.precise')
 def configure_precise_ip(field_spec, _):
     """ configures value supplier for ip.precise type """
     config = field_spec.get('config')
     if config is None:
-        raise SpecException('No config for: ' + json.dumps(field_spec) + ', param cidr required')
+        raise dataspec.SpecException('No config for: ' + json.dumps(field_spec) + ', param cidr required')
     cidr = config.get('cidr')
     sample = config.get('sample', 'no').lower() in ['yes', 'true', 'on']
     if cidr is None:
-        raise SpecException('Invalid config for: ' + json.dumps(field_spec) + ', param cidr required')
+        raise dataspec.SpecException('Invalid config for: ' + json.dumps(field_spec) + ', param cidr required')
     return IpV4PreciseSupplier(cidr, sample)
diff --git a/dataspec/type_handlers/nested_handler.py b/dataspec/supplier/core/nested.py
similarity index 73%
rename from dataspec/type_handlers/nested_handler.py
rename to dataspec/supplier/core/nested.py
index 355ac2d..dc062ba 100644
--- a/dataspec/type_handlers/nested_handler.py
+++ b/dataspec/supplier/core/nested.py
@@ -2,34 +2,36 @@
 Module for handling nested types
 """
 from typing import Dict, Any
+
 import dataspec
+import dataspec.model
 
 
-class NestedSupplier(dataspec.ValueSupplierInterface):
+class NestedSupplier(dataspec.model.ValueSupplierInterface):
     """
     Implementation for Nested Value Supplier
     """
 
     def __init__(self,
-                 field_supplier_map: Dict[str, dataspec.ValueSupplierInterface],
-                 count_supplier: dataspec.ValueSupplierInterface,
-                 as_array: bool):
+                 field_supplier_map: Dict[str, dataspec.model.ValueSupplierInterface],
+                 count_supplier: dataspec.model.ValueSupplierInterface,
+                 as_list: bool):
         self.field_supplier_map = field_supplier_map
         self.count_supplier = count_supplier
-        self.as_array = as_array
+        self.as_list = as_list
 
     def next(self, iteration: int):
         count = int(self.count_supplier.next(iteration))
         if count == 0:
-            if self.as_array:
+            if self.as_list:
                 return []
             return None
         if count > 1:
             vals = [self._single_pass(iteration + i) for i in range(count)]
             return vals
         # this is dict
-        vals = self._single_pass(iteration)
-        if self.as_array:
+        vals = self._single_pass(iteration)  # type: ignore
+        if self.as_list:
             return [vals]
         return vals
 
@@ -44,15 +46,15 @@ def configure_nested_supplier(spec, loader):
     keys = [key for key in fields.keys() if key not in loader.RESERVED]
     config = dataspec.utils.load_config(spec, loader)
     count_supplier = dataspec.suppliers.count_supplier_from_data(config.get('count', 1))
-    as_array = dataspec.utils.is_affirmative('as_array', config)
+    as_list = dataspec.utils.is_affirmative('as_list', config)
 
     field_supplier_map = {}
     # each non reserved key should have a valid spec as a value
     for key in keys:
         nested_spec = fields[key]
-        if nested_spec.get('type') == 'nested':
+        if 'type' in nested_spec and nested_spec.get('type') == 'nested':
             supplier = configure_nested_supplier(nested_spec, loader)
         else:
             supplier = loader.get_from_spec(nested_spec)
         field_supplier_map[key] = supplier
-    return NestedSupplier(field_supplier_map, count_supplier, as_array)
+    return NestedSupplier(field_supplier_map, count_supplier, as_list)
diff --git a/dataspec/type_handlers/range_handler.py b/dataspec/supplier/core/range_suppliers.py
similarity index 66%
rename from dataspec/type_handlers/range_handler.py
rename to dataspec/supplier/core/range_suppliers.py
index e05e390..913ac30 100644
--- a/dataspec/type_handlers/range_handler.py
+++ b/dataspec/supplier/core/range_suppliers.py
@@ -13,48 +13,51 @@
 import decimal
 import json
 import math
-from dataspec import registry, suppliers, schemas, SpecException
-from dataspec.utils import load_config
+
+import dataspec
 
 RANGE_KEY = 'range'
 RAND_RANGE_KEY = 'rand_range'
 
 
-@registry.schemas(RANGE_KEY)
+@dataspec.registry.schemas(RANGE_KEY)
 def get_range_schema():
-    return schemas.load(RANGE_KEY)
+    """ schema for range type """
+    return dataspec.schemas.load(RANGE_KEY)
 
 
-@registry.schemas(RAND_RANGE_KEY)
+@dataspec.registry.schemas(RAND_RANGE_KEY)
 def get_rand_range_schema():
+    """ schema for rand range type """
     # This shares a schema with range
-    return schemas.load(RANGE_KEY)
+    return dataspec.schemas.load(RANGE_KEY)
 
 
-@registry.types(RANGE_KEY)
+@dataspec.registry.types(RANGE_KEY)
 def configure_range_supplier(field_spec, _):
     """ configures the range value supplier """
     if 'data' not in field_spec:
-        raise SpecException('No data element defined for: %s' % json.dumps(field_spec))
+        raise dataspec.SpecException('No data element defined for: %s' % json.dumps(field_spec))
 
     data = field_spec.get('data')
     if not isinstance(data, list) or len(data) < 2:
-        raise SpecException(
+        raise dataspec.SpecException(
             'data element for ranges type must be list with at least two elements: %s' % json.dumps(field_spec))
     # we have the nested case
     if isinstance(data[0], list):
-        suppliers_list = [_configure_supplier_for_range_data(field_spec, subdata) for subdata in data]
-        return suppliers.from_list_of_suppliers(suppliers_list, True)
-    return _configure_supplier_for_range_data(field_spec, data)
+        suppliers_list = [_configure_supplier_for_data(field_spec, subdata) for subdata in data]
+        return dataspec.suppliers.from_list_of_suppliers(suppliers_list, True)
+    return _configure_supplier_for_data(field_spec, data)
 
 
-def _configure_supplier_for_range_data(field_spec, data):
+def _configure_supplier_for_data(field_spec, data):
+    """ configures the supplier based on the range data supplied """
     start = data[0]
     # default for built in range function is exclusive end, we want to default to inclusive as this is the
     # more intuitive behavior
     end = data[1] + 1
     if not end > start:
-        raise SpecException('end element must be larger than start: %s' % json.dumps(field_spec))
+        raise dataspec.SpecException('end element must be larger than start: %s' % json.dumps(field_spec))
     if len(data) == 2:
         step = 1
     else:
@@ -63,27 +66,27 @@ def _configure_supplier_for_range_data(field_spec, data):
         config = field_spec.get('config', {})
         precision = config.get('precision', None)
         if precision and not str(precision).isnumeric():
-            raise SpecException(f'precision must be valid integer {json.dumps(field_spec)}')
+            raise dataspec.SpecException(f'precision must be valid integer {json.dumps(field_spec)}')
         range_values = list(float_range(float(start), float(end), float(step), precision))
     else:
         range_values = list(range(start, end, step))
-    return suppliers.values(range_values)
+    return dataspec.suppliers.values(range_values)
 
 
-@registry.types(RAND_RANGE_KEY)
+@dataspec.registry.types(RAND_RANGE_KEY)
 def configure_rand_range_supplier(field_spec, loader):
     """ configures the random range value supplier """
     if 'data' not in field_spec:
-        raise SpecException('No data element defined for: %s' % json.dumps(field_spec))
+        raise dataspec.SpecException('No data element defined for: %s' % json.dumps(field_spec))
     data = field_spec.get('data')
-    config = load_config(field_spec, loader)
+    config = dataspec.utils.load_config(field_spec, loader)
     if not isinstance(data, list) or len(data) == 0:
-        raise SpecException(
+        raise dataspec.SpecException(
             'rand_range specs require data as array with at least one element: %s' % json.dumps(field_spec))
     start = 0
     if len(data) == 1:
         end = data[0]
-    if len(data) == 2:
+    if len(data) >= 2:
         start = data[0]
         end = data[1]
     precision = None
@@ -91,7 +94,7 @@ def configure_rand_range_supplier(field_spec, loader):
         precision = data[2]
     # config overrides third data element if specified
     precision = config.get('precision', precision)
-    return suppliers.random_range(start, end, precision)
+    return dataspec.suppliers.random_range(start, end, precision)
 
 
 def _any_is_float(data):
@@ -104,7 +107,8 @@ def _any_is_float(data):
 
 def float_range(start, stop, step, precision=None):
     """
-    Fancy foot work to support floating point ranges due to rounding errors with the way floating point numbers are stored
+    Fancy foot work to support floating point ranges due to rounding errors with the way floating point numbers are
+    stored
     """
     # attempt to defeat some rounding errors prevalent in python
     if precision:
diff --git a/dataspec/type_handlers/select_list_subset.py b/dataspec/supplier/core/select_list_subset.py
similarity index 58%
rename from dataspec/type_handlers/select_list_subset.py
rename to dataspec/supplier/core/select_list_subset.py
index 7ccf8de..cd4d763 100644
--- a/dataspec/type_handlers/select_list_subset.py
+++ b/dataspec/supplier/core/select_list_subset.py
@@ -24,25 +24,23 @@
 """
 import json
 
-from dataspec import registry, SpecException
-from dataspec.suppliers import list_stat_sampler
-from dataspec.utils import load_config
+import dataspec
 
 
-@registry.types('select_list_subset')
+@dataspec.registry.types('select_list_subset')
 def configure_supplier(field_spec, loader):
     """ configures supplier for select_list_subset type """
-    config = load_config(field_spec, loader)
-    if config is None or 'mean' not in config:
-        raise SpecException('Config with mean defined must be provided: %s' % json.dumps(field_spec))
+    config = dataspec.utils.load_config(field_spec, loader)
+    if config is None or ('mean' not in config and 'count' not in config):
+        raise dataspec.SpecException('Config with mean or count defined must be provided: %s' % json.dumps(field_spec))
     if 'ref' in field_spec and 'data' in field_spec:
-        raise SpecException('Only one of "data" or "ref" can be provided for: %s' % json.dumps(field_spec))
+        raise dataspec.SpecException('Only one of "data" or "ref" can be provided for: %s' % json.dumps(field_spec))
 
     if 'ref' in field_spec:
         ref_name = field_spec.get('ref')
         field_spec = loader.get_ref_spec(ref_name)
         if field_spec is None:
-            raise SpecException('No ref with name %s found: %s' % (ref_name, json.dumps(field_spec)))
+            raise dataspec.SpecException('No ref with name %s found: %s' % (ref_name, json.dumps(field_spec)))
 
         if 'data' in field_spec:
             data = field_spec.get('data')
@@ -51,5 +49,6 @@ def configure_supplier(field_spec, loader):
 
     if 'data' in field_spec:
         data = field_spec.get('data')
-
-    return list_stat_sampler(data, config)
+    if dataspec.utils.any_key_exists(config, ['mean', 'stddev']):
+        return dataspec.suppliers.list_stat_sampler(data, config)
+    return dataspec.suppliers.list_count_sampler(data, config)
diff --git a/dataspec/type_handlers/unicode_range.py b/dataspec/supplier/core/unicode_range.py
similarity index 63%
rename from dataspec/type_handlers/unicode_range.py
rename to dataspec/supplier/core/unicode_range.py
index 47f5a70..d01eae5 100644
--- a/dataspec/type_handlers/unicode_range.py
+++ b/dataspec/supplier/core/unicode_range.py
@@ -2,15 +2,13 @@
 Module for handling unicode ranges
 """
 import json
-from dataspec import registry, ValueSupplierInterface, SpecException
-from dataspec.utils import any_key_exists
-from dataspec.suppliers import list_stat_sampler, list_count_sampler, from_list_of_suppliers
-import dataspec.schemas as schemas
+
+import dataspec
 
 UNICODE_RANGE_KEY = 'unicode_range'
 
 
-class UnicodeRangeSupplier(ValueSupplierInterface):
+class UnicodeRangeSupplier(dataspec.ValueSupplierInterface):
     """ Value Supplier for unicode_range type """
 
     def __init__(self, wrapped):
@@ -22,24 +20,24 @@ def next(self, iteration):
         return ''.join(as_str)
 
 
-@registry.schemas(UNICODE_RANGE_KEY)
+@dataspec.registry.schemas(UNICODE_RANGE_KEY)
 def get_unicode_range_schema():
-    return schemas.load(UNICODE_RANGE_KEY)
+    return dataspec.schemas.load(UNICODE_RANGE_KEY)
 
 
-@registry.types(UNICODE_RANGE_KEY)
+@dataspec.registry.types(UNICODE_RANGE_KEY)
 def configure_supplier(spec, _):
     """ configure the supplier for unicode_range types """
     if 'data' not in spec:
-        raise SpecException('data is Required Element for unicode_range specs: ' + json.dumps(spec))
+        raise dataspec.SpecException('data is Required Element for unicode_range specs: ' + json.dumps(spec))
     data = spec['data']
     if not isinstance(data, list):
-        raise SpecException(
+        raise dataspec.SpecException(
             f'data should be a list or list of lists with two elements for {UNICODE_RANGE_KEY} specs: ' + json.dumps(spec))
     config = spec.get('config', {})
     if isinstance(data[0], list):
         suppliers = [_single_range(sublist, config) for sublist in data]
-        return from_list_of_suppliers(suppliers, True)
+        return dataspec.suppliers.from_list_of_suppliers(suppliers, True)
     return _single_range(data, config)
 
 
@@ -47,12 +45,12 @@ def _single_range(data, config):
     # supplies range of data as floats
     range_data = list(range(_decode_num(data[0]), _decode_num(data[1]) + 1))
     # casts the floats to ints
-    if any_key_exists(config, ['mean', 'stddev']):
+    if dataspec.utils.any_key_exists(config, ['mean', 'stddev']):
         if 'as_list' not in config:
             config['as_list'] = 'true'
-        wrapped = list_stat_sampler(range_data, config)
+        wrapped = dataspec.suppliers.list_stat_sampler(range_data, config)
     else:
-        wrapped = list_count_sampler(range_data, config)
+        wrapped = dataspec.suppliers.list_count_sampler(range_data, config)
     return UnicodeRangeSupplier(wrapped)
 
 
diff --git a/dataspec/type_handlers/uuid_handler.py b/dataspec/supplier/core/uuid_handler.py
similarity index 57%
rename from dataspec/type_handlers/uuid_handler.py
rename to dataspec/supplier/core/uuid_handler.py
index 4255b4e..d43c77f 100644
--- a/dataspec/type_handlers/uuid_handler.py
+++ b/dataspec/supplier/core/uuid_handler.py
@@ -2,24 +2,24 @@
 Module for handling uuid types
 """
 import uuid
-from dataspec import registry, ValueSupplierInterface
-import dataspec.schemas as schemas
+
+import dataspec
 
 UUID_KEY = 'uuid'
 
 
-class UuidSupplier(ValueSupplierInterface):
+class UuidSupplier(dataspec.ValueSupplierInterface):
     """ Value Supplier for uuid type """
     def next(self, _):
         return str(uuid.uuid4())
 
 
-@registry.schemas(UUID_KEY)
+@dataspec.registry.schemas(UUID_KEY)
 def get_uuid_schema():
-    return schemas.load(UUID_KEY)
+    return dataspec.schemas.load(UUID_KEY)
 
 
-@registry.types(UUID_KEY)
+@dataspec.registry.types(UUID_KEY)
 def configure_supplier(_, __):
     """ configure the supplier for uuid types """
     return UuidSupplier()
diff --git a/dataspec/supplier/core/values.py b/dataspec/supplier/core/values.py
new file mode 100644
index 0000000..33f4b97
--- /dev/null
+++ b/dataspec/supplier/core/values.py
@@ -0,0 +1,13 @@
+"""
+Module for values type functionality
+"""
+
+import dataspec
+
+VALUES_KEY = 'values'
+
+
+@dataspec.registry.schemas(VALUES_KEY)
+def get_schema():
+    """ returns the values schema """
+    return dataspec.schemas.load(VALUES_KEY)
diff --git a/dataspec/supplier/core/weighted_refs.py b/dataspec/supplier/core/weighted_refs.py
new file mode 100644
index 0000000..ee46427
--- /dev/null
+++ b/dataspec/supplier/core/weighted_refs.py
@@ -0,0 +1,47 @@
+"""
+This module handles the weightedref type.
+
+A weighted ref spec is used to select the values from a set of refs in a weighted fashion.
+
+The weightedref Field Spec structure is:
+{
+  "<field name>": {
+    "type": "weightedref",
+    "data": { "valid_ref_1": 0.N, "valid_ref_2": 0.N, ... }
+  }
+}
+"""
+import dataspec
+
+
+class WeightedRefsSupplier(dataspec.ValueSupplierInterface):
+    """
+    Value supplier that uses a weighted scheme to supply values from different reference value suppliers
+    """
+
+    def __init__(self, key_supplier, values_map):
+        self.key_supplier = key_supplier
+        self.values_map = values_map
+
+    def next(self, iteration):
+        key = self.key_supplier.next(iteration)
+        supplier = self.values_map.get(key)
+        if supplier is None:
+            raise dataspec.SpecException("Unknown Key '%s' for Weighted Reference" % key)
+        return supplier.next(iteration)
+
+
+@dataspec.registry.types('weightedref')
+def configure_supplier(parent_field_spec, loader):
+    """ configures supplier for weighted ref specs """
+    config = dataspec.utils.load_config(parent_field_spec, loader)
+    data = parent_field_spec['data']
+    key_supplier = dataspec.suppliers.values(data)
+    values_map = {}
+    for key in data.keys():
+        supplier = loader.get(key)
+        values_map[key] = supplier
+    supplier = WeightedRefsSupplier(key_supplier, values_map)
+    if 'count' in config:
+        return dataspec.suppliers.array_supplier(supplier, config)
+    return supplier
diff --git a/dataspec/supplier/list_count_sampler.py b/dataspec/supplier/list_count_sampler.py
index 2b99ee2..b77111b 100644
--- a/dataspec/supplier/list_count_sampler.py
+++ b/dataspec/supplier/list_count_sampler.py
@@ -1,15 +1,18 @@
 """ Module for ListCountSamplerSupplier """
+from typing import Union
 import random
 
-from dataspec.supplier.value_supplier import ValueSupplierInterface
+import dataspec
 
 
-class ListCountSamplerSupplier(ValueSupplierInterface):
+class ListCountSamplerSupplier(dataspec.ValueSupplierInterface):
     """
     Supplies values by sampling from a list with hard min max and count
     """
 
-    def __init__(self, data: list, count_supplier: ValueSupplierInterface, join_with: bool = None):
+    def __init__(self, data: Union[str, list],
+                 count_supplier: dataspec.ValueSupplierInterface,
+                 join_with: str = ''):
         self.values = data
         self.count_supplier = count_supplier
         self.join_with = join_with
@@ -17,6 +20,6 @@ def __init__(self, data: list, count_supplier: ValueSupplierInterface, join_with
     def next(self, iteration):
         count = self.count_supplier.next(iteration)
         data = random.sample(self.values, count)
-        if self.join_with:
+        if self.join_with is not None:
             return self.join_with.join([str(elem) for elem in data])
         return data
diff --git a/dataspec/supplier/list_stat_sampler.py b/dataspec/supplier/list_stat_sampler.py
index d6fc53f..a4ea8e4 100644
--- a/dataspec/supplier/list_stat_sampler.py
+++ b/dataspec/supplier/list_stat_sampler.py
@@ -1,13 +1,13 @@
 """
-Module for implementation of select list subset value supplier
+Module list stats sampler class
 """
 import math
 import random
-from dataspec.utils import is_affirmative
-from dataspec.supplier.value_supplier import ValueSupplierInterface
 
+import dataspec
 
-class ListStatSamplerSupplier(ValueSupplierInterface):
+
+class ListStatSamplerSupplier(dataspec.ValueSupplierInterface):
     """
     Implementation for supplying values from a list by select a portion of them
     and optionally joining them by some delimiter
@@ -15,7 +15,7 @@ class ListStatSamplerSupplier(ValueSupplierInterface):
 
     def __init__(self, data, config):
         self.values = data
-        self.mean = float(config.get('mean'))
+        self.mean = float(config.get('mean', 1))
         self.min = int(config.get('min', 1))
         self.max = int(config.get('max', len(self.values)))
         # attempt to create a reasonable standard deviation
@@ -25,7 +25,7 @@ def __init__(self, data, config):
             lower_delta = abs(int(self.mean - self.max))
         self.stddev = float(config.get('stddev', lower_delta))
         self.join_with = config.get('join_with', ' ')
-        self.as_list = is_affirmative('as_list', config, False)
+        self.as_list = dataspec.utils.is_affirmative('as_list', config, False)
 
     def next(self, _):
         if self.stddev == 0:
diff --git a/dataspec/supplier/list_values.py b/dataspec/supplier/list_values.py
index 101f352..4e559c8 100644
--- a/dataspec/supplier/list_values.py
+++ b/dataspec/supplier/list_values.py
@@ -2,15 +2,16 @@
 Module for value list supplier implementation
 """
 import random
-from .value_supplier import ValueSupplierInterface
 
+import dataspec
 
-class ListValueSupplier(ValueSupplierInterface):
+
+class ListValueSupplier(dataspec.ValueSupplierInterface):
     """
     Value Supplier implementation for supplying values from lists
     """
 
-    def __init__(self, data: list, count: ValueSupplierInterface, do_sampling: bool=False):
+    def __init__(self, data: list, count: dataspec.ValueSupplierInterface, do_sampling: bool = False):
         self.values = data
         self.do_sampling = do_sampling
         self.count = count
diff --git a/dataspec/supplier/value_supplier.py b/dataspec/supplier/value_supplier.py
deleted file mode 100644
index 40bc1cc..0000000
--- a/dataspec/supplier/value_supplier.py
+++ /dev/null
@@ -1,15 +0,0 @@
-"""
-Module holds the Interface that Value Suppliers should implement.
-"""
-
-
-class ValueSupplierInterface:
-    """
-    Interface for Classes that supply values
-    """
-    def next(self, iteration):
-        """
-        Produces the next value for the given iteration
-        :param iteration: current iteration
-        :return: the next value
-        """
diff --git a/dataspec/supplier/weighted_refs.py b/dataspec/supplier/weighted_refs.py
deleted file mode 100644
index 561d19f..0000000
--- a/dataspec/supplier/weighted_refs.py
+++ /dev/null
@@ -1,22 +0,0 @@
-"""
-Module for weighted refs value supplier
-"""
-from dataspec.exceptions import SpecException
-from .value_supplier import ValueSupplierInterface
-
-
-class WeightedRefsSupplier(ValueSupplierInterface):
-    """
-    Value supplier that uses a weighted scheme to supply values from different reference value suppliers
-    """
-
-    def __init__(self, key_supplier, values_map):
-        self.key_supplier = key_supplier
-        self.values_map = values_map
-
-    def next(self, iteration):
-        key = self.key_supplier.next(iteration)
-        supplier = self.values_map.get(key)
-        if supplier is None:
-            raise SpecException("Unknown Key '%s' for Weighted Reference" % key)
-        return supplier.next(iteration)
diff --git a/dataspec/supplier/weighted_values.py b/dataspec/supplier/weighted_values.py
index c829980..0786a8c 100644
--- a/dataspec/supplier/weighted_values.py
+++ b/dataspec/supplier/weighted_values.py
@@ -2,20 +2,26 @@
 Module for the class that implements supplying weighted values
 """
 import random
-from .value_supplier import ValueSupplierInterface
 
+import dataspec
 
-class WeightedValueSupplier(ValueSupplierInterface):
+
+class WeightedValueSupplier(dataspec.ValueSupplierInterface):
     """
     Value supplier implementation for weighted values
     """
 
-    def __init__(self, data):
+    def __init__(self, data, count_supplier: dataspec.ValueSupplierInterface):
         # may be passed raw data or a spec
         if isinstance(data, dict) and 'data' in data:
             data = data.get('data')
         self.choices = list(data.keys())
         self.weights = data.values()
+        self.count_supplier = count_supplier
 
-    def next(self, _):
-        return random.choices(self.choices, self.weights)[0]
+    def next(self, iteration):
+        count = self.count_supplier.next(iteration)
+        vals = random.choices(self.choices, self.weights, k=count)
+        if count == 1:
+            return vals[0]
+        return vals
diff --git a/dataspec/suppliers.py b/dataspec/suppliers.py
index f18684a..a79effe 100644
--- a/dataspec/suppliers.py
+++ b/dataspec/suppliers.py
@@ -1,37 +1,58 @@
 """
 Factory like module for core supplier related functions.
 """
-from typing import Union
+from typing import Union, Dict, Any
 import json
-import random
+
+import dataspec
 from .exceptions import SpecException
+from .supplier.common import (SingleValue, MultipleValueSupplier, RotatingSupplierList, DecoratedSupplier,
+                              CastingSupplier, RandomRangeSupplier, DistributionBackedSupplier, BufferedValueSuppier)
 from .utils import load_config, is_affirmative, get_caster
+from .supplier.core.combine import CombineValuesSupplier
 from .supplier.list_values import ListValueSupplier
-from .supplier.combine import CombineValuesSupplier
 from .supplier.weighted_values import WeightedValueSupplier
-from .supplier.weighted_refs import WeightedRefsSupplier
 from .supplier.list_stat_sampler import ListStatSamplerSupplier
 from .supplier.list_count_sampler import ListCountSamplerSupplier
-from .supplier.value_supplier import ValueSupplierInterface
-from . import casters
+from .model import Distribution
+from . import types, casters, distributions, ValueSupplierInterface
+from .defaults import *
 
 
-def values(spec, loader=None):
+def values(spec, loader=None, **kwargs) -> ValueSupplierInterface:
     """
-    Based on data, return the appropriate values supplier
+    Based on data, return the appropriate values supplier. data can be a spec, constant, list, or dict.
+
+    >>> import dataspec
+    >>> spec = {"type": "values", "data": [1,2,3,5,8,13]}
+    >>> fib_supplier = dataspec.suppliers.values(spec)
+
+    or just the raw data
+
+    >>> fib_supplier = dataspec.suppliers.values([1,2,3,5,8,13])
+
+    example with weights
+
+    >>> weights =  {"1": 0.1, "2": 0.2, "3": 0.1, "4": 0.2, "5": 0.1, "6": 0.2, "7": 0.1}
+    >>> mostly_even_supplier = dataspec.suppliers.values(weights)
+
+    :param spec: to load values from, or raw data itself
+    :param loader: if needed
+    :param kwargs: extra kwargs to add to config
+    :return: the value supplier for the spec
     """
     # shortcut notations no type, or data, the spec is the data
-    if 'data' not in spec:
+    if _data_not_in_spec(spec):
         data = spec
     else:
         data = spec['data']
 
-    config = load_config(spec, loader)
-    do_sampling = is_affirmative('sample', config)
+    config = load_config(spec, loader, **kwargs)
+    do_sampling = is_affirmative('sample', config, default=types.get_default('sample_mode'))
 
     if isinstance(data, list):
         # this supplier can handle the count param itself, so just return it
-        return value_list(data, config.get('count', 1), do_sampling)
+        return _value_list(data, config, do_sampling)
     if isinstance(data, dict):
         if do_sampling:
             raise SpecException('Cannot do sampling on weighted values: ' + json.dumps(spec))
@@ -41,16 +62,33 @@ def values(spec, loader=None):
 
     # Check for count param
     if 'count' in config:
-        return _MultipleValueSupplier(supplier, count_supplier_from_data(config['count']))
+        return MultipleValueSupplier(supplier, count_supplier_from_config(config))
     return supplier
 
 
-def count_supplier_from_data(data):
-    """ generates a supplier for the count parameter based on the type of the data """
+def _data_not_in_spec(spec):
+    """ check to see if the data element is defined for this spec """
+    if isinstance(spec, dict):
+        return 'data' not in spec
+    return True
+
+
+def count_supplier_from_data(data) -> ValueSupplierInterface:
+    """
+    generates a supplier for the count parameter based on the type of the data
+
+    valid data for counts:
+     * integer i.e. 1, 7, 99
+     * list of integers: [1, 7, 99], [1], [1, 2, 1, 2, 3]
+     * weighted map, where keys are numeric strings: {"1": 0.6, "2": 0.4}
+     * dataspec.Distribution i.e. normal, gauess
+    """
     if isinstance(data, list):
-        supplier = value_list(data, 1, False)
+        supplier = _value_list(data, None, False)
     elif isinstance(data, dict):
         supplier = weighted_values(data)
+    elif isinstance(data, Distribution):
+        supplier = cast_supplier(distribution_supplier(data), cast_to='int')
     else:
         try:
             supplier = single_value(int(data))
@@ -60,45 +98,76 @@ def count_supplier_from_data(data):
     return supplier
 
 
-class _SingleValue(ValueSupplierInterface):
-    """
-    Encapsulates supplier that only returns a static value
+def count_supplier_from_config(config: Dict) -> ValueSupplierInterface:
     """
+    creates a count supplier from the config, if the count param is defined, otherwise uses default of 1
 
-    def __init__(self, data):
-        self.data = data
+    optionally can specify count or count_dist.
 
-    def next(self, _):
-        return self.data
+    valid data for counts:
+     * integer i.e. 1, 7, 99
+     * list of integers: [1, 7, 99], [1], [1, 2, 1, 2, 3]
+     * weighted map, where keys are numeric strings: {"1": 0.6, "2": 0.4}
 
+    count_dist will be interpreted as a distribution i.e:
 
-def single_value(data):
-    """ Creates value supplier for the single value """
-    return _SingleValue(data)
+    >>> import dataspec
+    >>> config = {"count_dist": "uniform(start=10, end=100)"}
+    >>> count_supplier = dataspec.suppliers.count_supplier_from_config(config)
+
+    :param config: to use
+    :return: a count supplier
+    """
+    data = 1  # type: Any
+    if config and 'count' in config:
+        data = config['count']
+    if config and 'count_dist' in config:
+        data = distributions.from_string(config['count_dist'])
+    return count_supplier_from_data(data)
 
 
-def array_supplier(wrapped: ValueSupplierInterface, count_config):
-    """ returns a supplier that always returns an array of elements  based on the count config supplied """
-    return _MultipleValueSupplier(wrapped, count_supplier_from_data(count_config))
+def single_value(data):
+    """
+    Creates value supplier for the single value
 
+    Examples:
 
-class _MultipleValueSupplier(ValueSupplierInterface):
+    >>> import dataspec
+    >>> single_int_supplier = dataspec.suppliers.single_value(42)
+    >>> single_str_supplier = dataspec.suppliers.single_value("42")
+    >>> single_float_supplier = dataspec.suppliers.single_value(42.42)
     """
-    Supplier that generates list of values based on count parameter
+    return SingleValue(data)
+
+
+def array_supplier(wrapped: ValueSupplierInterface,
+                   count_config: dict) -> ValueSupplierInterface:
     """
+    Wraps an existing supplier and always returns an array/list of elements, uses count config to determine
+    number of items in the list
 
-    def __init__(self, wrapped: ValueSupplierInterface, count_supplier: ValueSupplierInterface):
-        self.wrapped = wrapped
-        self.count_supplier = count_supplier
+    Example:
 
-    def next(self, iteration):
-        count = self.count_supplier.next(iteration)
-        return [self.wrapped.next(iteration + i) for i in range(count)]
+    >>> import dataspec
+    >>> config = {"count_dist": "normal(mean=2, stddev=1)"}
+    >>> pet_supplier = dataspec.suppliers.values(["dog", "cat", "hamster", "pig", "rabbit", "horse"])
+    >>> returns_mostly_two = dataspec.suppliers.array_supplier(pet_supplier, config)
+    >>> pet_array = returns_mostly_two.next(0)
+    """
+    return MultipleValueSupplier(wrapped, count_supplier_from_config(count_config))
 
 
-def from_list_of_suppliers(supplier_list, modulate_iteration):
+def from_list_of_suppliers(supplier_list, modulate_iteration: bool = True) -> ValueSupplierInterface:
     """
     Returns a supplier that rotates through the provided suppliers incrementally
+
+    Example:
+
+    >>> import dataspec
+    >>> nice_pet_supplier = dataspec.suppliers.values(["dog", "cat", "hamster", "pig", "rabbit", "horse"])
+    >>> mean_pet_supplier = dataspec.suppliers.values(["alligator", "cobra", "mongoose", "killer bee"])
+    >>> pet_supplier = dataspec.suppliers.from_list_of_suppliers([nice_pet_supplier, mean_pet_supplier])
+
     :param supplier_list: to rotate through
     :param modulate_iteration: if the iteration number should be moded by the index of the supplier
     :return: a supplier for these suppliers
@@ -106,50 +175,54 @@ def from_list_of_suppliers(supplier_list, modulate_iteration):
     return RotatingSupplierList(supplier_list, modulate_iteration)
 
 
-class RotatingSupplierList(ValueSupplierInterface):
-    """
-    Class that rotates through a list of suppliers incrementally to provide the next value
-    """
-
-    def __init__(self, suppliers, modulate_iteration):
-        """
-        :param suppliers: list of suppliers to rotate through
-        :param modulate_iteration: if the iteration should be split evenly across all suppliers 
-        """
-        self.suppliers = suppliers
-        self.modulate_iteration = modulate_iteration
-
-    def next(self, iteration):
-        idx = iteration % len(self.suppliers)
-        if self.modulate_iteration:
-            modulated_iteration = int(iteration / len(self.suppliers))
-            return self.suppliers[idx].next(modulated_iteration)
-        return self.suppliers[idx].next(iteration)
-
-
-def value_list(data, count: Union[int, list, dict], do_sampling=False):
+def _value_list(data: list,
+                config: dict = None,
+                do_sampling=False) -> ValueSupplierInterface:
     """
     creates a value list supplier
+
     :param data: for the supplier
     :param do_sampling: if the data should be sampled instead of iterated through
-    :param count: number of values to return on each iteration, or list, or weighted mapping
+    :param config: config with optional count param
     :return: the supplier
     """
-    return ListValueSupplier(data, count_supplier_from_data(count), do_sampling)
+    if config is None:
+        config = {}
+    return ListValueSupplier(data, count_supplier_from_config(config), do_sampling)
 
 
-def weighted_values(data):
+def weighted_values(data: dict, config=None) -> ValueSupplierInterface:
     """
     creates a weighted value supplier
+
+    Example:
+
+    >>> import dataspec
+    >>> weights = {"dog": 0.5, "cat": 0.2, "bunny": 0.1, "hamster": 0.1, "pig": 0.05, "snake": 0.04, "rat": 0.01}
+    >>> weighted_pet_supplier = dataspec.suppliers.weighted_values(weights)
+    >>> most_likely_a_dog = weighted_pet_supplier.next(0)
+
     :param data: for the supplier
+    :param config: optional config
     :return: the supplier
     """
-    return WeightedValueSupplier(data)
+    return WeightedValueSupplier(data, count_supplier_from_config(config))
 
 
 def combine(suppliers, config=None):
     """
-    combine supplier
+    Creates a value supplier that will combine the outputs of the provided suppliers in order. The default is to
+    join the values with an empty string. Provide the join_with config param to specify a different string to
+    join the values with. Set as_list to true, if the values should be returned as a list and not joined
+
+    Example:
+
+    >>> import dataspec
+    >>> pet_supplier = dataspec.suppliers.values(["dog", "cat", "hamster", "pig", "rabbit", "horse"], sample=True)
+    >>> job_supplier = dataspec.suppliers.values(["breeder", "trainer", "fighter", "wrestler"], sample=True)
+    >>> interesting_jobs = dataspec.suppliers.combine([pet_supplier, job_supplier], {'join_with': ' '})
+    >>> next_career = interesting_jobs.next(0)
+
     :param suppliers: to combine value for
     :param config: for the combiner
     :return: the supplier
@@ -157,19 +230,47 @@ def combine(suppliers, config=None):
     return CombineValuesSupplier(suppliers, config)
 
 
-def weighted_ref(key_supplier, values_map):
+def random_range(start: Union[int, float],
+                 end: Union[int, float],
+                 precision: Union[int, float] = None,
+                 count: Union[int, float, list, dict, dataspec.Distribution] = 1) -> ValueSupplierInterface:
     """
-    weighted refs supplier
-    :param key_supplier: supplies weighted keys
-    :param values_map: map of key to supplier for that key
-    :return: the supplier
+    Creates a random range supplier for the start and end parameters with the given precision (number of decimal places)
+
+    Example:
+
+    >>> range_supplier = dataspec.suppliers.random_range(5, 25, precision=3)
+    >>> # should be between 5 and 25 with 3 decimal places
+    >>> next_value = range_supplier.next(0))
+
+    :param start: of range
+    :param end: of range
+    :param precision: number of decimal points to keep
+    :param count: number of elements to return, default is one
+    :return: the value supplier for the range
     """
-    return WeightedRefsSupplier(key_supplier, values_map)
+    return RandomRangeSupplier(start, end, precision, count_supplier_from_data(count))
 
 
-def list_stat_sampler(data, config):
+def list_stat_sampler(data: Union[str, list],
+                      config: dict) -> ValueSupplierInterface:
     """
-    sample from list with stats based params
+    sample from list (or string) with stats based params
+
+    Example:
+
+    >>> import dataspec
+    >>> stats_config = {"mean": 2, "stddev": 1}
+    >>> pet_list = ["dog", "cat", "hamster", "pig", "rabbit", "horse"]
+    >>> pet_supplier = dataspec.suppliers.list_stat_sampler(pet_list, stats_config)
+    >>> new_pets = pet_supplier.next(0)
+
+    string example
+
+    >>> char_config = {"min": 2, "mean": 4, "max": 8}
+    >>> char_supplier = dataspec.suppliers.list_stat_sampler("#!@#$%^&*()_-~", char_config)
+    >>> two_to_eight_chars = char_supplier.next(0)
+
     :param data: list to select subset from
     :param config: with minimal of mean specified
     :return: the supplier
@@ -177,24 +278,48 @@ def list_stat_sampler(data, config):
     return ListStatSamplerSupplier(data, config)
 
 
-def list_count_sampler(data: list, config: dict):
+def list_count_sampler(data: list, config: dict) -> ValueSupplierInterface:
     """
-    sample from list with counts
+    Samples N elements from data list based on config.  If count is provided,
+    each iteration exactly count elements will be returned.  If only min is provided,
+    between min and the total number of elements will be provided. If only max is provided,
+    between one and max elements will be returned. Specifying both min and max will provide
+    a sample containing a number of elements in this range.
+
+    Example:
+
+    >>> import dataspec
+    >>> count_config = {"min": 2, "max": 5}
+    >>> pet_list = ["dog", "cat", "hamster", "pig", "rabbit", "horse"]
+    >>> pet_supplier = dataspec.suppliers.list_count_sampler(pet_list, count_config)
+    >>> new_pets = pet_supplier.next(0)
+
     :param data: list to select subset from
     :param config: with minimal of count or min and max supplied
     :return: the supplier
     """
-    count = config.get('count')
-    if count is not None:
-        count_supplier = count_supplier_from_data(count)
+    if 'count' in config or 'count_dist' in config:
+        count_supplier = count_supplier_from_config(config)
     else:
         min_cnt = int(config.get('min', 1))
         max_cnt = int(config.get('max', len(data))) + 1
         count_range = list(range(min_cnt, max_cnt))
-        count_supplier = value_list(count_range, 1, True)
+        count_supplier = _value_list(count_range, None, True)
     return ListCountSamplerSupplier(data, count_supplier, config.get('join_with', None))
 
 
+def distribution_supplier(distribution: Distribution) -> ValueSupplierInterface:
+    """
+    creates a ValueSupplier that uses the given distribution to generate values
+
+    :param distribution: to use
+    :return: the value supplier
+    """
+    wrapped = DistributionBackedSupplier(distribution)
+    # buffer the values
+    return buffered(wrapped, {})
+
+
 def is_decorated(field_spec):
     """
     is this spec a decorated one
@@ -207,24 +332,6 @@ def is_decorated(field_spec):
     return 'prefix' in config or 'suffix' in config or 'quote' in config
 
 
-class DecoratedSupplier(ValueSupplierInterface):
-    """
-    Class used to add additional data to other suppliers output, such as a
-    prefix or suffix or to surround the output with quotes
-    """
-
-    def __init__(self, config, supplier):
-        self.prefix = config.get('prefix', '')
-        self.suffix = config.get('suffix', '')
-        self.quote = config.get('quote', '')
-        self.wrapped = supplier
-
-    def next(self, iteration):
-        value = self.wrapped.next(iteration)
-        # todo: cache mapping for efficiency?
-        return f'{self.quote}{self.prefix}{value}{self.suffix}{self.quote}'
-
-
 def decorated(field_spec, supplier):
     """
     Creates a decorated supplier around the provided on
@@ -235,19 +342,6 @@ def decorated(field_spec, supplier):
     return DecoratedSupplier(field_spec.get('config'), supplier)
 
 
-class CastingSupplier(ValueSupplierInterface):
-    """
-    Class that just casts the results of other suppliers
-    """
-
-    def __init__(self, wrapped, caster):
-        self.wrapped = wrapped
-        self.caster = caster
-
-    def next(self, iteration):
-        return self.caster.cast(self.wrapped.next(iteration))
-
-
 def is_cast(field_spec):
     """
     is this spec requires casting
@@ -260,41 +354,48 @@ def is_cast(field_spec):
     return 'cast' in config
 
 
-def cast_supplier(supplier, field_spec, cast_to=None):
+def cast_supplier(supplier: ValueSupplierInterface,
+                  field_spec: dict = None,
+                  cast_to: str = None) -> ValueSupplierInterface:
     """
     Provides a cast_supplier either from config or from explicit cast_to
+
     :param supplier: to cast results of
     :param field_spec: to look up cast config from
     :param cast_to: explicit cast type to use
     :return: the casting supplier
     """
+
     if cast_to:
         caster = casters.get(cast_to)
     else:
-        caster = get_caster(field_spec.get('config'))
+        if field_spec is None:
+            config = {}
+        else:
+            config = field_spec.get('config', {})
+        caster = get_caster(config)
     return CastingSupplier(supplier, caster)
 
 
-class RandomRangeSupplier(ValueSupplierInterface):
-    """
-    Class that supplies random ranges between specified bounds
+def is_buffered(field_spec):
     """
+    Should the values for this spec be buffered
 
-    def __init__(self, start, end, precision, count=1):
-        self.start = float(start)
-        self.end = float(end)
-        self.precision = precision
-        self.format_str = '{: .' + str(precision) + 'f}'
-        self.count = count
+    :param field_spec: to check
+    :return: true or false
+    """
+    config = field_spec.get('config', {})
+    return 'buffer_size' in config or is_affirmative('buffer', config)
 
-    def next(self, iteration):
-        next_nums = [random.uniform(self.start, self.end) for _ in range(self.count)]
-        if self.precision is not None:
-            next_nums = [self.format_str.format(next_num) for next_num in next_nums]
-        if self.count == 1:
-            return next_nums[0]
-        return next_nums
 
+def buffered(wrapped: ValueSupplierInterface, field_spec):
+    """
+    Creates a Value Supplier that buffers the results of the wrapped supplier
 
-def random_range(start, end, precision=None, count=1):
-    return RandomRangeSupplier(start, end, precision, count)
+    :param wrapped: the Value Supplir to buffer values for
+    :param field_spec: to check
+    :return:
+    """
+    config = field_spec.get('config', {})
+    buffer_size = int(config.get('buffer_size', 10))
+    return BufferedValueSuppier(wrapped, buffer_size)
diff --git a/dataspec/template_engines.py b/dataspec/template_engines.py
index e5432d4..4f0b6ef 100644
--- a/dataspec/template_engines.py
+++ b/dataspec/template_engines.py
@@ -2,24 +2,31 @@
 Handles loading and creating the templating engine
 """
 import os
-from jinja2 import Environment, FileSystemLoader, select_autoescape
+from pathlib import Path
+from typing import Union
+from jinja2 import Environment, FileSystemLoader, BaseLoader, select_autoescape
 
 
-def load(template_file):
+def for_file(template_file: Union[str, Path]):
     """
     Loads the templating engine for the template file specified
-    :param template_file: to fill in
+    :param template_file: to fill in, string or Path
     :return: the templating engine
     """
     return Jinja2Engine(template_file)
 
 
+def string(template):
+    """ Returns a template engine for processing templates as strings """
+    return Jinja2StringEngine(template)
+
+
 class Jinja2Engine:
     """
     A simple class that creates a facade around a Jinja2 templating environment
     """
 
-    def __init__(self, template_file):
+    def __init__(self, template_file: Union[str, Path]):
         template_dir = os.path.dirname(template_file)
         self.template_name = os.path.basename(template_file)
         self.env = Environment(
@@ -35,3 +42,25 @@ def process(self, record):
         """
         template = self.env.get_template(self.template_name)
         return template.render(record)
+
+
+class Jinja2StringEngine:
+    """
+    A Jinja2 Templating Engine for String Templates
+    """
+
+    def __init__(self, template):
+        self.template = template
+        self.env = Environment(
+            loader=BaseLoader(),
+            autoescape=select_autoescape(['html', 'xml'])
+        )
+
+    def process(self, record):
+        """
+        Render the template using the fields in the provided record
+        :param record: dictionary of field in template to value to populate the field with
+        :return: The rendered template
+        """
+        template = self.env.from_string(self.template)
+        return template.render(record)
diff --git a/dataspec/type_handlers/__init__.py b/dataspec/type_handlers/__init__.py
deleted file mode 100644
index f36a589..0000000
--- a/dataspec/type_handlers/__init__.py
+++ /dev/null
@@ -1,15 +0,0 @@
-""" init for type handlers submodule """
-__all__ = ['char_class_handler',
-           'combine',
-           'configref',
-           'csv_handler',
-           'date_handler',
-           'geo_handler',
-           'ip_handler',
-           'nested_handler',
-           'range_handler',
-           'select_list_subset',
-           'unicode_range',
-           'uuid_handler',
-           'values',
-           'weighted_ref',]
diff --git a/dataspec/type_handlers/date_handler.py b/dataspec/type_handlers/date_handler.py
deleted file mode 100644
index 373e1b6..0000000
--- a/dataspec/type_handlers/date_handler.py
+++ /dev/null
@@ -1,151 +0,0 @@
-"""
-Module for handling date related types
-
-The date Field Spec structure is:
-
-{
-  "<field name>": {
-    "type": "date",
-    OR,
-    "type": "date.iso",
-    OR,
-    "type": "date.iso.us",
-    "config": {"...": "..."}
-  }
-}
-
-"""
-import random
-import datetime
-from dataspec import registry, ValueSupplierInterface
-from dataspec.utils import load_config
-import dataspec.schemas as schemas
-
-DATE_KEY = 'date'
-DATE_ISO_KEY = 'date.iso'
-DATE_ISO_US_KEY = 'date.iso.us'
-
-DEFAULT_FORMAT = "%d-%m-%Y"
-ISO_FORMAT_NO_MICRO = '%Y-%m-%dT%H:%M:%S'
-ISO_FORMAT_WITH_MICRO = '%Y-%m-%dT%H:%M:%S.%f'
-
-
-@registry.schemas(DATE_KEY)
-def get_date_schema():
-    """ returns the schema for date types """
-    return schemas.load(DATE_KEY)
-
-
-@registry.schemas(DATE_ISO_KEY)
-def get_date_iso_schema():
-    """ returns the schema for date.iso types """
-    # NOTE: These all share a schema
-    return schemas.load(DATE_KEY)
-
-
-@registry.schemas(DATE_ISO_US_KEY)
-def get_date_iso_us_schema():
-    """ returns the schema for date.iso.us types """
-    # NOTE: These all share a schema
-    return schemas.load(DATE_KEY)
-
-
-class DateSupplier(ValueSupplierInterface):
-    """
-    Value Supplier implementation for dates
-    """
-
-    def __init__(self, delta_days, offset, anchor, date_format_string):
-        self.date_format = date_format_string
-
-        offset_date = datetime.timedelta(days=offset)
-        if anchor:
-            base = datetime.datetime.strptime(anchor, date_format_string) - offset_date
-        else:
-            base = datetime.datetime.now() - offset_date
-
-        self.start_date, end_date = _calculate_start_end_dates(base, delta_days)
-        self.delta_seconds = _calculate_delta_seconds(self.start_date, end_date)
-
-    def next(self, _):
-        random_second = random.randrange(self.delta_seconds)
-        next_date = self.start_date + datetime.timedelta(seconds=random_second)
-        if self.date_format:
-            return next_date.strftime(self.date_format)
-        return next_date.replace(microsecond=0).isoformat()
-
-
-@registry.types(DATE_KEY)
-def configure_supplier(field_spec, loader):
-    """ configures the date value supplier """
-    config = load_config(field_spec, loader)
-    delta_days = config.get('delta_days', 15)
-    offset = int(config.get('offset', 0))
-    anchor = config.get('anchor')
-    date_format = config.get('format', DEFAULT_FORMAT)
-    return DateSupplier(delta_days, offset, anchor, date_format)
-
-
-@registry.types(DATE_ISO_KEY)
-def configure_supplier_iso(field_spec, loader):
-    """ configures the date.iso value supplier """
-    return _configure_supplier_iso_date(field_spec, loader, ISO_FORMAT_NO_MICRO)
-
-
-@registry.types(DATE_ISO_US_KEY)
-def configure_supplier_iso_microseconds(field_spec, loader):
-    """ configures the date.iso.us value supplier """
-    return _configure_supplier_iso_date(field_spec, loader, ISO_FORMAT_WITH_MICRO)
-
-
-def _configure_supplier_iso_date(field_spec, loader, iso_date_format):
-    """ configures an iso based date supplier using the provided date format """
-    config = load_config(field_spec, loader)
-    delta_days = config.get('delta_days', 15)
-    offset = int(config.get('offset', 0))
-    anchor = config.get('anchor')
-    date_format = config.get('format', DEFAULT_FORMAT)
-    # make sure the anchor matches the ISO format we are using
-    if anchor:
-        anchor_date = datetime.datetime.strptime(anchor, date_format)
-        anchor = anchor_date.strftime(iso_date_format)
-    return DateSupplier(delta_days, offset, anchor, iso_date_format)
-
-
-def _calculate_start_end_dates(base, delta_days):
-    """
-    Calculates the datetime objects for the start of delta_days ago and the start of delta_days+1 ahead
-    To guarantee that the desired date range strings are created
-    :param base:
-    :param delta_days:
-    :return:
-    """
-    lower, upper = _calculate_upper_lower(delta_days)
-    start_date = (base + datetime.timedelta(days=lower)).replace(hour=0, minute=0, second=0, microsecond=0)
-    end_date = (base + datetime.timedelta(days=upper)).replace(hour=0, minute=0, second=0, microsecond=0)
-    return start_date, end_date
-
-
-def _calculate_upper_lower(delta_days):
-    """
-    Calculates the lower and upper bounds based on the many formats accepted by delta days
-    :param delta_days:
-    :return: the lower and upper number of days for delta
-    """
-    if isinstance(delta_days, list):
-        lower = int(delta_days[0])
-        upper = int(delta_days[1])
-    else:
-        lower = -int(delta_days)
-        upper = int(delta_days)
-    lower = -(abs(lower))
-    # this makes end date inclusive
-    upper = abs(upper) + 1
-    return lower, upper
-
-
-def _calculate_delta_seconds(start, end):
-    """ calculates the delta in seconds between the two dates """
-    delta = end - start
-    int_delta = (delta.days * 24 * 60 * 60) + delta.seconds
-    return int_delta
diff --git a/dataspec/type_handlers/values.py b/dataspec/type_handlers/values.py
deleted file mode 100644
index 435f0b0..0000000
--- a/dataspec/type_handlers/values.py
+++ /dev/null
@@ -1,14 +0,0 @@
-"""
-Module for values type functionality
-"""
-
-from dataspec import registry
-import dataspec.schemas as schemas
-
-VALUES_KEY = 'values'
-
-
-@registry.schemas(VALUES_KEY)
-def get_schema():
-    """ returns the values schema """
-    return schemas.load(VALUES_KEY)
diff --git a/dataspec/type_handlers/weighted_ref.py b/dataspec/type_handlers/weighted_ref.py
deleted file mode 100644
index 9ae11f0..0000000
--- a/dataspec/type_handlers/weighted_ref.py
+++ /dev/null
@@ -1,30 +0,0 @@
-"""
-This module handles the weightedref type.
-
-A weighted ref spec is used to select the values from a set of refs in a weighted fashion.
-
-The weightedref Field Spec structure is:
-{
-  "<field name>": {
-    "type": "weightedref",
-    "data": { "valid_ref_1": 0.N, "valid_ref_2": 0.N, ... }
-  }
-}
-"""
-from dataspec import registry, suppliers, utils
-
-
-@registry.types('weightedref')
-def configure_supplier(parent_field_spec, loader):
-    """ configures supplier for weighted ref specs """
-    config = utils.load_config(parent_field_spec, loader)
-    data = parent_field_spec['data']
-    key_supplier = suppliers.values(data)
-    values_map = {}
-    for key in data.keys():
-        supplier = loader.get(key)
-        values_map[key] = supplier
-    supplier = suppliers.weighted_ref(key_supplier, values_map)
-    if 'count' in config:
-        return suppliers.array_supplier(supplier, config.get('count', 1))
-    return supplier
diff --git a/dataspec/types.py b/dataspec/types.py
index d6ba610..86729a5 100644
--- a/dataspec/types.py
+++ b/dataspec/types.py
@@ -1,21 +1,31 @@
 """
 Module for the dataspec registration system
 """
+from typing import Any
 import logging
-import catalogue
+import catalogue  # type: ignore
 
 log = logging.getLogger(__name__)
 
 
 class registry:
     """
-    Catalogue registry for types, preprocessors, and logging configuration
+    Catalogue registry for types, preprocessors, logging configuration, and others
     """
+    # types for field specs
     types = catalogue.create('dataspec', 'type')
+    # schemas for field spec types
     schemas = catalogue.create('dataspec', 'schemas')
+    # functions to modify specs before running
     preprocessors = catalogue.create('dataspec', 'preprocessor')
+    # custom logging setup
     logging = catalogue.create('dataspec', 'logging')
+    # registered formats for output
     formats = catalogue.create('dataspec', 'format')
+    # different numeric distributions, normal, uniform, etc
+    distribution = catalogue.create('dataspec', 'distribution')
+    # default values
+    defaults = catalogue.create('dataspec', 'defaults')
 
 
 def lookup_type(key):
@@ -44,4 +54,21 @@ def lookup_schema(key):
 
 
 def valid_formats():
-    return list(registry.formats.get_all().keys())
\ No newline at end of file
+    """ list of registered formats """
+    return list(registry.formats.get_all().keys())
+
+
+def get_default(key):
+    """ get the default value for the specified key """
+    func = registry.defaults.get(key)
+    return func()
+
+
+def set_default(key: str, value: Any):
+    """ set the default value for the given key in the registry """
+    registry.defaults.register(name=key, func=lambda *_: value)
+
+
+def all_defaults():
+    """ creates a dictionary of the current state of the registered defaults """
+    return {k: get_default(k) for k in registry.defaults.get_all()}
diff --git a/dataspec/utils.py b/dataspec/utils.py
index 0b1c745..2403ace 100644
--- a/dataspec/utils.py
+++ b/dataspec/utils.py
@@ -4,7 +4,9 @@
 import os
 import importlib
 import logging
-import dataspec.casters as casters
+
+from . import casters
+from .model import DataSpec
 
 
 def load_custom_code(code_path):
@@ -14,7 +16,7 @@ def load_custom_code(code_path):
     :return: None
     """
     if not os.path.exists(code_path):
-        raise Exception(f'Path to {code_path} not found.')
+        raise FileNotFoundError(f'Path to {code_path} not found.')
     try:
         spec = importlib.util.spec_from_file_location("python_code", str(code_path))
         module = importlib.util.module_from_spec(spec)
@@ -23,7 +25,7 @@ def load_custom_code(code_path):
         logging.warning("Couldn't load custom Python code: %s: %s", code_path, str(exception))
 
 
-def is_affirmative(key, config, default=False):
+def is_affirmative(key: str, config: dict, default=False) -> bool:
     """
     Checks if the config value is one of true, yes, or on (case doesn't matter), default is False
     :param key: to check
@@ -35,7 +37,7 @@ def is_affirmative(key, config, default=False):
     return value in ['yes', 'true', 'on']
 
 
-def load_config(field_spec, loader):
+def load_config(field_spec, loader, **kwargs):
     """
     Loads the config and any secondary configs into one object
     :param field_spec: that should contain config
@@ -43,9 +45,10 @@ def load_config(field_spec, loader):
     :return: the full config
     """
     if not isinstance(field_spec, dict):
-        return {}
+        return kwargs
 
     config = field_spec.get('config', {})
+    config.update(kwargs)
     refkey = config.get('configref')
     if refkey:
         configref = loader.get_ref_spec(refkey)
@@ -63,9 +66,10 @@ def any_key_exists(config, keys):
     return any(key in config for key in keys)
 
 
-def update_config(spec, key, value):
-    """ add the key and value to the spec config """
-    config = spec.get('config', {})
-    config[key] = value
-    spec['config'] = config
-    return spec
+def get_raw_spec(data_spec):
+    """ The data spec may be raw or object version, this gets the raw underlying spec """
+    if isinstance(data_spec, DataSpec):
+        raw_spec = data_spec.raw_spec
+    else:
+        raw_spec = data_spec
+    return raw_spec
diff --git a/docs/FIELDSPECS.md b/docs/FIELDSPECS.md
index 9b4f10f..bb1d409 100644
--- a/docs/FIELDSPECS.md
+++ b/docs/FIELDSPECS.md
@@ -1,6 +1,9 @@
 Field Spec Definitions
 ========================
-
+ 
+ 
+ 
+ 
 
 1. [Quick Reference](#Quick_Reference)
 1. [Overview](#Overview)
@@ -8,7 +11,7 @@ Field Spec Definitions
     1. [The Full Format](#The_full_format.)
     1. [Values Shorthand](#Values_Shorthand)
     1. [Inline Key Type Shorthand](#Inline_Key_Type_Shorthand)
-    1. [Inline Key Config Shorthad](#Inline_Key_Config_Shorthad)
+    1. [Inline Key Config Shorthand](#Inline_Key_Config_Shorthad)
 1. [Spec Configuration](#Spec_Configuration)
     1. [Common Configurations](#Common_Configurations)
     1. [Count Config Parameter](#CountsField)
@@ -19,8 +22,10 @@ Field Spec Definitions
         1. [Weighted Values](#Weighted_Values)
         1. [Sample Mode](#Sample_Mode)
     1. [Combine](#Combine)
+    1. [Combine List](#CombineList)
     1. [Date](#Date)
     1. [Range](#Range)
+    1. [Random Range](#RandRange)
     1. [Uuid](#Uuid)
     1. [Character Class](#CharClass)
        1. [Built In Classes](#SupportedClasses)
@@ -37,68 +42,106 @@ Field Spec Definitions
 
 # <a name="Quick_Reference"></a>Quick Reference
 
-| type                        | description                            | config params                |
-|-----------------------------|----------------------------------------|------------------------------|
-|[values](#Values)            | constant, list, or weighted dictionary |                              |
-|[range](#Range)              | range of values                        |                              |
-|[rand_range](#RandRange)     | random value in a range                |                              |
-|[combine](#Combine)          | refs or fields                         | join_with                    |
-|[combine-list](#CombineList) | list of lists of refs to combine       | join_with                    |
-|[date](#Date)                | date strings                           | many see details below       |
-|[date.iso](#Date)            | date strings in ISO8601 format no microseconds| many see details below|
-|[date.iso.us](#Date)         | date strings in ISO8601 format w/ microseconds| many see details below|
-|[uuid](#Uuid)                | generates valid uuid                   |                              |
-|[char_class](#CharClass)     | generates strings from character classes| many see details below      |
-|[unicode_range](#UnicodeRange)| generates strings from unicode ranges | many see details below       |
-|[geo.lat](#Geo)              | generates decimal latitude             | start_lat,end_lat,precision  |
-|[geo.long](#Geo)             | generates decimal longitude            | start_long,end_long,precision|
-|[geo.pair](#Geo)             | generates long,lat pair                | join_with,start_lat,end_lat,start_long,end_long,precision|
-|[ip/ipv4](#IP_Addresses)     | generates ip v4 addresses              | base, cidr /8,/16,/24 only   |
-|[ip.precise](#IP_Addresses)  | generates ip v4 addresses              | cidr(required) i.e. 192.168.1.0/14 |
-|[weightedref](#Weighted_Ref) | produces values from refs in weighted fashion |                       |
-|[select_list_subset](#Select_List_Subset) | selects subset of fields that are combined to create the value for the field | join_with |
-|[csv](#CSV_Data)             | Uses external csv file to supply data  | many see details below       |
-|[csv_select](#CSV_Select)    | Efficient way to select multiple csv columns | many see details below |
-|[nested](#Nested)            | For nested fields                      |                              |
+type                         | description                            | config params
+-----------------------------|----------------------------------------|------------------------------
+[values](#Values)            | constant, list, or weighted dictionary |
+[range](#Range)              | range of values                        |
+[rand_range](#RandRange)     | random value in a range                |
+[combine](#Combine)          | refs or fields                         | join_with
+[combine-list](#CombineList) | list of lists of refs to combine       | join_with
+[date](#Date)                | date strings                           | many see details below
+[date.iso](#Date)            | date strings in ISO8601 format no microseconds| many see details below
+[date.iso.us](#Date)         | date strings in ISO8601 format w/ microseconds| many see details below
+[uuid](#Uuid)                | generates valid uuid                   |
+[char_class](#CharClass)     | generates strings from character classes| many see details below
+[unicode_range](#UnicodeRange)| generates strings from unicode ranges | many see details below
+[geo.lat](#Geo)              | generates decimal latitude             | start_lat,end_lat,precision
+[geo.long](#Geo)             | generates decimal longitude            | start_long,end_long,precision
+[geo.pair](#Geo)             | generates long,lat pair                | join_with,start_lat,end_lat,</br>start_long,end_long,precision
+[ip/ipv4](#IP_Addresses)     | generates ip v4 addresses              | base, cidr /8,/16,/24 only
+[ip.precise](#IP_Addresses)  | generates ip v4 addresses              | cidr(required) i.e. 192.168.1.0/14
+[weightedref](#Weighted_Ref) | produces values from refs in weighted fashion |
+[select_list_subset](#Select_List_Subset) | selects subset of fields that are</br> combined to create the value for the field | join_with
+[csv](#CSV_Data)             | Uses external csv file to supply data  | many see details below
+[csv_select](#CSV_Select)    | Efficient way to select multiple csv columns | many see details below
+[nested](#Nested)            | For nested fields                      |
+
 # <a name="Overview"></a>Overview
 
-Each field that should be generated needs a specification that describes the way the values for it should be created. We
-refer to this as a Field Spec. The simplest type of Field Spec is a values spec. The main format of a values spec is a
-list of values to use. By default, these values are rotated through incrementally. If the number of increments is larger
-than the number of values in the list, the values start over from the beginning of the list. When combining values from
-two values providers that are lists, they will be combined in incrementing order. i.e:
+Each field that should be generated needs a specification that describes the way
+the values for it should be created. We refer to this as a Field Spec. The
+simplest type of Field Spec is a values spec. The main format of a values spec
+is a list of values to use. By default, these values are rotated through
+incrementally. If the number of increments is larger than the number of values
+in the list, the values start over from the beginning of the list. When
+combining values from two values providers that are lists, they will be combined
+in incrementing order. For example, the spec below will produce the values A1,
+B2, C3 continuously.
 
 <details open>
   <summary>JSON Spec</summary>
 
 ```json
 {
-  "combine": {"type": "combine", "refs": ["ONE", "TWO"]},
+  "combine": {
+    "type": "combine",
+    "refs": ["ONE", "TWO"]
+  },
   "refs": {
-    "ONE": ["A", "B", "C"],
-    "TWO": [1, 2, 3]
+    "ONE": {
+      "type": "values",
+      "data": ["A", "B", "C"]
+    },
+    "TWO": {
+      "type": "values",
+      "data": [1, 2, 3]
+    }
   }
 }
 ```
-</details>
 
+</details>
 <details>
   <summary>YAML Spec</summary>
 
 ```yaml
----
 combine:
   type: combine
   refs: [ONE, TWO]
 refs:
-  ONE: [A, B, C]
-  TWO: [1, 2, 3]
+  ONE:
+    type: values
+    data: [A, B, C]
+  TWO:
+    type: values
+    data: [1, 2, 3]
+```
+
+</details>
+<details>
+  <summary>API Example</summary>
+
+```python
+import dataspec
+
+spec_builder = dataspec.spec_builder()
+
+refs = spec_builder.refs()
+one = refs.values('ONE', ["A", "B", "C"])
+two = refs.values('TWO', [1, 2, 3])
+
+spec_builder.combine('combine', refs=[one, two])
+
+spec = spec_builder.build()
 ```
+
 </details>
-Will produce the values A1, B2, C3 continuously.
 
-```shell script
-dataspec -s ~/scratch/sample.json -i 7
+<details open>
+  <summary>Example Command and Output</summary>
+
+```shell
+dataspec -s dataspec.json --log-level error -i 7
 A1
 B2
 C3
@@ -108,6 +151,8 @@ C3
 A1
 ```
 
+</details>
+
 If an additional number is added to TWO, we now get 12 distinct values:
 
 <details open>
@@ -115,31 +160,66 @@ If an additional number is added to TWO, we now get 12 distinct values:
 
 ```json
 {
-  "combine": {"type": "combine", "refs": ["ONE", "TWO"]},
+  "combine": {
+    "type": "combine",
+    "refs": ["ONE", "TWO"]
+  },
   "refs": {
-    "ONE": ["A", "B", "C"],
-    "TWO": [1, 2, 3, 4]
+    "ONE": {
+      "type": "values",
+      "data": ["A", "B", "C"]
+    },
+    "TWO": {
+      "type": "values",
+      "data": [1, 2, 3, 4]
+    }
   }
 }
 ```
-</details>
 
+</details>
 <details>
   <summary>YAML Spec</summary>
 
 ```yaml
----
 combine:
   type: combine
   refs: [ONE, TWO]
 refs:
-  ONE: [A, B, C]
-  TWO: [1, 2, 3 ,4]
+  ONE:
+    type: values
+    data: [A, B, C]
+  TWO:
+    type: values
+    data: [1, 2, 3, 4]
+```
+
+</details>
+<details>
+  <summary>API Example</summary>
+
+```python
+import dataspec
+
+spec_builder = dataspec.spec_builder()
+
+refs = spec_builder.refs()
+one = refs.values('ONE', ["A", "B", "C"])
+two = refs.values('TWO', [1, 2, 3, 4])
+
+spec_builder.combine('combine', refs=[one, two])
+
+spec = spec_builder.build()
 ```
+
 </details>
 
-```shell script
-dataspec -s ~/scratch/sample.json -i 12 | sort
+<details open>
+  <summary>Example Command and Output</summary>
+
+```shell
+dataspec -s dataspec.json --log-level error -i 12 \
+  | sort
 A1
 A2
 A3
@@ -154,113 +234,188 @@ C3
 C4
 ```
 
-If we want our values to be generated randomly from the provided lists, we set the config param `sample` to true:
+</details>
+
+If we want our values to be generated randomly from the provided lists, we set
+the config param `sample` to true:
 
 <details open>
   <summary>JSON Spec</summary>
 
 ```json
 {
-  "combine": {"type": "combine", "refs": ["ONE", "TWO"]},
+  "combine": {
+    "type": "combine",
+    "refs": ["ONE", "TWO"]
+  },
   "refs": {
-    "ONE?sample=true": ["A", "B", "C"],
-    "TWO?sample=true": [1, 2, 3, 4]
+    "ONE": {
+      "type": "values",
+      "data": ["A", "B", "C"],
+      "config": {
+        "sample": true
+      }
+    },
+    "TWO": {
+      "type": "values",
+      "data": [1, 2, 3, 4],
+      "config": {
+        "sample": "yes"
+      }
+    }
   }
 }
 ```
-</details>
 
+</details>
 <details>
   <summary>YAML Spec</summary>
 
 ```yaml
----
 combine:
   type: combine
   refs: [ONE, TWO]
 refs:
-  ONE?sample=true: [A, B, C]
-  TWO?sample=true: [1, 2, 3 ,4]
+  ONE:
+    type: values
+    data: [A, B, C]
+    config:
+      sample: true
+  TWO:
+    type: values
+    data: [1, 2, 3, 4]
+    config:
+      sample: 'yes'
+```
+
+</details>
+<details>
+  <summary>API Example</summary>
+
+```python
+import dataspec
+
+spec_builder = dataspec.spec_builder()
+
+refs = spec_builder.refs()
+one = refs.values('ONE', ["A", "B", "C"], sample=True)
+two = refs.values('TWO', [1, 2, 3, 4], sample="yes")
+
+spec_builder.combine('combine', refs=[one, two])
+
+spec = spec_builder.build()
 ```
+
 </details>
 
 # <a name="Field_Spec_Structure"></a>Field Spec Structure
 
-There are several different ways to define a spec. There is the full spec format and a variety of short hand notations.
+There are several ways to define a Field Spec. There is the full spec format,
+and a variety of short hand notations.
 
 ## <a name="The_full_format."></a>The Full Format.
 
-The only required element is type. Each Type Handler requires different pieces of information. See the Field Type
-reference below for details on each type.
+The only required element is type. Each Type Handler requires different pieces
+of information. See the Field Type reference below for details on each type.
+Below is the general structure.
 
 ```
 {
   "type": "<the type>",
   "config": {
     "key1": "value1",
-    ...
+    ...</br>
     "keyN": "valueN"
   },
   "data": ["the data"],
   "ref": "REF_POINTER_IF_USED",
-  "refs": ["USES", "MORE", "THAN", "ONE"]
+  "refs": ["USES", "MORE", "THAN", "ONE"],
+  "fields": { "for": {}, "nested": {}, "types": {} }
 }
 ```
 
 ## <a name="Values_Shorthand"></a>Values Shorthand
 
-The values type is very common and so has a shorthand notation. Below is an example full Field Spec for some values
-types fields and the same spec in shorthand notation.
+The values type is very common and so has a shorthand notation. Below is an
+example full Field Spec for some values types fields and the same spec in
+shorthand notation.
 
 <details open>
   <summary>JSON Spec</summary>
 
 ```json
 {
-  "field1": {"type": "vaules", "data": [1, 2, 3, 4, 5]},
-  "field2": {"type": "values", "data": {"A": 0.5, "B": 0.3, "C": 0.2}},
-  "field3": {"type": "values", "data": "CONSTANT"}
+  "field1": {
+    "type": "values",
+    "data": [1, 2, 3, 4, 5]
+  },
+  "field2": {
+    "type": "values",
+    "data": {"A": 0.5, "B": 0.3, "C": 0.2}
+  },
+  "field3": {
+    "type": "values",
+    "data": "CONSTANT"
+  }
 }
 ```
-</details>
 
+</details>
 <details>
   <summary>YAML Spec</summary>
 
 ```yaml
----
 field1:
-  type: vaules
+  type: values
   data: [1, 2, 3, 4, 5]
 field2:
   type: values
-  data:
-    A: 0.5
-    B: 0.3
-    C: 0.2
+  data: {A: 0.5, B: 0.3, C: 0.2}
 field3:
   type: values
   data: CONSTANT
 ```
+
+</details>
+<details>
+  <summary>API Example</summary>
+
+```python
+import dataspec
+
+spec_builder = dataspec.spec_builder()
+
+spec_builder.add_field('field1', dataspec.builder.values([1, 2, 3, 4, 5]))
+spec_builder.add_field('field2', dataspec.builder.values({"A": 0.5, "B": 0.3, "C": 0.2}))
+spec_builder.add_field('field3', dataspec.builder.values("CONSTANT"))
+
+spec = spec_builder.build()
+```
+
 </details>
 
+Shorthand Format:
+
 <details open>
   <summary>JSON Spec</summary>
 
 ```json
 {
   "field1": [1, 2, 3, 4, 5],
-  "field2": {"A": 0.5, "B": 0.3, "C": 0.2},
+  "field2": {
+    "A": 0.5,
+    "B": 0.3,
+    "C": 0.2
+  },
   "field3": "CONSTANT"
 }
 ```
-</details>
 
+</details>
 <details>
   <summary>YAML Spec</summary>
 
 ```yaml
----
 field1: [1, 2, 3, 4, 5]
 field2:
   A: 0.5
@@ -268,20 +423,41 @@ field2:
   C: 0.2
 field3: CONSTANT
 ```
+
+</details>
+<details>
+  <summary>API Example</summary>
+
+```python
+import dataspec
+
+spec_builder = dataspec.spec_builder()
+
+spec_builder.add_field('field1', [1, 2, 3, 4, 5])
+spec_builder.add_field('field2', {"A": 0.5, "B": 0.3, "C": 0.2})
+spec_builder.add_field('field3', "CONSTANT")
+
+spec = spec_builder.build()
+```
+
 </details>
 
-The value after the field name is just the value of the data element from the full Field Spec. Config params can be
-added to the key using the URL syntax described below.
+The value after the field name is just the value of the data element from the
+full Field Spec. Config params can be added to the key using the URL syntax
+described below.
 
 ## <a name="Inline_Key_Type_Shorthand"></a>Inline Key Type Shorthand
 
-Some specs lend themselves to being easily specified with few parameters. One short hand way to do this is the use a
-colon in the key to specify the type after the field name. For example `{"id:uuid":{}}`. This says the field `id` is of
-type `uuid` and has no further configuration. If no type is specified, the field is assumed to be a `values` type.
+Some specs lend themselves to being easily specified with few parameters. One
+short hand way to do this is the use a colon in the key to specify the type
+after the field name. For example `{"id:uuid":{}}`. This says the field `id` is
+of type `uuid` and has no further configuration. If no type is specified, the
+field is assumed to be a `values` type.
 
-## <a name="Inline_Key_Config_Shorthad"></a>Inline Key Config Shorthad
+## <a name="Inline_Key_Config_Shorthad"></a>Inline Key Config Shorthand
 
-It is also possible to specify configuration parameters in the key by using URL style parameters. For example.
+It is also possible to specify configuration parameters in the key by using URL
+style parameters. For example.
 
 <details open>
   <summary>JSON Spec</summary>
@@ -291,23 +467,39 @@ It is also possible to specify configuration parameters in the key by using URL
   "network:ipv4?cidr=192.168.0.0/16": {}
 }
 ```
-</details>
 
+</details>
 <details>
   <summary>YAML Spec</summary>
 
 ```yaml
----
 network:ipv4?cidr=192.168.0.0/16: {}
 ```
+
+</details>
+<details>
+  <summary>API Example</summary>
+
+```python
+import dataspec
+
+spec_builder = dataspec.spec_builder()
+
+spec_builder.add_field("network:ipv4?cidr=192.168.0.0/16", {})
+
+spec = spec_builder.build()
+```
+
 </details>
 
-The `network` field is of type `ipv4` and the required `cidr` param is specified in the key.
+The `network` field is of type `ipv4` and the required `cidr` param is specified
+in the key.
 
 # <a name="Spec_Configuration"></a>Spec Configuration
 
-There are two ways to configure a spec. One is by providing a `config` element in the Field Spec and the other is by
-using a URL parameter format in the key. For example, the following two fields will produce the same values:
+There are two ways to configure a spec. One is by providing a `config` element
+in the Field Spec and the other is by using a URL parameter format in the key.
+For example, the following two fields will produce the same values:
 
 <details open>
   <summary>JSON Spec</summary>
@@ -317,41 +509,64 @@ using a URL parameter format in the key. For example, the following two fields w
   "ONE": {
     "type": "values",
     "data": [1, 2, 3],
-    "config": {"prefix": "TEST", "suffix": "@DEMO"}
+    "config": {
+      "prefix": "TEST",
+      "suffix": "@DEMO"
+    }
   },
-  "TWO?prefix=TEST&suffix=@DEMO": [1, 2, 3]
+  "TWO?prefix=TEST&suffix=@DEMO": {
+    "type": "values",
+    "data": [1, 2, 3]
+  }
 }
 ```
-</details>
 
+</details>
 <details>
   <summary>YAML Spec</summary>
 
 ```yaml
----
 ONE:
   type: values
+  data: [1, 2, 3]
   config:
     prefix: TEST
     suffix: '@DEMO'
+TWO?prefix=TEST&suffix=@DEMO:
+  type: values
   data: [1, 2, 3]
-TWO?prefix=TEST&suffix=@DEMO: [1, 2, 3]
 ```
+
 </details>
+<details>
+  <summary>API Example</summary>
 
-## <a name="Common_Configurations"></a>Common Configurations
+```python
+import dataspec
+
+spec_builder = dataspec.spec_builder()
+
+spec_builder.values('ONE', [1, 2, 3], prefix='TEST', suffix='@DEMO')
+spec_builder.values('TWO?prefix=TEST&suffix=@DEMO', [1, 2, 3])
 
-There are some configuration values that can be applied to all or a subset of types. These are listed below
+spec = spec_builder.build()
+```
+
+</details>
+
+## <a name="Common_Configurations"></a>Common Configurations
 
-| key   | argument  |effect |
-|-------|-----------|-------|
-|prefix | string    |Prepends the value to all results |
-|suffix | string    |Appends the value to all results  |
-|quote  | string    |Wraps the resulting value on both sides with the provided string |
-|cast   | i,int,f,float,s,str,string|For numeric types, will cast results the provided type|
-|join_with|string   |For types that produce multiple values, use this string to join them   |
-|as_list|yes,true,on|For types that produce multiple values, return as list without joining |
+There are some configuration values that can be applied to all or a subset of
+types. These are listed below
 
+key      | argument  |effect 
+---------|-----------|-------
+prefix   | string    |Prepends the value to all results 
+suffix   | string    |Appends the value to all results  
+quote    | string    |Wraps the resulting value on both sides with the</br> provided string 
+cast     | i,int,f,float,s,str,string|For numeric types, will cast results</br> the provided type
+join_with|string     |For types that produce multiple values, use this</br> string to join them   
+as_list  |yes,true,on|For types that produce multiple values, return as</br> list without joining 
 
 Example:
 
@@ -361,46 +576,132 @@ Example:
 ```json
 {
   "field": {
-      "type": "values",
-      "config": {
-        "prefix": "Hello "
-      },
-      "data": ["world", "beautiful", "destiny"]
+    "type": "values",
+    "data": ["world", "beautiful", "destiny"],
+    "config": {
+      "prefix": "hello "
     }
+  }
 }
 ```
-</details>
 
+</details>
 <details>
   <summary>YAML Spec</summary>
 
 ```yaml
----
 field:
   type: values
-  config:
-    prefix: 'Hello '
   data: [world, beautiful, destiny]
+  config:
+    prefix: 'hello '
+```
+
+</details>
+<details>
+  <summary>API Example</summary>
+
+```python
+import dataspec
+
+spec_builder = dataspec.spec_builder()
+
+spec_builder.values('field', 
+                    ["world", "beautiful", "destiny"], 
+                    prefix='hello ')
+
+spec = spec_builder.build()
 ```
+
 </details>
 
 ## <a name="CountsField"></a>Count Config Parameter
 
-Several types support a `count` config parameter. The value of the count parameter can be any of the supported values
-specs formats. For example a constant `3`, list `[2, 3, 7]`, or weighted map `{"1": 0.5, "2": 0.3, "3": 0.2 }`. This
-will produce the number of values by creating a value supplier for the count based on the supplied parameter. Most of
-the time if the count is greater that 1, the values will be returned as an array. Some types support joining the values
-by specifying the `join_with` parameter. Some types will let you explicitly set the `as_array` parameter to force the
-results to be returned as an array and not the default for the given type.
+Several types support a `count` config parameter. The value of the count
+parameter can be any of the supported values specs formats. For example a
+constant `3`, list `[2, 3, 7]`, or weighted
+map `{"1": 0.5, "2": 0.3, "3": 0.2 }`. This will produce the number of values by
+creating a value supplier for the count based on the supplied parameter. Most of
+the time if the count is greater that 1, the values will be returned as an
+array. Some types support joining the values by specifying the `join_with`
+parameter. Some types will let you explicitly set the `as_list` parameter to
+force the results to be returned as an array and not the default for the given
+type.
+
+### Count Distributions
+
+Another way to specify a count is to use a count distribution. This is done with
+the `count_dist` param.  The param takes a string argument which is the
+distribution along with its required arguments in function call form with
+parameters explicitly named.  See the table below.
+
+distribution|required arguments|optional args|examples
+------------|------------------|-------------|---
+uniform     |start</br>end     |             |"uniform(start=10, end=30)"
+</i>        |                  |             |"uniform(start=1, end=3)"
+guass       |mean</br>stddev   |min</br>max  |"gauss(mean=2, stddev=1)"
+guassian    |                  |             |"guassian(mean=7, stddev=1, min=4)"
+normal      |                  |             |"normal(mean=25, stddev=10, max=40)"
+
+`normal`, `guassian`, and `gauss` are all aliases for a
+[Normal Distribution](https://en.wikipedia.org/wiki/Normal_distribution).
+
+Example:
+
+<details open>
+  <summary>JSON Spec</summary>
+
+```json
+{
+  "field": {
+    "type": "char_class",
+    "data": "visible",
+    "config": {
+      "count_dist": "normal(mean=5, stddev=2, min=1, max=9)"
+    }
+  }
+}
+```
+
+</details>
+<details>
+  <summary>YAML Spec</summary>
+
+```yaml
+field:
+  type: char_class
+  data: visible
+  config:
+    count_dist: normal(mean=5, stddev=2, min=1, max=9)
+```
+
+</details>
+<details>
+  <summary>API Example</summary>
+
+```python
+import dataspec
+
+spec_builder = dataspec.spec_builder()
+
+spec_builder.char_class(key='field',
+                        data='visible',
+                        count_dist='normal(mean=5, stddev=2, min=1, max=9)')
+
+spec = spec_builder.build()
+```
+
+</details>
 
 # <a name="Field_Spec_Types"></a>Field Spec Types
 
-These are the built in types
+These are the built-in types
 
 ## <a name="Values"></a>Values
 
-There are three types of values specs: Constants, List, and Weighted. Values specs have a shorthand notation where the
-value of the data element replaces the full spec. See examples below.
+There are three types of values specs: Constants, List, and Weighted. Values
+specs have a shorthand notation where the value of the data element replaces the
+full spec. See examples below.
 
 ### <a name="Constant_Values"></a>Constant Values
 
@@ -411,12 +712,15 @@ A Constant Value is just a single value that is used in every iteration
 
 ```json
 {
-  "constant1": {"type": "values", "data": 42},
+  "constant1": {
+    "type": "values",
+    "data": 42
+  },
   "shorthand_constant": "This is simulated data and should not be used for nefarious purposes"
 }
 ```
-</details>
 
+</details>
 <details>
   <summary>YAML Spec</summary>
 
@@ -426,37 +730,73 @@ constant1:
   data: 42
 shorthand_constant: This is simulated data and should not be used for nefarious purposes
 ```
+
+</details>
+<details>
+  <summary>API Example</summary>
+
+```python
+import dataspec
+
+spec_builder = dataspec.spec_builder()
+
+spec_builder.values('constant1', 42)
+spec_builder.add_field('shorthand_constant', "This is simulated data and should not be used for nefarious purposes")
+
+spec = spec_builder.build()
+```
+
 </details>
 
 ### <a name="List_Values"></a>List Values
 
-List values are rotated through in order. If the number of iterations is larger than the size of the list, we start over
-from the beginning of the list. Use the `sample` config param to specify that the values should be selected at random
-from the provided list.
+List values are rotated through in order. If the number of iterations is larger
+than the size of the list, we start over from the beginning of the list. Use
+the `sample` config param to specify that the values should be selected at
+random from the provided list.
 
 <details open>
   <summary>JSON Spec</summary>
 
 ```json
 {
-  "list1": {"type": "values", "data": [200, 202, 303, 400, 404, 500]},
-  "shorthand_list":  [200, 202, 303, 400, 404, 500],
+  "list1": {
+    "type": "values",
+    "data": [200, 202, 303, 400, 404, 500]
+  },
+  "shorthand_list": [200, 202, 303, 400, 404, 500],
   "random_pet?sample=true": ["dog", "cat", "bunny", "pig", "rhino", "hedgehog"]
 }
 ```
-</details>
 
+</details>
 <details>
   <summary>YAML Spec</summary>
 
 ```yaml
----
 list1:
   type: values
   data: [200, 202, 303, 400, 404, 500]
 shorthand_list: [200, 202, 303, 400, 404, 500]
 random_pet?sample=true: [dog, cat, bunny, pig, rhino, hedgehog]
 ```
+
+</details>
+<details>
+  <summary>API Example</summary>
+
+```python
+import dataspec
+
+spec_builder = dataspec.spec_builder()
+
+spec_builder.values('list1', [200, 202, 303, 400, 404, 500])
+spec_builder.add_field("shorthand_list",  [200, 202, 303, 400, 404, 500])
+spec_builder.add_field("random_pet?sample=true", ["dog", "cat", "bunny", "pig", "rhino", "hedgehog"])
+
+spec = spec_builder.build()
+```
+
 </details>
 
 ### <a name="Weighted_Values"></a>Weighted Values
@@ -470,34 +810,28 @@ Weighted values are generated according to their weights.
 {
   "weighted1": {
     "type": "values",
-    "data": {
-      "200": 0.4, "202": 0.3, "303": 0.1,
-      "400": 0.05, "403": 0.05, "404": 0.05, "500": 0.05
-    }
+    "data": {"200": 0.4, "202": 0.3, "303": 0.1, "400": 0.05, "403": 0.05, "404": 0.05, "500": 0.05}
   },
   "shorthand_weighted": {
-    "200": 0.4, "202": 0.3, "303": 0.1,
-    "400": 0.05, "403": 0.05, "404": 0.05, "500": 0.05
+    "200": 0.4,
+    "202": 0.3,
+    "303": 0.1,
+    "400": 0.05,
+    "403": 0.05,
+    "404": 0.05,
+    "500": 0.05
   }
 }
 ```
-</details>
 
+</details>
 <details>
   <summary>YAML Spec</summary>
 
 ```yaml
----
 weighted1:
   type: values
-  data:
-    '200': 0.4
-    '202': 0.3
-    '303': 0.1
-    '400': 0.05
-    '403': 0.05
-    '404': 0.05
-    '500': 0.05
+  data: {'200': 0.4, '202': 0.3, '303': 0.1, '400': 0.05, '403': 0.05, '404': 0.05, '500': 0.05}
 shorthand_weighted:
   '200': 0.4
   '202': 0.3
@@ -507,53 +841,104 @@ shorthand_weighted:
   '404': 0.05
   '500': 0.05
 ```
+
+</details>
+<details>
+  <summary>API Example</summary>
+
+```python
+import dataspec
+
+spec_builder = dataspec.spec_builder()
+
+spec_builder.values('weighted1', {
+        "200": 0.4, "202": 0.3, "303": 0.1,
+        "400": 0.05, "403": 0.05, "404": 0.05, "500": 0.05
+})
+spec_builder.add_field("shorthand_weighted", {
+        "200": 0.4, "202": 0.3, "303": 0.1,
+        "400": 0.05, "403": 0.05, "404": 0.05, "500": 0.05
+})
+
+spec = spec_builder.build()
+```
+
 </details>
 
-The example above will generate 200 40% of the time and 400 and 403 5%. The higher the number of iterations the more
-likely the values will match their specified weights.
+The example above will generate 200 40% of the time and 400 and 403 5%. The
+higher the number of iterations the more likely the values will match their
+specified weights.
 
 ### <a name="Sample_Mode"></a>Sample Mode
 
-To increase the randomness of the data being generated you can configure a FieldSpec that contains a list of values to
-be sampled instead of iterated through incrementally. Normally the spec below would create the repeating
-sequence: `A1 B2 C3`, but since both fields `ONE` and `TWO` are in sample mode, we will get all nine combinations of
-values after a significant number of iterations. This would also be true if only one was set to sample mode. To turn
-sample mode on either use a URL param or config entry with one of `on`,  `yes`, or `true`. NOTE: Sample mode is only
-valid with entries that are lists.
+To increase the randomness of the data being generated, you can configure a
+FieldSpec that contains a list of values to be sampled instead of iterated
+through incrementally. Normally the spec below would create the repeating
+sequence: `A1 B2 C3`, but since both fields `ONE` and `TWO` are in sample mode,
+we will get all nine combinations of values after a significant number of
+iterations. This would also be true if only one was set to sample mode. To turn
+sample mode on either use a URL param or config entry with one of `on`,  `yes`,
+or `true`. NOTE: Sample mode is only valid with entries that are lists.
 
 <details open>
   <summary>JSON Spec</summary>
 
 ```json
 {
-  "combine": {"type": "combine", "refs": ["ONE", "TWO"]},
+  "combine": {
+    "type": "combine",
+    "refs": ["ONE", "TWO"]
+  },
   "refs": {
     "ONE?sample=true": ["A", "B", "C"],
-    "TWO?sample=true": [1, 2, 3]
+    "TWO?sample=true": [1, 2, 3, 4]
   }
 }
 ```
-</details>
 
+</details>
 <details>
   <summary>YAML Spec</summary>
 
 ```yaml
----
 combine:
   type: combine
-  refs:
-  - ONE
-  - TWO
+  refs: [ONE, TWO]
 refs:
   ONE?sample=true: [A, B, C]
-  TWO?sample=true: [1, 2, 3]
+  TWO?sample=true: [1, 2, 3, 4]
+```
+
+</details>
+<details>
+  <summary>API Example</summary>
+
+```python
+import dataspec
+
+spec_builder = dataspec.spec_builder()
+
+refs = spec_builder.refs()
+refs.add_field('ONE?sample=true', ["A", "B", "C"])
+refs.add_field('TWO?sample=true', [1, 2, 3, 4])
+
+spec_builder.combine('combine', refs=['ONE', 'TWO'])
+
+spec = spec_builder.build()
 ```
+
 </details>
 
+#### Sample All
+
+If running from the command line, you cas specify the `--sample-lists` flag to
+make all list backed data to have sampling turned on by default. If using the
+python API, do `dataspec.types.set_default('sample_mode', True)`
+
 ## <a name="Combine"></a>Combine
 
-A combine Field Spec is used to concatenate or append two or more fields or reference to one another.
+A combine Field Spec is used to concatenate or append two or more fields or
+reference to one another.
 
 The combine Field Spec structure is:
 
@@ -564,7 +949,9 @@ The combine Field Spec structure is:
     "fields": ["valid field name1", "valid field name2"],
     OR
     "refs": ["valid ref1", "valid ref2"],
-    "config": {"join_with": "<optional string to use to join fields or refs, default is none>"}
+    "config": {
+      "join_with": "<optional string to use to join fields or refs, default is none>"
+    }
   }
 }
 ```
@@ -576,10 +963,12 @@ Example below uses the first and last refs to create a full name field.
 
 ```json
 {
-  "full name": {
+  "combine": {
     "type": "combine",
     "refs": ["first", "last"],
-    "config": {"join_with": " "}
+    "config": {
+      "join_with": " "
+    }
   },
   "refs": {
     "first": {
@@ -593,168 +982,854 @@ Example below uses the first and last refs to create a full name field.
   }
 }
 ```
+
+</details>
+<details>
+  <summary>YAML Spec</summary>
+
+```yaml
+combine:
+  type: combine
+  refs: [first, last]
+  config:
+    join_with: ' '
+refs:
+  first:
+    type: values
+    data: [zebra, hedgehog, llama, flamingo]
+  last:
+    type: values
+    data: [jones, smith, williams]
+```
+
+</details>
+<details>
+  <summary>API Example</summary>
+
+```python
+import dataspec
+
+spec_builder = dataspec.spec_builder()
+
+refs = spec_builder.refs()
+first = refs.values(key="first",
+                    data=["zebra", "hedgehog", "llama", "flamingo"])
+last = refs.values(key="last",
+                   data=["jones", "smith", "williams"])
+
+spec_builder.combine('combine', refs=[first, last], join_with=" ")
+
+spec = spec_builder.build()
+```
+
+</details>
+
+## <a name="CombineList"></a>Combine List
+
+A combine-list Field Spec is used to specify a list of lists of refs to combine.
+This is useful if there are a lot of variations on the values that should be
+combined. This allows all the variations to be specified in one place. Note:
+This approach requires the same join_with param for each set of refs.
+
+The combine Field Spec structure is:
+
+```
+{
+  "<field name>": {
+    "type": "combine-list",
+    "refs": [
+      ["valid ref1", "valid ref2"],
+      ["valid ref1", "valid ref2", "valid_ref3", ...], ...
+      ["another_ref", "one_more_ref"]
+    ],
+    "config": {"join_with": "<optional string to use to join fields or refs, default is none>"}
+  }
+}
+```
+
+This is a slight modification to the above combine Example.
+
+<details open>
+  <summary>JSON Spec</summary>
+
+```json
+{
+  "full_name": {
+    "type": "combine-list",
+    "refs": [
+      ["first", "last"],
+      ["first", "middle", "last"],
+      ["first", "middle_initial", "last"]
+    ],
+    "config": {
+      "join_with": " "
+    }
+  },
+  "refs": {
+    "first": {
+      "type": "values",
+      "data": ["zebra", "hedgehog", "llama", "flamingo"]
+    },
+    "last": {
+      "type": "values",
+      "data": ["jones", "smith", "williams"]
+    },
+    "middle": {
+      "type": "values",
+      "data": ["cloud", "sage", "river"]
+    },
+    "middle_initial": {
+      "type": "values",
+      "data": {"a": 0.3, "m": 0.3, "j": 0.1, "l": 0.1, "e": 0.1, "w": 0.1}
+    }
+  }
+}
+```
+
+</details>
+<details>
+  <summary>YAML Spec</summary>
+
+```yaml
+full_name:
+  type: combine-list
+  refs:
+  - [first, last]
+  - [first, middle, last]
+  - [first, middle_initial, last]
+  config:
+    join_with: ' '
+refs:
+  first:
+    type: values
+    data: [zebra, hedgehog, llama, flamingo]
+  last:
+    type: values
+    data: [jones, smith, williams]
+  middle:
+    type: values
+    data: [cloud, sage, river]
+  middle_initial:
+    type: values
+    data: {a: 0.3, m: 0.3, j: 0.1, l: 0.1, e: 0.1, w: 0.1}
+```
+
+</details>
+<details>
+  <summary>API Example</summary>
+
+```python
+import dataspec
+
+spec_builder = dataspec.spec_builder()
+
+refs = spec_builder.refs()
+first = refs.values(
+    key="first",
+    data=["zebra", "hedgehog", "llama", "flamingo"])
+last = refs.values(
+    key="last",
+    data=["jones", "smith", "williams"])
+middle = refs.values(
+    key="middle",
+    data=["cloud", "sage", "river"])
+middle_initial = refs.values(
+    key="middle_initial",
+    data={"a": 0.3, "m": 0.3, "j": 0.1, "l": 0.1, "e": 0.1, "w": 0.1})
+
+spec_builder.combine_list(
+    key='full_name',
+    refs=[
+        [first, last],
+        [first, middle, last],
+        [first, middle_initial, last],
+        ],
+    join_with=" ")
+
+spec = spec_builder.build()
+```
+
+</details>
+
+## <a name="Date"></a>Date
+
+A Date Field Spec is used to generate date strings. The default format is
+day-month-year i.e. Christmas 2050 would be: 25-12-2050. There is also
+a `date.iso` type that generates ISO8601 formatted date strings without
+microseconds and a `date.iso.us` for one that generates them with microseconds.
+We use the
+[format specification](https://docs.python.org/3/library/datetime.html#strftime-and-strptime-format-codes)
+from the datetime module.
+
+### Uniformly Sampled Dates
+
+The default strategy is to create random dates within a 30 day range, where the
+start date is today. You can use the `start` parameter to set a specific start
+date for the dates. You can also explicitly specify an `end` date. The `start`
+and `end` parameters should conform to the specified date format, or the default
+if none is provided. The `offset` parameter can be used to shift the dates by a
+specified number of days. A positive `offset` will shift the start date back. A
+negative `offset` will shift the date forward. The `duration_days` parameter can
+be used to specify the number of days that should be covered in the date range,
+instead of the default 30 days. This parameter is usually specified as an
+integer constant.
+
+```
+       start                              end (default start + 30 days)
+          |--------------------------------|
+  |+offset|                           start+duration_days
+  |--------------------------------|
+          |-offset|
+                  |--------------------------------|    
+```
+
+### Dates Distributed around a Center Point
+
+An alternative strategy is to specify a `center_date` parameter with an
+optional `stddev_days`. This will create a normal or gaussian distribution of
+dates around the center point.
+
+```  
+                   |
+                   |
+                |  |  |
+             |  |  |  |  |
+          |  |  |  |  |  |  |  
+ |  |  |  |  |  |  |  |  |  |  |  |  |
+|-------------------------------------|
+|         | stddev | stddev |         |
+                center
+```
+
+There are a lot of configuration parameters for the date type. Each are
+described below.
+
+### Parameters
+
+<details>
+
+<summary>Parameter Details</summary>
+
+param | type | description                                  | default | examples
+------|------|----------------------------------------------|---------|--------- 
+format|string |Valid datetime format string |%d-%m-%Y |%Y%m%d</br>%m/%d/%Y</br>%H:%M:%S</br> 
+duration_days| |The number of days from the start date</br>to create date strings for |30 |1</br>30</br>90</br>9999</br> 
+start|string |date string matching format or default</br>format to use for start date |None |22-02-2022</br>02/22/1972</br>2009-09-01T08:08.000Z</br> 
+end|string |date string matching format or default</br>format to use for end date |None |22-02-2022</br>02/22/1972</br>2009-09-01T08:08.000Z</br> 
+offset|integer |number of days to shift base date by,</br>positive means shift backwards, negative</br>means forward |0 |30</br>-30</br>365</br>730</br> 
+center_date|string |date string matching format or default</br>format to use for center date |None |22-02-2022</br>02/22/1972</br>2009-09-01T08:08.000Z</br> 
+stddev_days| |The standard deviation in days from the</br>center date that dates should be</br>distributed |15 |1</br>12</br>720</br>
+</details>
+
+The date Field Spec structure is:
+
+```
+{
+  "<field name>": {
+    "type": "date",
+    OR,
+    "type": "date.iso",
+    OR,
+    "type": "date.iso.us",
+    "config": {"...": "..."}
+  }
+}
+```
+
+### Examples
+
+To help with the number of variations of date formats, there are a number of
+examples below. They all assume today is 15 Jan 2050, so the default date
+formatted for today would be 15-01-2050. Click More Examples to see all
+examples.
+
+#### Uniform Dates Examples
+
+<details open>
+  <summary>JSON Spec</summary>
+
+```json
+{
+  "dates:date?duration_days=90&start=15-Dec-2050 12:00&format=%d-%b-%Y %H:%M": {}
+}
+```
+
+</details>
+<details>
+  <summary>YAML Spec</summary>
+
+```yaml
+dates:date?duration_days=90&start=15-Dec-2050 12:00&format=%d-%b-%Y %H:%M: {}
+```
+
+</details>
+<details>
+  <summary>API Example</summary>
+
+```python
+import dataspec
+
+spec_builder = dataspec.spec_builder()
+
+spec_builder.add_field("dates:date?duration_days=90&start=15-Dec-2050 12:00&format=%d-%b-%Y %H:%M", {})
+
+spec = spec_builder.build()
+```
+
+</details>
+
+<details open>
+  <summary>Example Command and Output</summary>
+
+```shell
+dataspec -s dataspec.json --log-level error -i 1000 \
+  | sort -t- -k3n -k2n -k1n | uniq | sed -n '1p;$p'
+15-Dec-2050 13:41
+31-Jan-2051 23:32
+```
+
+</details>
+
+<details>
+<summary>More Examples</summary>
+
+
+<details>
+  <summary>JSON Spec</summary>
+
+```json
+{
+  "dates:date": {}
+}
+```
+
+</details>
+<details>
+  <summary>YAML Spec</summary>
+
+```yaml
+dates:date: {}
+```
+
+</details>
+<details>
+  <summary>API Example</summary>
+
+```python
+import dataspec
+
+spec_builder = dataspec.spec_builder()
+
+spec_builder.add_field("dates:date", {})
+
+spec = spec_builder.build()
+```
+
+</details>
+
+<details open>
+  <summary>Example Command and Output</summary>
+
+```shell
+dataspec --inline '{"dates:date": {}}' --log-level error -i 1000 \
+  | sort -t- -k3n -k2n -k1n | uniq | sed -n '1p;$p'
+02-07-2021
+01-08-2021
+```
+
+</details>
+
+
+<details>
+  <summary>JSON Spec</summary>
+
+```json
+{
+  "dates:date?offset=1": {}
+}
+```
+
+</details>
+<details>
+  <summary>YAML Spec</summary>
+
+```yaml
+dates:date?offset=1: {}
+```
+
+</details>
+<details>
+  <summary>API Example</summary>
+
+```python
+import dataspec
+
+spec_builder = dataspec.spec_builder()
+
+spec_builder.add_field("dates:date?offset=1", {})
+
+spec = spec_builder.build()
+```
+
+</details>
+
+<details open>
+  <summary>Example Command and Output</summary>
+
+```shell
+dataspec --inline '{"dates:date?offset=1": {}}' --log-level error -i 1000 \
+  | sort -t- -k3n -k2n -k1n | uniq | sed -n '1p;$p'
+01-07-2021
+31-07-2021
+```
+
+</details>
+
+
+<details>
+  <summary>JSON Spec</summary>
+
+```json
+{
+  "dates:date?duration_days=1": {}
+}
+```
+
+</details>
+<details>
+  <summary>YAML Spec</summary>
+
+```yaml
+dates:date?duration_days=1: {}
+```
+
+</details>
+<details>
+  <summary>API Example</summary>
+
+```python
+import dataspec
+
+spec_builder = dataspec.spec_builder()
+
+spec_builder.add_field("dates:date?duration_days=1", {})
+
+spec = spec_builder.build()
+```
+
+</details>
+
+<details open>
+  <summary>Example Command and Output</summary>
+
+```shell
+dataspec --inline '{"dates:date?duration_days=1": {}}' --log-level error -i 1000 \
+  | sort -t- -k3n -k2n -k1n | uniq | sed -n '1p;$p'
+02-07-2021
+03-07-2021
+```
+
+</details>
+
+
+<details>
+  <summary>JSON Spec</summary>
+
+```json
+{
+  "dates:date?duration_days=10": {}
+}
+```
+
+</details>
+<details>
+  <summary>YAML Spec</summary>
+
+```yaml
+dates:date?duration_days=10: {}
+```
+
+</details>
+<details>
+  <summary>API Example</summary>
+
+```python
+import dataspec
+
+spec_builder = dataspec.spec_builder()
+
+spec_builder.add_field("dates:date?duration_days=10", {})
+
+spec = spec_builder.build()
+```
+
+</details>
+
+<details open>
+  <summary>Example Command and Output</summary>
+
+```shell
+dataspec --inline '{"dates:date?duration_days=10": {}}' --log-level error -i 1000 \
+  | sort -t- -k3n -k2n -k1n | uniq | sed -n '1p;$p'
+02-07-2021
+12-07-2021
+```
+
+</details>
+
+
+<details>
+  <summary>JSON Spec</summary>
+
+```json
+{
+  "dates:date?duration_days=1&offset=1": {}
+}
+```
+
+</details>
+<details>
+  <summary>YAML Spec</summary>
+
+```yaml
+dates:date?duration_days=1&offset=1: {}
+```
+
+</details>
+<details>
+  <summary>API Example</summary>
+
+```python
+import dataspec
+
+spec_builder = dataspec.spec_builder()
+
+spec_builder.add_field("dates:date?duration_days=1&offset=1", {})
+
+spec = spec_builder.build()
+```
+
+</details>
+
+<details open>
+  <summary>Example Command and Output</summary>
+
+```shell
+dataspec --inline '{"dates:date?duration_days=1&offset=1": {}}' --log-level error -i 1000 \
+  | sort -t- -k3n -k2n -k1n | uniq | sed -n '1p;$p'
+01-07-2021
+02-07-2021
+```
+
+</details>
+
+
+<details>
+  <summary>JSON Spec</summary>
+
+```json
+{
+  "dates:date?duration_days=1&offset=-1": {}
+}
+```
+
+</details>
+<details>
+  <summary>YAML Spec</summary>
+
+```yaml
+dates:date?duration_days=1&offset=-1: {}
+```
+
+</details>
+<details>
+  <summary>API Example</summary>
+
+```python
+import dataspec
+
+spec_builder = dataspec.spec_builder()
+
+spec_builder.add_field("dates:date?duration_days=1&offset=-1", {})
+
+spec = spec_builder.build()
+```
+
+</details>
+
+<details open>
+  <summary>Example Command and Output</summary>
+
+```shell
+dataspec --inline '{"dates:date?duration_days=1&offset=-1": {}}' --log-level error -i 1000 \
+  | sort -t- -k3n -k2n -k1n | uniq | sed -n '1p;$p'
+03-07-2021
+04-07-2021
+```
+
+</details>
+
+
+<details>
+  <summary>JSON Spec</summary>
+
+```json
+{
+  "dates:date?duration_days=1&offset=1&start=15-12-2050": {}
+}
+```
+
+</details>
+<details>
+  <summary>YAML Spec</summary>
+
+```yaml
+dates:date?duration_days=1&offset=1&start=15-12-2050: {}
+```
+
+</details>
+<details>
+  <summary>API Example</summary>
+
+```python
+import dataspec
+
+spec_builder = dataspec.spec_builder()
+
+spec_builder.add_field("dates:date?duration_days=1&offset=1&start=15-12-2050", {})
+
+spec = spec_builder.build()
+```
+
+</details>
+
+<details open>
+  <summary>Example Command and Output</summary>
+
+```shell
+dataspec --inline '{"dates:date?duration_days=1&offset=1&start=15-12-2050": {}}' --log-level error -i 1000 \
+  | sort -t- -k3n -k2n -k1n | uniq | sed -n '1p;$p'
+14-12-2050
+14-12-2050
+```
+
 </details>
 
+
+<details>
+  <summary>JSON Spec</summary>
+
+```json
+{
+  "dates:date?duration_days=1&start=15-Dec-2050 12:00&format=%d-%b-%Y %H:%M": {}
+}
+```
+
+</details>
 <details>
   <summary>YAML Spec</summary>
 
 ```yaml
----
-full name:
-  type: combine
-  refs:
-  - first
-  - last
-  config:
-    join_with: ' '
-refs:
-  first:
-    type: values
-    data: [zebra, hedgehog, llama, flamingo]
-  last:
-    type: values
-    data: [jones, smith, williams]
+dates:date?duration_days=1&start=15-Dec-2050 12:00&format=%d-%b-%Y %H:%M: {}
 ```
+
 </details>
+<details>
+  <summary>API Example</summary>
 
-## <a name="CombineList"></a>Combine List
+```python
+import dataspec
 
-A combine-list Field Spec is used to specify a list of lists of refs to combine. This is useful if there are a lot of
-variations on the values that should be combined. This allows all the variations to be specified in one place. Note:
-This approach requires the same join_with param for each set of refs.
+spec_builder = dataspec.spec_builder()
 
-The combine Field Spec structure is:
+spec_builder.add_field("dates:date?duration_days=1&start=15-Dec-2050 12:00&format=%d-%b-%Y %H:%M", {})
+
+spec = spec_builder.build()
+```
+
+</details>
+
+<details open>
+  <summary>Example Command and Output</summary>
 
+```shell
+dataspec --inline '{"dates:date?duration_days=1&start=15-Dec-2050 12:00&format=%d-%b-%Y %H:%M": {}}' --log-level error -i 1000 \
+  | sort -t- -k3n -k2M -k1n | uniq | sed -n '1p;$p'
+15-Dec-2050 12:00
+16-Dec-2050 11:58
 ```
+
+</details>
+
+</details>
+
+#### Centered Dates Examples
+
+<details open>
+  <summary>JSON Spec</summary>
+
+```json
 {
-  "<field name>": {
-    "type": "combine-list",
-    "refs": [
-      ["valid ref1", "valid ref2"],
-      ["valid ref1", "valid ref2", "valid_ref3", ...], ...
-      ["another_ref", "one_more_ref"]
-    ],
-    "config": {"join_with": "<optional string to use to join fields or refs, default is none>"}
-  }
+  "dates:date?center_date=20500601 12:00&format=%Y%m%d %H:%M&stddev_days=2": {}
 }
 ```
 
-This is a slight modification to the above combine Example.
+</details>
+<details>
+  <summary>YAML Spec</summary>
+
+```yaml
+dates:date?center_date=20500601 12:00&format=%Y%m%d %H:%M&stddev_days=2: {}
+```
+
+</details>
+<details>
+  <summary>API Example</summary>
+
+```python
+import dataspec
+
+spec_builder = dataspec.spec_builder()
+
+spec_builder.add_field("dates:date?center_date=20500601 12:00&format=%Y%m%d %H:%M&stddev_days=2", {})
+
+spec = spec_builder.build()
+```
+
+</details>
 
 <details open>
+  <summary>Example Command and Output</summary>
+
+```shell
+dataspec --inline '{"dates:date?center_date=20500601 12:00&format=%Y%m%d %H:%M&stddev_days=2": {}}' --log-level error -i 1000 \
+  | sort -n | uniq | sed -n '1p;$p'
+20500525 20:43
+20500607 00:36
+```
+
+</details>
+
+<details>
+<summary>More Examples</summary>
+
+
+<details>
   <summary>JSON Spec</summary>
 
 ```json
 {
-  "full name": {
-    "type": "combine-list",
-    "refs": [
-      ["first", "last"],
-      ["first", "middle", "last"],
-      ["first", "middle_initial", "last"]
-    ],
-    "config": {"join_with": " "}
-  },
-  "refs": {
-    "first": ["zebra", "hedgehog", "llama", "flamingo"],
-    "last": ["jones", "smith", "williams"],
-    "middle": ["cloud", "sage", "river"],
-    "middle_initial": {"a": 0.3, "m": 0.3, "j": 0.1, "l": 0.1, "e": 0.1, "w": 0.1}
-  }
+  "dates:date?stddev_days=1": {}
 }
 ```
-</details>
 
+</details>
 <details>
   <summary>YAML Spec</summary>
 
 ```yaml
----
-full name:
-  type: combine-list
-  refs: [
-    [first, last],
-    [first, middle, last],
-    [first, middle_initial, last]
-  ]
-  config:
-    join_with: ' '
-refs:
-  first: [zebra, hedgehog, llama, flamingo]
-  last: [jones, smith, williams]
-  middle: [cloud, sage, river]
-  middle_initial:
-    a: 0.3
-    m: 0.3
-    j: 0.1
-    l: 0.1
-    e: 0.1
-    w: 0.1
+dates:date?stddev_days=1: {}
 ```
+
 </details>
+<details>
+  <summary>API Example</summary>
 
-## <a name="Date"></a>Date
+```python
+import dataspec
 
-A Date Field Spec is used to generate date strings. The default format is day-month-year i.e. Christmas 2050 would be:
-25-12-2050. There is also a `date.iso` type that generates ISO8601 formatted date strings without microseconds and a
-`date.iso.us` for one that generates them with microseconds. We use
-the [format specification](https://docs.python.org/3/library/datetime.html#strftime-and-strptime-format-codes)
-from the datetime module. The default strategy is to create dates around a center date. The default is to use today with
-a spread of +-15 days. To make the base or anchor date the start or end of the date range, use the delta_days parameter
-with an array of two elements, where one is zero. If the first is zero then all generated dates will only be after the
-base/anchor date. If the second element is zero then all generated dates will be before the base/anchor date. There are
-a lot of configuration parameters for the date module. Each are described below.
+spec_builder = dataspec.spec_builder()
 
-### Parameters
+spec_builder.add_field("dates:date?stddev_days=1", {})
 
-| param | description                                      | examples |
-|-------|--------------------------------------------------|----------|
-|format | datetime compatible format specification         | %Y-%m-%d, %m/%d/%Y, %H:%M:%S,... |
-|delta_days | The number of days +- from the base/anchor date to create date strings for | 1, 12, \[14, 0\] |
-|anchor | date string matching format or default format to use for base date | 22-02-2022 |
-|offset | number of days to shift base date by, positive means shift backwards, negative means forward | 30, -7, ...|
+spec = spec_builder.build()
+```
 
-The date Field Spec structure is:
+</details>
+
+<details open>
+  <summary>Example Command and Output</summary>
 
+```shell
+dataspec --inline '{"dates:date?stddev_days=1": {}}' --log-level error -i 1000 \
+  | sort -t- -k3n -k2n -k1n | uniq | sed -n '1p;$p'
+29-06-2021
+05-07-2021
 ```
+
+</details>
+
+
+<details>
+  <summary>JSON Spec</summary>
+
+```json
 {
-  "<field name>": {
-    "type": "date",
-    OR,
-    "type": "date.iso",
-    OR,
-    "type": "date.iso.us",
-    "config": {"...": "..."}
-  }
+  "dates:date?stddev_days=15": {}
 }
 ```
 
-### Examples
+</details>
+<details>
+  <summary>YAML Spec</summary>
+
+```yaml
+dates:date?stddev_days=15: {}
+```
+
+</details>
+<details>
+  <summary>API Example</summary>
+
+```python
+import dataspec
+
+spec_builder = dataspec.spec_builder()
+
+spec_builder.add_field("dates:date?stddev_days=15", {})
+
+spec = spec_builder.build()
+```
+
+</details>
+
+<details open>
+  <summary>Example Command and Output</summary>
+
+```shell
+dataspec --inline '{"dates:date?stddev_days=15": {}}' --log-level error -i 1000 \
+  | sort -t- -k3n -k2n -k1n | uniq | sed -n '1p;$p'
+19-05-2021
+16-08-2021
+```
+
+</details>
 
-To help with the number of variations of date formats, below is a table of examples. Assume today is 15 Jan 2050, so the
-default date formatted for today would be 15-01-2050
-
-|format  |delta_days|anchor     |offset|produces                 |spec|
-|--------|----------|-----------|------|-------------------------|----|
-|-       |-         |-          |-     |12-31-2049 ... 30-01-2050|`{"dates:date":{}}`|
-|-       |-         |-          |1     |12-30-2049 ... 29-01-2050|`{"dates:date?offset=1":{}}`|
-|-       |1         |-          |-     |14-01-2050 ... 16-01-2050|`{"dates:date?delta_days=1":{}}`|
-|-       |-1        |-          |-     |same as above            |`{"dates:date?delta_days=-1":{}}`|
-|-       |1         |-          |1     |13-01-2050 ... 15-01-2050|`{"dates:date?delta_days=1&offset=1":{}}`|
-|-       |1         |-          |-1    |15-01-2050 ... 17-01-2050|`{"dates:date?delta_days=1&offset=-1":{}}`|
-|-       |1         |15-12-2050 |1     |13-12-2050 ... 15-12-2050|`{"dates:date?delta_days=1&offset=1&anchor=15-12-2050":{}}`|
-|%d-%b-%Y|1         |15-Dec-2050|-     |14-Dec-2050 ... 16-Dec-2050|`{"dates:date?delta_days=1&anchor=15-Dec-2050&format=%d-%b-%Y":{}}`|
-|-       |\[1,2\]   |-          |-     |15-01-2050 ... 17-01-2050|`{"dates:date":{"config":{"delta_days":[0, 2]}}}`|
+</details>
 
 ### ISO8601 formatted dates
 
-The type `date.iso` will produce a ISO8601 formatted date in the bounds configured without milliseconds. Use
-the `date.iso.us` type to generate them with microseconds.
+The type `date.iso` will produce a ISO8601 formatted date in the bounds
+configured without milliseconds. Use the `date.iso.us` type to generate them
+with microseconds.
 
 ## <a name="Range"></a>Range
 
-A `range` spec is used to generate a range of values. The ranges are inclusive for start and end. The start, stop, and
-step can be integers or floating point numbers.
+A `range` spec is used to generate a range of values. The ranges are inclusive
+for start and end. The start, stop, and step can be integers or floating-point
+numbers.
+
+### Parameters
+
+<details>
+
+<summary>Parameter Details</summary>
+
+param | type | description                                  | default | examples
+------|------|----------------------------------------------|---------|--------- 
+cast|string |Type to cast values to for field| |i</br>int</br>f</br>float</br>s</br>str</br>string</br>h</br>hex</br> 
+precision|integer |How many digits after decimal point to</br>include in values|None |0</br>2</br>7</br>12</br>
+</details>
 
 The range Field Spec structure is:
 
@@ -785,33 +1860,42 @@ Example: Range 0 to 10 with a step of 0.5
     "type": "range",
     "data": [0, 10, 0.5]
   },
-  "range_shorthand1:range": {"data": [0, 10, 0.5]},
+  "range_shorthand1:range": {
+    "data": [0, 10, 0.5]
+  },
   "range_shorthand2:range": [0, 10, 0.5]
 }
 ```
-</details>
 
+</details>
 <details>
   <summary>YAML Spec</summary>
 
 ```yaml
----
-range_shorthand1:range:
-  data:
-  - 0
-  - 10
-  - 0.5
-range_shorthand2:range:
-- 0
-- 10
-- 0.5
 zero_to_ten:
-  data:
-  - 0
-  - 10
-  - 0.5
   type: range
+  data: [0, 10, 0.5]
+range_shorthand1:range:
+  data: [0, 10, 0.5]
+range_shorthand2:range: [0, 10, 0.5]
+```
+
+</details>
+<details>
+  <summary>API Example</summary>
+
+```python
+import dataspec
+
+spec_builder = dataspec.spec_builder()
+
+spec_builder.range_spec(key="zero_to_ten", data=[0, 10, 0.5])
+spec_builder.add_field(key="range_shorthand1:range", spec={"data": [0, 10, 0.5]})
+spec_builder.add_field(key="range_shorthand2:range", spec=[0, 10, 0.5])
+
+spec = spec_builder.build()
 ```
+
 </details>
 
 Example: Multiple Ranges One Field
@@ -823,37 +1907,51 @@ Example: Multiple Ranges One Field
 {
   "salaries": {
     "type": "range",
-    "data": [
-      [1000, 10000, 1000],
-      [10000, 55000, 5000],
-      [55000, 155000, 10000]
-    ]
+    "data": [[1000, 10000, 1000], [10000, 55000, 5000], [55000, 155000, 10000]]
   }
 }
 ```
-</details>
 
+</details>
 <details>
   <summary>YAML Spec</summary>
 
 ```yaml
----
 salaries:
   type: range
-  data: [
-    [1000, 10000, 1000],
-    [10000, 55000, 5000],
-    [55000, 155000, 10000]
-  ]
+  data: [[1000, 10000, 1000], [10000, 55000, 5000], [55000, 155000, 10000]]
+```
+
+</details>
+<details>
+  <summary>API Example</summary>
+
+```python
+import dataspec
+
+spec_builder = dataspec.spec_builder()
+
+spec_builder.range_spec(
+    key="salaries",
+    data=[
+      [1000, 10000, 1000],
+      [10000, 55000, 5000],
+      [55000, 155000, 10000]
+    ])
+
+spec = spec_builder.build()
 ```
+
 </details>
 
-This spec produces integer values for three different ranges each with different step sizes.
+This spec produces integer values for three different ranges each with different
+step sizes.
 
 ## <a name="RandRange"></a>Random Range
 
-A `rand_range` spec is used to generate a number with in a range. Use the `cast` param to explicitly cast the value to
-one of int, float, or string. The default is to return value as a string.
+A `rand_range` spec is used to generate a number with in a range. Use the `cast`
+param to explicitly cast the value to one of int, float, or string. The default
+is to return value as a string.
 
 The range Field Spec structure is:
 
@@ -870,7 +1968,8 @@ The range Field Spec structure is:
 }
 ```
 
-If a single element is provided in the `data` array, it will be used as the upper bound and 0 will be the lower.
+If a single element is provided in the `data` array, it will be used as the
+upper bound and 0 will be the lower.
 
 ### Config Params
 
@@ -881,8 +1980,9 @@ If a single element is provided in the `data` array, it will be used as the uppe
 
 Example:
 
-Two different population fields. The first generates an integer uniformly between 100 and 1000. The second generates a
-float between 200.2 and 1222.7 with two values after the decimal place. Note the abbreviation for cast.
+In this example we have two different population fields. The first generates an
+integer uniformly between 100 and 1000. The second generates a float between
+200.2 and 1222.7 with two values after the decimal place.
 
 <details open>
   <summary>JSON Spec</summary>
@@ -892,18 +1992,19 @@ float between 200.2 and 1222.7 with two values after the decimal place. Note the
   "population": {
     "type": "rand_range",
     "data": [100, 1000],
-    "config": {"cast": "int"}
+    "config": {
+      "cast": "int"
+    }
   },
   "pop:rand_range?cast=f": [200.2, 1222.7, 2]
 }
 ```
-</details>
 
+</details>
 <details>
   <summary>YAML Spec</summary>
 
 ```yaml
----
 population:
   type: rand_range
   data: [100, 1000]
@@ -911,6 +2012,39 @@ population:
     cast: int
 pop:rand_range?cast=f: [200.2, 1222.7, 2]
 ```
+
+</details>
+<details>
+  <summary>API Example</summary>
+
+```python
+import dataspec
+
+spec_builder = dataspec.spec_builder()
+
+spec_builder.rand_range(
+    key="population",
+    data=[100, 1000],
+    cast="int")
+spec_builder.add_field("pop:rand_range?cast=f", [200.2, 1222.7, 2])
+
+spec = spec_builder.build()
+```
+
+</details>
+
+<details open>
+  <summary>Example Command and Output</summary>
+
+```shell
+dataspec -s dataspec.json --log-level error -i 5  --format json -x
+{"population": 828, "pop": 630.87}
+{"population": 339, "pop": 361.01}
+{"population": 254, "pop": 549.29}
+{"population": 509, "pop": 261.89}
+{"population": 980, "pop": 594.94}
+```
+
 </details>
 
 ## <a name="Uuid"></a>Uuid
@@ -940,24 +2074,40 @@ Example Spec
   "id_shorthand:uuid": {}
 }
 ```
-</details>
 
+</details>
 <details>
   <summary>YAML Spec</summary>
 
 ```yaml
----
 id:
   type: uuid
 id_shorthand:uuid: {}
 ```
+
+</details>
+<details>
+  <summary>API Example</summary>
+
+```python
+import dataspec
+
+spec_builder = dataspec.spec_builder()
+
+spec_builder.uuid(key="id")
+spec_builder.add_field("id_shorthand:uuid", {})
+
+spec = spec_builder.build()
+```
+
 </details>
 
 ## <a name="CharClass"></a>Character Classes
 
-A `char_class` type is used to create strings that are made up of characters from specific character classes. The strings
-can be of fixed or variable length. There are several built in character classes. You can also provide your own set of
-characters to sample from. Below is the list of supported character classes:
+A `char_class` type is used to create strings that are made up of characters
+from specific character classes. The strings can be of fixed or variable length.
+There are several built in character classes. You can also provide your own set
+of characters to sample from. Below is the list of supported character classes:
 
 ### <a name="SupportedClasses"></a>Built In Classes
 
@@ -973,15 +2123,29 @@ characters to sample from. Below is the list of supported character classes:
 |visible    |All printable ascii chars excluding whitespace|
 |punctuation|local specific punctuation|
 |special    |local specific punctuation|
-|hex        |Hexidecimal digits including upper and lower case a-f|
-|hex-lower  |Hexidecimal digits only including lower case a-f|
-|hex-upper  |Hexidecimal digits only including upper case A-F|
+|hex        |Hexadecimal digits including upper and lower case a-f|
+|hex-lower  |Hexadecimal digits only including lower case a-f|
+|hex-upper  |Hexadecimal digits only including upper case A-F|
 
 Helpful Links:
 
-  * https://en.wikipedia.org/wiki/ASCII#Character_groups
-  * https://www.cs.cmu.edu/~pattis/15-1XX/common/handouts/ascii.html
-  * https://docs.python.org/3/library/string.html
+* https://en.wikipedia.org/wiki/ASCII#Character_groups
+* https://www.cs.cmu.edu/~pattis/15-1XX/common/handouts/ascii.html
+* https://docs.python.org/3/library/string.html
+
+### Parameters
+
+<details>
+
+<summary>Parameter Details</summary>
+
+param | type | description                                  | default | examples
+------|------|----------------------------------------------|---------|--------- 
+min|integer |minimum number of characters in string |None |1</br>7</br>2255</br> 
+max|integer |maximum number of characters in string |None |1</br>7</br>2255</br> 
+mean|number |mean number of characters in string |None |3</br>5</br>7.5</br> 
+stddev|number |standard deviation from mean for number</br>of characters in string |None |0.5</br>3</br>7</br>
+</details>
 
 ### Usage
 
@@ -999,7 +2163,7 @@ A `char_class` Field Spec takes the form
     or
     "data": <string with custom set of characters to sample from>
     or
-    "data": [<char_class_name1>, <char_class_name2>, ..., <custom characters>, ...]
+    "data": [<char_class_name1>, <char_class_name2>, ..., <custom characters>]
     # configuration
     "config":{
       # General Parameters
@@ -1021,9 +2185,10 @@ A `char_class` Field Spec takes the form
 
 ### Shorthand Notation for Single Character Classes
 
-If a single character class is needed, the type can be specified with a `cc-` prefix: `cc-<char_class_name>`
-e.g. `"type": "cc-visible"` would only use characters from the `visible` class. If this format is used, the `data`
-element is ignored and only characters from the single character class are sampled from.
+If a single character class is needed, the type can be specified with a `cc-`
+prefix: `cc-<char_class_name>` e.g. `"type": "cc-visible"` would only use
+characters from the `visible` class. If this format is used, the `data` element
+is ignored and only characters from the single character class are sampled from.
 
 <details open>
   <summary>JSON Spec</summary>
@@ -1033,20 +2198,36 @@ element is ignored and only characters from the single character class are sampl
   "one_to_five_digits:cc-digits?min=1&max=5": {}
 }
 ```
-</details>
 
+</details>
 <details>
   <summary>YAML Spec</summary>
 
 ```yaml
 one_to_five_digits:cc-digits?min=1&max=5: {}
 ```
+
+</details>
+<details>
+  <summary>API Example</summary>
+
+```python
+import dataspec
+
+spec_builder = dataspec.spec_builder()
+
+spec_builder.add_field("one_to_five_digits:cc-digits?min=1&max=5", {})
+
+spec = spec_builder.build()
+```
+
 </details>
 
 ### Examples
 
-Below is an example selecting several character classes along with a set of custom ones to use to generate passwords.
-The generated passwords are between 10 and 18 characters in length with a mean size of 14 characters and a standard
+Below is an example selecting several character classes along with a set of
+custom ones to use to generate passwords. The generated passwords are between 10
+and 18 characters in length with a mean size of 14 characters and a standard
 deviation of 2.
 
 <details open>
@@ -1056,63 +2237,89 @@ deviation of 2.
 {
   "password": {
     "type": "char_class",
-    "data": [
-      "word",
-      "special",
-      "hex-lower",
-      "M4$p3c!@l$@uc3"
-    ],
+    "data": ["word", "special", "hex-lower", "M4$p3c!@l$@uc3"],
     "config": {
       "mean": 14,
       "stddev": 2,
       "min": 10,
       "max": 18,
-      "exclude": ["'", "\""]
+      "exclude": [
+        "-",
+        "\""
+      ]
     }
   }
 }
 ```
-</details>
 
+</details>
 <details>
   <summary>YAML Spec</summary>
 
 ```yaml
----
 password:
   type: char_class
-  data:
-  - word
-  - special
-  - hex-lower
-  - M4$p3c!@l$@uc3
+  data: [word, special, hex-lower, M4$p3c!@l$@uc3]
   config:
     mean: 14
     stddev: 2
     min: 10
     max: 18
-    exclude: ['''', '"']
+    exclude:
+    - '-'
+    - '"'
+```
+
+</details>
+<details>
+  <summary>API Example</summary>
+
+```python
+import dataspec
+
+spec_builder = dataspec.spec_builder()
+
+spec_builder.char_class(
+    key="password",
+    data=[
+      "word",
+      "special",
+      "hex-lower",
+      "M4$p3c!@l$@uc3"
+    ],
+    mean=14,
+    stddev=2,
+    min=10,
+    max=18,
+    exclude=["-", "\""])
+
+spec = spec_builder.build()
 ```
+
 </details>
 
-If we run this example:
+
+<details>
+  <summary>Example Command and Output</summary>
 
 ```shell
-dataspec -s password.json -i 10
-61Nl=U35LVY^*
-pe576Z*P198&f4t
-1p}=HA9b?iJDc
-7$C])[o<BDh&W9kz
-F%iGot-j~eUfpT3qR
-(TM9b$o$A4KG*{&
-{WPm1;]O:w@y27l\
-JeTmpZ@zYN2Ms3,
-96i@FPA$^Q#%(|7Cba
-Q=+6D*8I36R%<n`$8
+dataspec -s dataspec.json --log-level error -i 10
+c3cFwpv!7c>(
+@qf`4;3yF7d#DM
+;'&5]$8pu3_7,E?
+|$aULq73cJY
+hb2cM4Zl2pPMnX_
+NV3TDhFsyQ)|4c
+pd01u|859!p)iT
+i7$8F93x+3uKG
+d8hb@_SfHaP!
+,ps]`Sbw;k<3o[
 ```
 
-The `stddev` config parameters is not required, but without it the sizes will tend to stack on the edges of the allowed
-size range.
+</details>
+
+The `stddev` config parameters is not required, but without it the sizes will
+tend to stack on the edges of the allowed size range.
 
 <details>
   <summary>Detailed Example</summary>
@@ -1154,11 +2361,14 @@ done | sort | uniq -c | sort -n -k2,2
 
 ## <a name="UnicodeRange"></a>Unicode Ranges
 
-The `unicode_range` type is similar to the `char_class` type, but it is used to generate characters from valid unicode
-ranges. See [UnicodeRanges](https://www.ling.upenn.edu/courses/Spring_2003/ling538/UnicodeRanges.html) for a list of the
-different valid ranges. One or more ranges can be specified in the `data` element by providing a list or list of lists
-with two elements each specifying the start and end hex code points. If we wanted to generate Japanese Hiragana (Code
-points 0x3040 to 0x30FF) characters as one of our fields we could use the following spec:
+The `unicode_range` type is similar to the `char_class` type, but it is used to
+generate characters from valid unicode ranges.
+See [UnicodeRanges](https://www.ling.upenn.edu/courses/Spring_2003/ling538/UnicodeRanges.html)
+for a list of the different valid ranges. One or more ranges can be specified in
+the `data` element by providing a list or list of lists with two elements each
+specifying the start and end hex code points. If we wanted to generate Japanese
+Hiragana (Code points 0x3040 to 0x30FF) characters as one of our fields we could
+use the following spec:
 
 <details open>
   <summary>JSON Spec</summary>
@@ -1174,37 +2384,55 @@ points 0x3040 to 0x30FF) characters as one of our fields we could use the follow
   }
 }
 ```
-</details>
 
+</details>
 <details>
   <summary>YAML Spec</summary>
 
 ```yaml
 text:
   type: unicode_range
-  data: ['3040', '309f']
+  data: ['3040', 309f]
   config:
     mean: 5
+```
+
+</details>
+<details>
+  <summary>API Example</summary>
+
+```python
+import dataspec
+
+spec_builder = dataspec.spec_builder()
 
+spec_builder.unicode_range("text", ["3040", "309f"], mean=5)
+
+spec = spec_builder.build()
 ```
+
 </details>
 
-If we run this example:
+
+<details>
+  <summary>Example Command and Output</summary>
 
 ```shell
-dataspec -s unicode.yaml -i 10 -l off
-そぬをく
-が
-としぶ
-ぉけべ゙
-ゃれげえ
-っぴ
-つざめよがぽ
-えろぱづゖごそ぀べかっ
-い゘
-ぱまる
+dataspec -s dataspec.json --log-level error -i 10
+じ
+んじ
+むぬ
+でど゗
+あぉごそはぶふ
+ぬりよゖび
+ん゜れゆひつど
+ぁそゝどぞおしろ
+ぇゃぴけ
+めき゚
 ```
 
+</details>
+
 ### Usage
 
 A `unicode_range` Field Spec takes the form
@@ -1242,34 +2470,37 @@ A `unicode_range` Field Spec takes the form
 
 ## <a name="Geo"></a>Geo Related Types
 
-There are three main geo types: `geo.lat`, `geo.long`, and `geo.pair`. The defaults will create decimal string values in
-the valid ranges: -90 to 90 for latitude and -180 to 180 for longitude. You can bound the ranges in several ways. The
-first is with the `start_lat`, `end_lat`, `start_long`, `end_long` config params. These will set the individual bounds
-for each of the segments. You can use one or more of them. The other mechanism is by defining a `bbox` array which
-consists of the lower left geo point and the upper right one.
+There are three main geo types: `geo.lat`, `geo.long`, and `geo.pair`. The
+defaults will create decimal string values in the valid ranges: -90 to 90 for
+latitude and -180 to 180 for longitude. You can bound the ranges in several
+ways. The first is with the `start_lat`, `end_lat`, `start_long`, `end_long`
+config params. These will set the individual bounds for each of the segments.
+You can use one or more of them. The other mechanism is by defining a `bbox`
+array which consists of the lower left geo point and the upper right one.
 See: [Bounding_Box](https://wiki.openstreetmap.org/wiki/Bounding_Box#)
 
 Config Params:
 
-|type    |param     |description                                  |
-|--------|----------|---------------------------------------------|
-|all     |precision |number of decimal places for lat or long, default is 4          |
-|        |bbox      |array of \[min Longitude, min Latitude, max Longitude, max Latitude\]|
-|geo.lat |start_lat |lower bound for latitude                                        |
-|        |end_lat   |upper bound for latitude                                        |
-|geo.long|start_long|lower bound for longitude                                       |
-|        |end_long  |upper bound for longitude                                       |
-|geo.pair|join_with |delimiter to join long and lat with, default is comma           |
-|        |as_list   |One of yes, true, or on if the pair should be returned as a list instead of as a joined string|
-|        |lat_first |if latitude should be first in the generated pair, default is longitude first|
-|        |start_lat |lower bound for latitude                                        |
-|        |end_lat   |upper bound for latitude                                        |
-|        |start_long|lower bound for longitude                                       |
-|        |end_long  |upper bound for longitude                                       |
+|type    |param     |description
+|--------|----------|---------------------------------------------
+|all     |precision |number of decimal places for lat or long, default is 4
+|        |bbox      |array of \[min Longitude, min Latitude, max Longitude,</br> max Latitude\]
+|geo.lat |start_lat |lower bound for latitude
+|        |end_lat   |upper bound for latitude
+|geo.long|start_long|lower bound for longitude
+|        |end_long  |upper bound for longitude
+|geo.pair|join_with |delimiter to join long and lat with, default is comma
+|        |as_list   |One of yes, true, or on if the pair should be returned</br> as a list instead of as a joined string|
+|        |lat_first |if latitude should be first in the generated pair,</br> default is longitude first|
+|        |start_lat |lower bound for latitude
+|        |end_lat   |upper bound for latitude
+|        |start_long|lower bound for longitude
+|        |end_long  |upper bound for longitude
 
 Examples:
 
-Generates a `longitude,latitude` pair with in the bounding box defining Egypt with 3 decimal points of precision.
+Generates a `longitude,latitude` pair with in the bounding box defining Egypt
+with 3 decimal points of precision.
 
 <details open>
   <summary>JSON Spec</summary>
@@ -1277,23 +2508,27 @@ Generates a `longitude,latitude` pair with in the bounding box defining Egypt wi
 ```json
 {
   "egypt": {
-    "type": "geo.point",
+    "type": "geo.pair",
     "config": {
-      "bbox": [31.33134, 22.03795, 34.19295, 25.00562],
+      "bbox": [
+        31.33134,
+        22.03795,
+        34.19295,
+        25.00562
+      ],
       "precision": 3
     }
   }
 }
 ```
-</details>
 
+</details>
 <details>
   <summary>YAML Spec</summary>
 
 ```yaml
----
 egypt:
-  type: geo.point
+  type: geo.pair
   config:
     bbox:
     - 31.33134
@@ -1302,12 +2537,44 @@ egypt:
     - 25.00562
     precision: 3
 ```
+
+</details>
+<details>
+  <summary>API Example</summary>
+
+```python
+import dataspec
+
+spec_builder = dataspec.spec_builder()
+
+spec_builder.geo_pair("egypt", bbox=[31.33134, 22.03795, 34.19295, 25.00562], precision=3)
+
+spec = spec_builder.build()
+```
+
 </details>
 
 ## <a name="IP_Addresses"></a>IP Addresses
 
-Ip addresses can be generated using [CIDR notation](https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing) or by
-specifying a base.
+Ip addresses can be generated
+using [CIDR notation](https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing)
+or by specifying a base. For example, if you wanted to generate ips in the
+10.0.0.0 to 10.0.0.255 range, you could either specify a `cidr` param of
+10.0.0.0/24 or a `base` param of 10.0.0.
+
+### Parameters
+
+<details>
+
+<summary>Parameter Details</summary>
+
+param | type | description                                  | default | examples
+------|------|----------------------------------------------|---------|--------- 
+base|string |base of ip address | |192</br>10.</br>100.100</br>192.168.</br>10.10.10</br> 
+cidr|string |cidr notation i.e. 192.168.0.0/16, only</br>/8 /16 /24 supported | |192.168.0.0/24</br>10.0.0.0/16</br>100.0.0.0/8</br>
+</details>
+
+### Usage
 
 The ipv4 Field Spec structure is:
 
@@ -1341,13 +2608,12 @@ Example Spec:
   "network_with_base:ip?base=192.168.0": {}
 }
 ```
-</details>
 
+</details>
 <details>
   <summary>YAML Spec</summary>
 
 ```yaml
----
 network:
   type: ipv4
   config:
@@ -1355,55 +2621,109 @@ network:
 network_shorthand:ip?cidr=2.22.222.0/16: {}
 network_with_base:ip?base=192.168.0: {}
 ```
+
+</details>
+<details>
+  <summary>API Example</summary>
+
+```python
+import dataspec
+
+spec_builder = dataspec.spec_builder()
+
+spec_builder.ipv4(key="network", cidr="2.22.222.0/16")
+spec_builder.add_field("network_shorthand:ip?cidr=2.22.222.0/16", {})
+spec_builder.add_field("network_with_base:ip?base=192.168.0", {})
+
+spec = spec_builder.build()
+```
+
 </details>
 
 ### <a name="Precise_IP"></a> Precise CIDR Addresses
 
-The default ip type only supports cidr masks of /8 /16 and /24. If you want more precise ip ranges you need to use the
-`ip.precise` type. This type requires a cidr as the single config param. For some cidr values the number of generated
-ips becomes large, and the underlying module used becomes preventatively slow. Even with a /16 address it can take
-multiple seconds to generate only 1000 ips. Anything smaller than that may not be worth it. The default mode
-for `ip.precise` is to increment the ip addresses. Set config param `sample` to on of `true`, `on`, or `yes` to enable
-random ip addresses selected from the generated ranges.
+The default ip type only supports cidr masks of /8 /16 and /24. If you want more
+precise ip ranges you need to use the `ip.precise` type. This type requires a
+cidr as the single config param. For some cidr values the number of generated
+ips becomes large, and the underlying module used becomes preventatively slow.
+Even with a /16 address it can take multiple seconds to generate only 1000 ips.
+Anything smaller than that may not be worth it. The default mode
+for `ip.precise` is to increment the ip addresses. Set config param `sample` to
+on of `true`, `on`, or `yes` to enable random ip addresses selected from the
+generated ranges.
 
 #### Examples
 
-Ips in the 10.n.n.n range, extremely slow, this is around 16 Million unique ip addresses
+Ips in the 10.n.n.n range, extremely slow, this is around 16 Million unique ip
+addresses
 
 <details open>
   <summary>JSON Spec</summary>
 
 ```json
-{"network:ip.precise?cidr=10.0.0.0/8":{}}
+{
+  "network:ip.precise?cidr=10.0.0.0/8": {}
+}
 ```
-</details>
 
+</details>
 <details>
   <summary>YAML Spec</summary>
 
 ```yaml
----
 network:ip.precise?cidr=10.0.0.0/8: {}
 ```
+
+</details>
+<details>
+  <summary>API Example</summary>
+
+```python
+import dataspec
+
+spec_builder = dataspec.spec_builder()
+
+spec_builder.add_field("network:ip.precise?cidr=10.0.0.0/8", {})
+
+spec = spec_builder.build()
+```
+
 </details>
 
-Ips in the 192.168.0.0 to 192.171.255.255 range, relatively slow, creates around 250K addresses
+Ips in the 192.168.0.0 to 192.171.255.255 range, relatively slow, creates around
+250K addresses
 
 <details open>
   <summary>JSON Spec</summary>
 
 ```json
-{"network:ip.precise?cidr=192.168.0.0/14&sample=true": {}}
+{
+  "network:ip.precise?cidr=192.168.0.0/14&sample=true": {}
+}
 ```
-</details>
 
+</details>
 <details>
   <summary>YAML Spec</summary>
 
 ```yaml
----
 network:ip.precise?cidr=192.168.0.0/14&sample=true: {}
 ```
+
+</details>
+<details>
+  <summary>API Example</summary>
+
+```python
+import dataspec
+
+spec_builder = dataspec.spec_builder()
+
+spec_builder.add_field("network:ip.precise?cidr=192.168.0.0/14&sample=true", {})
+
+spec = spec_builder.build()
+```
+
 </details>
 
 Ips in the 2.22.220.0 to 2.22.223.255 range, speed is tolerable
@@ -1412,22 +2732,39 @@ Ips in the 2.22.220.0 to 2.22.223.255 range, speed is tolerable
   <summary>JSON Spec</summary>
 
 ```json
-{"network:ip.precise?cidr=2.22.222.0/22": {}}
+{
+  "network:ip.precise?cidr=2.22.0.0/22": {}
+}
 ```
-</details>
 
+</details>
 <details>
   <summary>YAML Spec</summary>
 
 ```yaml
----
-network:ip.precise?cidr=2.22.222.0/22: {}
+network:ip.precise?cidr=2.22.0.0/22: {}
+```
+
+</details>
+<details>
+  <summary>API Example</summary>
+
+```python
+import dataspec
+
+spec_builder = dataspec.spec_builder()
+
+spec_builder.add_field("network:ip.precise?cidr=2.22.0.0/22", {})
+
+spec = spec_builder.build()
 ```
+
 </details>
 
 ## <a name="Weighted_Ref"></a>Weighted Ref
 
-A weighted ref spec is used to select the values from a set of refs in a weighted fashion.
+A weighted ref spec is used to select the values from a set of refs in a
+weighted fashion.
 
 The weightedref Field Spec structure is:
 
@@ -1440,8 +2777,8 @@ The weightedref Field Spec structure is:
 }
 ```
 
-For example if we want to generate a set of HTTP response codes, but we want mostly success related codes we could use
-the follow spec.
+For example if we want to generate a set of HTTP response codes, but we want
+mostly success related codes we could use the follow spec.
 
 <details open>
   <summary>JSON Spec</summary>
@@ -1450,46 +2787,70 @@ the follow spec.
 {
   "http_code": {
     "type": "weightedref",
-    "data": {
-      "GOOD_CODES": 0.7,
-      "BAD_CODES": 0.3
-    }
+    "data": {"GOOD_CODES": 0.7, "BAD_CODES": 0.3}
   },
   "refs": {
-    "GOOD_CODES": {"200": 0.5, "202": 0.3, "203": 0.1, "300": 0.1},
-    "BAD_CODES": {"400": 0.5, "403": 0.3, "404": 0.1, "500": 0.1}
+    "GOOD_CODES": {
+      "200": 0.5,
+      "202": 0.3,
+      "203": 0.1,
+      "300": 0.1
+    },
+    "BAD_CODES": {
+      "400": 0.5,
+      "403": 0.3,
+      "404": 0.1,
+      "500": 0.1
+    }
   }
 }
 ```
-</details>
 
+</details>
 <details>
   <summary>YAML Spec</summary>
 
 ```yaml
----
 http_code:
   type: weightedref
-  data:
-    GOOD_CODES: 0.7
-    BAD_CODES: 0.3
+  data: {GOOD_CODES: 0.7, BAD_CODES: 0.3}
 refs:
-  BAD_CODES:
-    '400': 0.5
-    '403': 0.3
-    '404': 0.1
-    '500': 0.1
   GOOD_CODES:
     '200': 0.5
     '202': 0.3
     '203': 0.1
     '300': 0.1
+  BAD_CODES:
+    '400': 0.5
+    '403': 0.3
+    '404': 0.1
+    '500': 0.1
+```
+
+</details>
+<details>
+  <summary>API Example</summary>
+
+```python
+import dataspec
+
+spec_builder = dataspec.spec_builder()
+
+refs = spec_builder.refs()
+refs.add_field('GOOD_CODES', {"200": 0.5, "202": 0.3, "203": 0.1, "300": 0.1})
+refs.add_field('BAD_CODES', {"400": 0.5, "403": 0.3, "404": 0.1, "500": 0.1})
+
+spec_builder.weightedref('http_code', data={"GOOD_CODES": 0.7, "BAD_CODES": 0.3})
+
+spec = spec_builder.build()
 ```
+
 </details>
 
 ## <a name="Select_List_Subset"></a>Select List Subset
 
-A select list subset spec is used to select multiple values from a list to use as the value for a field.
+A select list subset spec is used to select multiple values from a list to use
+as the value for a field.
 
 The select_list_subset Field Spec structure is:
 
@@ -1502,6 +2863,8 @@ The select_list_subset Field Spec structure is:
       "stddev": N,
       "min": N,
       "max": N,
+      or
+      "count": N,
       "join_with": "<delimiter to join with>"
     },
     "data": ["data", "to", "select", "from"],
@@ -1511,10 +2874,12 @@ The select_list_subset Field Spec structure is:
 }
 ```
 
-The join_with config option is used to specify how the selected values should be combined. The mean and stddev config
-options tell how many items should be chosen. For example a mean of 2 and stddev of 1, would mostly choose 2 items then
-sometimes 1 or 3 or more. Set the stddev to 0 if only the exact number of items should be chosen.
-You can also set a min and max. Example:
+The join_with config option is used to specify how the selected values should be
+combined. The mean and stddev config options tell how many items should be
+chosen. For example a mean of 2 and stddev of 1, would mostly choose 2 items
+then sometimes 1 or 3 or more. There are two ways to produce an exact number of
+elements.  The first is to use the `count` param by itself.  The second is to set
+the stddev to 0. You can also set a min and max. Example:
 
 <details open>
   <summary>JSON Spec</summary>
@@ -1523,62 +2888,79 @@ You can also set a min and max. Example:
 {
   "ingredients": {
     "type": "select_list_subset",
+    "data": ["onions", "mushrooms", "garlic", "bell peppers", "spinach", "potatoes", "carrots"],
     "config": {
       "mean": 3,
       "stddev": 1,
       "min": 2,
       "max": 4,
       "join_with": ", "
-    },
-    "data": ["onions", "mushrooms", "garlic", "bell peppers", "spinach", "potatoes", "carrots"]
+    }
   }
 }
 ```
-</details>
 
+</details>
 <details>
   <summary>YAML Spec</summary>
 
 ```yaml
----
 ingredients:
+  type: select_list_subset
+  data: [onions, mushrooms, garlic, bell peppers, spinach, potatoes, carrots]
   config:
-    join_with: ', '
-    max: 4
     mean: 3
-    min: 2
     stddev: 1
-  data:
-  - onions
-  - mushrooms
-  - garlic
-  - bell peppers
-  - spinach
-  - potatoes
-  - carrots
-  type: select_list_subset
+    min: 2
+    max: 4
+    join_with: ', '
+```
+
+</details>
+<details>
+  <summary>API Example</summary>
+
+```python
+import dataspec
+
+spec_builder = dataspec.spec_builder()
+
+spec_builder.select_list_subset(
+    key="ingredients",
+    data=["onions", "mushrooms", "garlic", "bell peppers", "spinach", "potatoes", "carrots"],
+    mean=3,
+    stddev=1,
+    min=2,
+    max=4,
+    join_with=", ")
+
+spec = spec_builder.build()
 ```
+
 </details>
 
-```shell script
-dataspec -s ~/scratch/ingredients.json -i 10
+
+<details>
+  <summary>Example Command and Output</summary>
+
+```shell
+dataspec -s dataspec.json --log-level error -i 5
+mushrooms, garlic
+carrots, potatoes
 garlic, onions
-garlic, spinach
-bell peppers, spinach
-mushrooms, bell peppers, carrots, potatoes
-mushrooms, potatoes, bell peppers
-potatoes, onions, garlic, bell peppers
-potatoes, bell peppers, onions, garlic
-spinach, bell peppers
-spinach, onions, garlic
-carrots, garlic, mushrooms, potatoes
+carrots, potatoes, mushrooms
+garlic, bell peppers, mushrooms
 ```
 
+</details>
+
 ### <a name='quoting_sublist'></a> Quoting Sublist Elements
 
-The default `quote` parameter will only quote the whole combined list of elements. To quote each individual element of
-the sublist you need to use a special form of `join_with` along with the `quote` param. For example if we wanted all of
-our ingredients surrounded with double quotes. We would update our spec this way.
+The default `quote` parameter will only quote the whole combined list of
+elements. To quote each individual element of the sublist you need to use a
+special form of `join_with` along with the `quote` param. For example if we
+wanted all of our ingredients surrounded with double quotes. We would update our
+spec this way.
 
 <details open>
   <summary>JSON Spec</summary>
@@ -1587,67 +2969,87 @@ our ingredients surrounded with double quotes. We would update our spec this way
 {
   "ingredients": {
     "type": "select_list_subset",
+    "data": ["onions", "mushrooms", "garlic", "bell peppers", "spinach", "potatoes", "carrots"],
     "config": {
-      "mean": 3, "stddev": 1, "min": 2, "max": 4,
+      "mean": 3,
+      "stddev": 1,
+      "min": 2,
+      "max": 4,
       "join_with": "\", \"",
       "quote": "\""
-    },
-    "data": ["onions", "mushrooms", "garlic", "bell peppers", "spinach", "potatoes", "carrots"]
+    }
   }
 }
 ```
-</details>
 
+</details>
 <details>
   <summary>YAML Spec</summary>
 
 ```yaml
----
 ingredients:
   type: select_list_subset
+  data: [onions, mushrooms, garlic, bell peppers, spinach, potatoes, carrots]
   config:
-    join_with: '", "'
     mean: 3
     stddev: 1
     min: 2
     max: 4
+    join_with: '", "'
     quote: '"'
-  data:
-  - onions
-  - mushrooms
-  - garlic
-  - bell peppers
-  - spinach
-  - potatoes
-  - carrots
 ```
+
+</details>
+<details>
+  <summary>API Example</summary>
+
+```python
+import dataspec
+
+spec_builder = dataspec.spec_builder()
+
+spec_builder.select_list_subset(
+    key="ingredients",
+    data=["onions", "mushrooms", "garlic", "bell peppers", "spinach", "potatoes", "carrots"],
+    mean=3,
+    stddev=1,
+    min=2,
+    max=4,
+    join_with="\", \"",
+    quote="\"")
+
+spec = spec_builder.build()
+```
+
 </details>
 
-Now when we run our datespec we get:
 
-```shell script
-dataspec -s ~/scratch/quoted_ingredients.json -i 10
-"spinach", "mushrooms", "bell peppers", "onions"
-"spinach", "onions", "mushrooms", "garlic"
-"carrots", "garlic", "mushrooms", "onions"
+<details>
+  <summary>Example Command and Output</summary>
+
+```shell
+dataspec -s dataspec.json --log-level error -i 5
+"onions", "bell peppers"
+"carrots", "spinach"
 "mushrooms", "bell peppers", "carrots"
-"carrots", "potatoes", "bell peppers", "onions"
-"spinach", "mushrooms"
-"mushrooms", "bell peppers", "onions"
-"potatoes", "carrots", "bell peppers", "spinach"
-"garlic", "mushrooms", "potatoes"
-"carrots", "spinach", "bell peppers", "potatoes"
+"bell peppers", "garlic"
+"potatoes", "spinach"
 ```
 
+</details>
+
 ## <a name='CSV_Data'></a> CSV Data
 
-If you have an existing large set of data in a tabular format that you want to use, it would be burdensome to copy and
-paste the data into a spec. To make use of data already in a tabular format you can use a `csv` Field Spec. These specs
-allow you to identify a column from a tabular data file to use to provide the values for a field. Another advantage of
-using a csv spec is that it is easy to have fields that are correlated be generated together. All rows will be selected
-incrementally, unless any of the fields are configured to use `sample` mode. You can use `sample` mode on individual
-columns, or you can use it across all columns by creating a `configref` spec. See [csv_select](#csv_select) for an efficient way
-to select multiple columns from a csv file.
+If you have an existing large set of data in a tabular format that you want to
+use, it would be burdensome to copy and paste the data into a spec. To make use
+of data already in a tabular format you can use a `csv` Field Spec. These specs
+allow you to identify a column from a tabular data file to use to provide the
+values for a field. Another advantage of using a csv spec is that it is easy to
+have fields that are correlated be generated together. All rows will be selected
+incrementally, unless any of the fields are configured to use `sample` mode. You
+can use `sample` mode on individual columns, or you can use it across all
+columns by creating a `configref` spec. See [csv_select](#csv_select) for an
+efficient way to select multiple columns from a csv file.
 
 The `csv` Field Spec structure is:
 
@@ -1674,7 +3076,7 @@ The `csv` Field Spec structure is:
 |-----------|---------|--------|-----------|
 |datafile   |no       |data.csv|filename in datandir to use|
 |headers    |no       |false   |yes, on, true for affirmative|
-|column     |no       |1       |1 based column number or field name if headers are present|
+|column     |no       |1       |1 based column number or field name if headers</br> are present|
 |delimiter  |no       |,       |how values are separated|
 |quotechar  |no       |"       |how values are quoted, default is double quote|
 |sample     |no       |False   |If the values should be selected at random|
@@ -1684,10 +3086,11 @@ The `csv` Field Spec structure is:
 
 ##### Single Field
 
-The simplest example is a file with a single field that contains the values to generate for a field. For example if we
-have a known list of cities, we can put this in a file and reference it from our spec. The advantage of this approach is
-that it is easy to add new data points and to use small sets of data for testing by creating directories that have
-smaller input files.
+The simplest example is a file with a single field that contains the values to
+generate for a field. For example if we have a known list of cities, we can put
+this in a file and reference it from our spec. The advantage of this approach is
+that it is easy to add new data points and to use small sets of data for testing
+by creating directories that have smaller input files.
 
 <details open>
   <summary>JSON Spec</summary>
@@ -1704,13 +3107,12 @@ smaller input files.
   }
 }
 ```
-</details>
 
+</details>
 <details>
   <summary>YAML Spec</summary>
 
 ```yaml
----
 cities:
   type: csv
   config:
@@ -1718,6 +3120,25 @@ cities:
     delimiter: '~'
     sample: true
 ```
+
+</details>
+<details>
+  <summary>API Example</summary>
+
+```python
+import dataspec
+
+spec_builder = dataspec.spec_builder()
+
+spec_builder.csv(
+    key="cities",
+    datafile="cities.csv",
+    delimiter="~",
+    sample=True)
+
+spec = spec_builder.build()
+```
+
 </details>
 
 ```shell
@@ -1729,12 +3150,13 @@ Chicage
 London
 ```
 
-Note that if your data might have commas in it (the default delimiter), you should specify a delimiter that will not be
-found in your data.
+Note that if your data might have commas in it (the default delimiter), you
+should specify a delimiter that will not be found in your data.
 
 ##### Multiple Fields Non Comma Separated
 
-In this example we have a tab delimited file with multiple columns that we want to use.
+In this example we have a tab delimited file with multiple columns that we want
+to use.
 
 ```
 status	status_description	status_type
@@ -1753,65 +3175,91 @@ Our Data Spec looks like:
 
 ```json
 {
-    "status": {
-        "type": "csv",
-        "config": {
-            "configref": "tabs_config",
-            "column": 1
-        }
-    },
-    "description": {
-        "type": "csv",
-        "config": {
-            "configref": "tabs_config",
-            "column": 2
-        }
-    },
-    "status_type:csv?configref=tabs_config&column=3": {},
-    "refs": {
-        "tabs_config": {
-            "type": "configref",
-            "config": {
-                "datafile": "tabs.csv",
-                "delimiter": "\\t",
-                "headers": true
-            }
-        }
+  "status": {
+    "type": "csv",
+    "config": {
+      "column": 1,
+      "configref": "tabs_config"
+    }
+  },
+  "description": {
+    "type": "csv",
+    "config": {
+      "column": 2,
+      "configref": "tabs_config"
+    }
+  },
+  "status_type:csv?configref=tabs_config&column=3": {},
+  "refs": {
+    "tabs_config": {
+      "type": "configref",
+      "config": {
+        "datafile": "tabs.csv",
+        "delimiter": "\t",
+        "headers": true
+      }
     }
+  }
 }
 ```
-</details>
 
+</details>
 <details>
   <summary>YAML Spec</summary>
 
 ```yaml
----
 status:
   type: csv
   config:
-    configref: tabs_config
     column: 1
+    configref: tabs_config
 description:
   type: csv
   config:
-    configref: tabs_config
     column: 2
-# shorthand notation
-status_type:csv?configref=tabs_config&column=3: { }
+    configref: tabs_config
+status_type:csv?configref=tabs_config&column=3: {}
 refs:
   tabs_config:
     type: configref
     config:
       datafile: tabs.csv
-      delimiter: '\t'
+      delimiter: "\t"
       headers: true
 ```
+
+</details>
+<details>
+  <summary>API Example</summary>
+
+```python
+import dataspec
+
+spec_builder = dataspec.spec_builder()
+
+spec_builder.configref(
+    key="tabs_config",
+    datafile="tabs.csv",
+    delimiter="\t",
+    headers=True)
+spec_builder.csv(
+    key="status",
+    column=1,
+    configref="tabs_config")
+spec_builder.csv(
+    key="description",
+    column=2,
+    configref="tabs_config")
+spec_builder.add_field("status_type:csv?configref=tabs_config&column=3", {})
+
+spec = spec_builder.build()
+```
+
 </details>
 
-The `configref` exist so that we don't have to repeat ourselves for common configurations across multiple fields. If we
-use the following template `{{ status }},{{ description }},{{ status_type }}` and run this spec we will get output
-similar to:
+The `configref` exist so that we don't have to repeat ourselves for common
+configurations across multiple fields. If we use the following template `{{ status }},{{ description }},{{ status_type }}` and run this
+spec we will get output similar to:
 
 ```shell
 dataspec --spec tabs.yaml --datadir ./data -t template.jinja -i 5
@@ -1824,11 +3272,14 @@ dataspec --spec tabs.yaml --datadir ./data -t template.jinja -i 5
 
 ## <a name="CSV_Select"></a>CSV Select
 
-A common process is to select subsets of the columns from a csv file to use. The `csv_select` type makes this more
-efficient than using the standard `csv` type. Below is an example that will Convert data from
-the [Geonames](http://www.geonames.org/) [allCountries.zip](http://download.geonames.org/export/dump/allCountries.zip)
-dataset by selecting a subset of the columns from the tab delimited file. The key in the data element is the new name
-for the field. The value can either be the 1 indexed column number or the name of the field if the data has `headers`.
+A common process is to select subsets of the columns from a csv file to use.
+The `csv_select` type makes this more efficient than using the standard `csv`
+type. Below is an example that will Convert data from the
+[Geonames](http://www.geonames.org/)
+[allCountries.zip](http://download.geonames.org/export/dump/allCountries.zip)
+dataset by selecting a subset of the columns from the tab delimited file. The
+key in the data element is the new name for the field. The value can either be
+the 1 indexed column number, or the name of the field if the data has `headers`.
 Our example doesn't have headers, so we are using the 1 based indexes.
 
 <details open>
@@ -1836,53 +3287,68 @@ Our example doesn't have headers, so we are using the 1 based indexes.
 
 ```json
 {
-    "placeholder": {
-        "type": "csv_select",
-        "data": {
-            "geonameid": 1,
-            "name": 2,
-            "latitude": 5,
-            "longitude": 6,
-            "country_code": 9,
-            "population": 15
-        },
-        "config": {
-            "datafile": "allCountries.txt",
-            "headers": false,
-            "delimiter": "\t"
-        }
+  "placeholder": {
+    "type": "csv_select",
+    "data": {"geonameid": 1, "name": 2, "latitude": 5, "longitude": 6, "country_code": 9, "population": 15},
+    "config": {
+      "datafile": "allCountries.txt",
+      "headers": false,
+      "delimiter": "\t"
     }
+  }
 }
 ```
-</details>
 
+</details>
 <details>
   <summary>YAML Spec</summary>
 
 ```yaml
----
 placeholder:
   type: csv_select
-  data:
-    geonameid: 1
-    name: 2
-    latitude: 5
-    longitude: 6
-    country_code: 9
-    population: 15
+  data: {geonameid: 1, name: 2, latitude: 5, longitude: 6, country_code: 9, population: 15}
   config:
     datafile: allCountries.txt
-    headers: no
+    headers: false
     delimiter: "\t"
 ```
+
+</details>
+<details>
+  <summary>API Example</summary>
+
+```python
+import dataspec
+
+spec_builder = dataspec.spec_builder()
+
+spec_builder.csv_select(
+    key="placeholder",
+    data={
+        "geonameid": 1,
+        "name": 2,
+        "latitude": 5,
+        "longitude": 6,
+        "country_code": 9,
+        "population": 15
+    },
+    datafile="allCountries.txt",
+    headers=False,
+    delimiter="	")
+
+spec = spec_builder.build()
+```
+
 </details>
 
 ## <a name="nested"></a>Nested Fields
 
-Nested types are used to create fields that contain subfields. Nested types can also contain nested fields to allow
-multiple levels of nesting. Use the `nested` type to generate a field that contains subfields. The subfields are
-defined in the `fields` element of the nested spec. The `fields` element will be treated like a top level dataspec
-and has access to the `refs` and other elements of the root.
+Nested types are used to create fields that contain subfields. Nested types can
+also contain nested fields to allow multiple levels of nesting. Use the `nested`
+type to generate a field that contains subfields. The subfields are defined in
+the `fields` element of the nested spec. The `fields` element will be treated
+like a top level dataspec and has access to the `refs` and other elements of the
+root.
 
 The `nested` Field Spec structure is:
 
@@ -1913,14 +3379,18 @@ Below is an example of the data we wish to generate:
     "user_id": "bad135dad987",
     "geo": {
       "place_id": 12345,
-      "coordinates": [118.2, 34.0]
+      "coordinates": [
+        118.2,
+        34.0
+      ]
     }
   }
 }
 ```
 
-The `user` is a nested object, which has a subfield `geo`, which is also a nested object. The `id` and `user_id` fields
-are uuids. The coordinates field is a list of longitude followed by latitude. Below are the specs that will generate
+The `user` is a nested object, which has a subfield `geo`, which is also a
+nested object. The `id` and `user_id` fields are uuids. The coordinates field is
+a list of longitude followed by latitude. Below are the specs that will generate
 data that matches this schema.
 
 <details open>
@@ -1928,11 +3398,17 @@ data that matches this schema.
 
 ```json
 {
-  "id:uuid": {},
-  "user:nested": {
+  "id": {
+    "type": "uuid"
+  },
+  "user": {
+    "type": "nested",
     "fields": {
-      "user_id:uuid": {},
-      "geo:nested": {
+      "user_id": {
+        "type": "uuid"
+      },
+      "geo": {
+        "type": "nested",
         "fields": {
           "place_id:cc-digits?mean=5": {},
           "coordinates:geo.pair?as_list=true": {}
@@ -1942,38 +3418,74 @@ data that matches this schema.
   }
 }
 ```
-</details>
 
+</details>
 <details>
   <summary>YAML Spec</summary>
 
 ```yaml
-id:uuid: {}
-user:nested:
+id:
+  type: uuid
+user:
+  type: nested
   fields:
-    user_id:uuid: {}
-    geo:nested:
+    user_id:
+      type: uuid
+    geo:
+      type: nested
       fields:
         place_id:cc-digits?mean=5: {}
         coordinates:geo.pair?as_list=true: {}
 ```
+
+</details>
+<details>
+  <summary>API Example</summary>
+
+```python
+import dataspec
+
+spec_builder = dataspec.spec_builder()
+
+geo_fields = dataspec.spec_builder()
+geo_fields.add_field("place_id:cc-digits?mean=5", {})
+geo_fields.add_field("coordinates:geo.pair?as_list=true", {})
+
+user_fields = dataspec.spec_builder()
+user_fields.uuid("user_id")
+user_fields.nested("geo", geo_fields.build())
+
+spec_builder.uuid("id")
+spec_builder.nested("user", user_fields.build())
+
+spec = spec_builder.build()
+```
+
 </details>
 
-If we run this example:
+
+<details>
+  <summary>Example Command and Output</summary>
 
 ```shell
-dataspec -s double-nested.json -i 1 --format json-pretty -x
+dataspec -s dataspec.json --log-level error -i 1
 {
-    "id": "4278b060-442d-4558-bf2c-5f1df68cb265",
+    "id": "327658cd-b3de-477a-a902-742efd03ef89",
     "user": {
+        "user_id": "c14c7709-d0f6-4cd8-b9ff-a936960ca63f",
         "geo": {
+            "place_id": "59283706",
             "coordinates": [
-                "-167.4324",
-                " 84.6883"
-            ],
-            "place_id": "42018569"
-        },
-        "user_id": "13d5c2a6-80c8-4bdb-89b2-7da9699cd0fb"
+                "-142.8146",
+                " 66.3702"
+            ]
+        }
+    },
+    "_internal": {
+        "_iteration": 0,
+        "_field_group": "ALL"
     }
-} 
-```
\ No newline at end of file
+}
+```
+
+</details>
\ No newline at end of file
diff --git a/docs/SCHEMAS.md b/docs/SCHEMAS.md
index 2e8510f..c39a298 100644
--- a/docs/SCHEMAS.md
+++ b/docs/SCHEMAS.md
@@ -262,7 +262,7 @@ Types covered by schema: `date, date.iso, date.iso.us`
           "description": "Valid date format string",
           "type": "string"
         },
-        "delta_days": {
+        "duration_days": {
           "oneOf": [
             {"type": "integer"},
             {
@@ -273,7 +273,7 @@ Types covered by schema: `date, date.iso, date.iso.us`
             }
           ]
         },
-        "anchor": {"type": "string"},
+        "start": {"type": "string"},
         "offset": {"type": "integer"}
       }
     },
diff --git a/docs/templates/FIELDSPECS.jinja.md b/docs/templates/FIELDSPECS.jinja.md
index d466c66..6149035 100644
--- a/docs/templates/FIELDSPECS.jinja.md
+++ b/docs/templates/FIELDSPECS.jinja.md
@@ -1,17 +1,25 @@
 Field Spec Definitions
 ========================
-{% macro show_example(example) -%}
-{% if example.json is defined  -%}
+{% macro show_example(example, expand_json=True) -%}
+{% if example.description is defined -%}
+**{{ example.description | wordwrap(78) }}**
+
+{% endif -%}
+{% if example.json is defined -%}
+{% if expand_json -%}
 <details open>
+{%- else %}
+<details>
+{%- endif %}
   <summary>JSON Spec</summary>
 
 ```json
 {{ example.json }}
 ```
+
 </details>
 {%- endif %}
-
-{% if example.yaml is defined  -%}
+{% if example.yaml is defined -%}
 
 <details>
   <summary>YAML Spec</summary>
@@ -19,17 +27,73 @@ Field Spec Definitions
 ```yaml
 {{ example.yaml }}
 ```
+
 </details>
 
 {%- endif %}
-{%- endmacro %}
-{% macro show_command_and_output(example) -%}
-{% if example.command is defined  -%}
+{% if example.api is defined -%}
+
+<details>
+  <summary>API Example</summary>
+
+```python
+{{example.api}}
+```
+
+</details>
+
+{%- endif %} 
+{%- endmacro %} 
+{% macro show_command_and_output(example, expand=False) -%}
+{% if example.command is defined -%}
+{% if expand -%}
+<details open>
+{%- else %}
+<details>
+{%- endif %}
+  <summary>Example Command and Output</summary>
+
 ```shell
 {{ example.command }}
 {{ example.output }}
 ```
+
+</details>
+
+{%- endif %} 
+{%- endmacro %} 
+{% macro format_examples(def) -%}
+{% if def.examples is defined -%} 
+{% for example in def.examples -%} 
+{{ example }}</br>
+{%- endfor %}
+{%- endif %}
+{%- endmacro %} 
+{% macro format_description(def) -%}
+{% if def.description is defined -%}
+{{ def.description |wordwrap(40)|replace('\n','</br>') }}
 {%- endif %}
+{%- endmacro %} 
+{% macro show_params(schema, definitions) -%}
+
+### Parameters
+
+<details>
+
+<summary>Parameter Details</summary>
+
+param | type | description                                  | default | examples
+------|------|----------------------------------------------|---------|---------
+
+{%- for param, def in schema.properties.config.properties.items() if param not in ['prefix', 'suffix', 'quote', 'count']%} 
+{%- if param in definitions %} 
+{{ param }}|{{definitions[param].type}} |{{ format_description(definitions[param]) }}|{{definitions[param].default }} |{{ format_examples(definitions[param]) }}
+{%- else %} 
+{{ param }}|{{def.type}} |{{ format_description(def) }} |{{ def.default }} |{{ format_examples(def) }}
+{%- endif %} 
+{%- endfor %}
+</details>
+
 {%- endmacro %}
 1. [Quick Reference](#Quick_Reference)
 1. [Overview](#Overview)
@@ -37,7 +101,7 @@ Field Spec Definitions
     1. [The Full Format](#The_full_format.)
     1. [Values Shorthand](#Values_Shorthand)
     1. [Inline Key Type Shorthand](#Inline_Key_Type_Shorthand)
-    1. [Inline Key Config Shorthad](#Inline_Key_Config_Shorthad)
+    1. [Inline Key Config Shorthand](#Inline_Key_Config_Shorthad)
 1. [Spec Configuration](#Spec_Configuration)
     1. [Common Configurations](#Common_Configurations)
     1. [Count Config Parameter](#CountsField)
@@ -48,8 +112,10 @@ Field Spec Definitions
         1. [Weighted Values](#Weighted_Values)
         1. [Sample Mode](#Sample_Mode)
     1. [Combine](#Combine)
+    1. [Combine List](#CombineList)
     1. [Date](#Date)
     1. [Range](#Range)
+    1. [Random Range](#RandRange)
     1. [Uuid](#Uuid)
     1. [Character Class](#CharClass)
        1. [Built In Classes](#SupportedClasses)
@@ -66,144 +132,138 @@ Field Spec Definitions
 
 # <a name="Quick_Reference"></a>Quick Reference
 
-| type                        | description                            | config params                |
-|-----------------------------|----------------------------------------|------------------------------|
-|[values](#Values)            | constant, list, or weighted dictionary |                              |
-|[range](#Range)              | range of values                        |                              |
-|[rand_range](#RandRange)     | random value in a range                |                              |
-|[combine](#Combine)          | refs or fields                         | join_with                    |
-|[combine-list](#CombineList) | list of lists of refs to combine       | join_with                    |
-|[date](#Date)                | date strings                           | many see details below       |
-|[date.iso](#Date)            | date strings in ISO8601 format no microseconds| many see details below|
-|[date.iso.us](#Date)         | date strings in ISO8601 format w/ microseconds| many see details below|
-|[uuid](#Uuid)                | generates valid uuid                   |                              |
-|[char_class](#CharClass)     | generates strings from character classes| many see details below      |
-|[unicode_range](#UnicodeRange)| generates strings from unicode ranges | many see details below       |
-|[geo.lat](#Geo)              | generates decimal latitude             | start_lat,end_lat,precision  |
-|[geo.long](#Geo)             | generates decimal longitude            | start_long,end_long,precision|
-|[geo.pair](#Geo)             | generates long,lat pair                | join_with,start_lat,end_lat,start_long,end_long,precision|
-|[ip/ipv4](#IP_Addresses)     | generates ip v4 addresses              | base, cidr /8,/16,/24 only   |
-|[ip.precise](#IP_Addresses)  | generates ip v4 addresses              | cidr(required) i.e. 192.168.1.0/14 |
-|[weightedref](#Weighted_Ref) | produces values from refs in weighted fashion |                       |
-|[select_list_subset](#Select_List_Subset) | selects subset of fields that are combined to create the value for the field | join_with |
-|[csv](#CSV_Data)             | Uses external csv file to supply data  | many see details below       |
-|[csv_select](#CSV_Select)    | Efficient way to select multiple csv columns | many see details below |
-|[nested](#Nested)            | For nested fields                      |                              |
+type                         | description                            | config params
+-----------------------------|----------------------------------------|------------------------------
+[values](#Values)            | constant, list, or weighted dictionary |
+[range](#Range)              | range of values                        |
+[rand_range](#RandRange)     | random value in a range                |
+[combine](#Combine)          | refs or fields                         | join_with
+[combine-list](#CombineList) | list of lists of refs to combine       | join_with
+[date](#Date)                | date strings                           | many see details below
+[date.iso](#Date)            | date strings in ISO8601 format no microseconds| many see details below
+[date.iso.us](#Date)         | date strings in ISO8601 format w/ microseconds| many see details below
+[uuid](#Uuid)                | generates valid uuid                   |
+[char_class](#CharClass)     | generates strings from character classes| many see details below
+[unicode_range](#UnicodeRange)| generates strings from unicode ranges | many see details below
+[geo.lat](#Geo)              | generates decimal latitude             | start_lat,end_lat,precision
+[geo.long](#Geo)             | generates decimal longitude            | start_long,end_long,precision
+[geo.pair](#Geo)             | generates long,lat pair                | join_with,start_lat,end_lat,</br>start_long,end_long,precision
+[ip/ipv4](#IP_Addresses)     | generates ip v4 addresses              | base, cidr /8,/16,/24 only
+[ip.precise](#IP_Addresses)  | generates ip v4 addresses              | cidr(required) i.e. 192.168.1.0/14
+[weightedref](#Weighted_Ref) | produces values from refs in weighted fashion |
+[select_list_subset](#Select_List_Subset) | selects subset of fields that are</br> combined to create the value for the field | join_with
+[csv](#CSV_Data)             | Uses external csv file to supply data  | many see details below
+[csv_select](#CSV_Select)    | Efficient way to select multiple csv columns | many see details below
+[nested](#Nested)            | For nested fields                      |
+
 # <a name="Overview"></a>Overview
 
-Each field that should be generated needs a specification that describes the way the values for it should be created. We
-refer to this as a Field Spec. The simplest type of Field Spec is a values spec. The main format of a values spec is a
-list of values to use. By default, these values are rotated through incrementally. If the number of increments is larger
-than the number of values in the list, the values start over from the beginning of the list. When combining values from
-two values providers that are lists, they will be combined in incrementing order. i.e:
+Each field that should be generated needs a specification that describes the way
+the values for it should be created. We refer to this as a Field Spec. The
+simplest type of Field Spec is a values spec. The main format of a values spec
+is a list of values to use. By default, these values are rotated through
+incrementally. If the number of increments is larger than the number of values
+in the list, the values start over from the beginning of the list. When
+combining values from two values providers that are lists, they will be combined
+in incrementing order. For example, the spec below will produce the values A1,
+B2, C3 continuously.
 
 {{ show_example(examples.overview_example_one) }}
-Will produce the values A1, B2, C3 continuously.
 
-```shell script
-dataspec -s ~/scratch/sample.json -i 7
-A1
-B2
-C3
-A1
-B2
-C3
-A1
-```
+{{ show_command_and_output(examples.overview_example_one, True) }}
 
 If an additional number is added to TWO, we now get 12 distinct values:
 
 {{ show_example(examples.overview_example_two) }}
 
-```shell script
-dataspec -s ~/scratch/sample.json -i 12 | sort
-A1
-A2
-A3
-A4
-B1
-B2
-B3
-B4
-C1
-C2
-C3
-C4
-```
+{{ show_command_and_output(examples.overview_example_two, True) }}
 
-If we want our values to be generated randomly from the provided lists, we set the config param `sample` to true:
+If we want our values to be generated randomly from the provided lists, we set
+the config param `sample` to true:
 
 {{ show_example(examples.overview_example_three) }}
 
 # <a name="Field_Spec_Structure"></a>Field Spec Structure
 
-There are several different ways to define a spec. There is the full spec format and a variety of short hand notations.
+There are several ways to define a Field Spec. There is the full spec format,
+and a variety of short hand notations.
 
 ## <a name="The_full_format."></a>The Full Format.
 
-The only required element is type. Each Type Handler requires different pieces of information. See the Field Type
-reference below for details on each type.
+The only required element is type. Each Type Handler requires different pieces
+of information. See the Field Type reference below for details on each type.
+Below is the general structure.
 
 ```
 {
   "type": "<the type>",
   "config": {
     "key1": "value1",
-    ...
+    ...</br>
     "keyN": "valueN"
   },
   "data": ["the data"],
   "ref": "REF_POINTER_IF_USED",
-  "refs": ["USES", "MORE", "THAN", "ONE"]
+  "refs": ["USES", "MORE", "THAN", "ONE"],
+  "fields": { "for": {}, "nested": {}, "types": {} }
 }
 ```
 
 ## <a name="Values_Shorthand"></a>Values Shorthand
 
-The values type is very common and so has a shorthand notation. Below is an example full Field Spec for some values
-types fields and the same spec in shorthand notation.
+The values type is very common and so has a shorthand notation. Below is an
+example full Field Spec for some values types fields and the same spec in
+shorthand notation.
 
 {{ show_example(examples.values_shorthand_one) }}
 
+Shorthand Format:
+
 {{ show_example(examples.values_shorthand_two) }}
 
-The value after the field name is just the value of the data element from the full Field Spec. Config params can be
-added to the key using the URL syntax described below.
+The value after the field name is just the value of the data element from the
+full Field Spec. Config params can be added to the key using the URL syntax
+described below.
 
 ## <a name="Inline_Key_Type_Shorthand"></a>Inline Key Type Shorthand
 
-Some specs lend themselves to being easily specified with few parameters. One short hand way to do this is the use a
-colon in the key to specify the type after the field name. For example `{"id:uuid":{}}`. This says the field `id` is of
-type `uuid` and has no further configuration. If no type is specified, the field is assumed to be a `values` type.
+Some specs lend themselves to being easily specified with few parameters. One
+short hand way to do this is the use a colon in the key to specify the type
+after the field name. For example `{"id:uuid":{}}`. This says the field `id` is
+of type `uuid` and has no further configuration. If no type is specified, the
+field is assumed to be a `values` type.
 
-## <a name="Inline_Key_Config_Shorthad"></a>Inline Key Config Shorthad
+## <a name="Inline_Key_Config_Shorthad"></a>Inline Key Config Shorthand
 
-It is also possible to specify configuration parameters in the key by using URL style parameters. For example.
+It is also possible to specify configuration parameters in the key by using URL
+style parameters. For example.
 
 {{ show_example(examples.inline_key_example) }}
 
-The `network` field is of type `ipv4` and the required `cidr` param is specified in the key.
+The `network` field is of type `ipv4` and the required `cidr` param is specified
+in the key.
 
 # <a name="Spec_Configuration"></a>Spec Configuration
 
-There are two ways to configure a spec. One is by providing a `config` element in the Field Spec and the other is by
-using a URL parameter format in the key. For example, the following two fields will produce the same values:
+There are two ways to configure a spec. One is by providing a `config` element
+in the Field Spec and the other is by using a URL parameter format in the key.
+For example, the following two fields will produce the same values:
 
 {{ show_example(examples.config_example_one) }}
 
 ## <a name="Common_Configurations"></a>Common Configurations
 
-There are some configuration values that can be applied to all or a subset of types. These are listed below
-
-| key   | argument  |effect |
-|-------|-----------|-------|
-|prefix | string    |Prepends the value to all results |
-|suffix | string    |Appends the value to all results  |
-|quote  | string    |Wraps the resulting value on both sides with the provided string |
-|cast   | i,int,f,float,s,str,string|For numeric types, will cast results the provided type|
-|join_with|string   |For types that produce multiple values, use this string to join them   |
-|as_list|yes,true,on|For types that produce multiple values, return as list without joining |
+There are some configuration values that can be applied to all or a subset of
+types. These are listed below
 
+key      | argument  |effect 
+---------|-----------|-------
+prefix   | string    |Prepends the value to all results 
+suffix   | string    |Appends the value to all results  
+quote    | string    |Wraps the resulting value on both sides with the</br> provided string 
+cast     | i,int,f,float,s,str,string|For numeric types, will cast results</br> the provided type
+join_with|string     |For types that produce multiple values, use this</br> string to join them   
+as_list  |yes,true,on|For types that produce multiple values, return as</br> list without joining 
 
 Example:
 
@@ -211,21 +271,48 @@ Example:
 
 ## <a name="CountsField"></a>Count Config Parameter
 
-Several types support a `count` config parameter. The value of the count parameter can be any of the supported values
-specs formats. For example a constant `3`, list `[2, 3, 7]`, or weighted map `{"1": 0.5, "2": 0.3, "3": 0.2 }`. This
-will produce the number of values by creating a value supplier for the count based on the supplied parameter. Most of
-the time if the count is greater that 1, the values will be returned as an array. Some types support joining the values
-by specifying the `join_with` parameter. Some types will let you explicitly set the `as_array` parameter to force the
-results to be returned as an array and not the default for the given type.
+Several types support a `count` config parameter. The value of the count
+parameter can be any of the supported values specs formats. For example a
+constant `3`, list `[2, 3, 7]`, or weighted
+map `{"1": 0.5, "2": 0.3, "3": 0.2 }`. This will produce the number of values by
+creating a value supplier for the count based on the supplied parameter. Most of
+the time if the count is greater that 1, the values will be returned as an
+array. Some types support joining the values by specifying the `join_with`
+parameter. Some types will let you explicitly set the `as_list` parameter to
+force the results to be returned as an array and not the default for the given
+type.
+
+### Count Distributions
+
+Another way to specify a count is to use a count distribution. This is done with
+the `count_dist` param.  The param takes a string argument which is the
+distribution along with its required arguments in function call form with
+parameters explicitly named.  See the table below.
+
+distribution|required arguments|optional args|examples
+------------|------------------|-------------|---
+uniform     |start</br>end     |             |"uniform(start=10, end=30)"
+</i>        |                  |             |"uniform(start=1, end=3)"
+guass       |mean</br>stddev   |min</br>max  |"gauss(mean=2, stddev=1)"
+guassian    |                  |             |"guassian(mean=7, stddev=1, min=4)"
+normal      |                  |             |"normal(mean=25, stddev=10, max=40)"
+
+`normal`, `guassian`, and `gauss` are all aliases for a
+[Normal Distribution](https://en.wikipedia.org/wiki/Normal_distribution).
+
+Example:
+
+{{ show_example(examples.count_dist_example_one) }}
 
 # <a name="Field_Spec_Types"></a>Field Spec Types
 
-These are the built in types
+These are the built-in types
 
 ## <a name="Values"></a>Values
 
-There are three types of values specs: Constants, List, and Weighted. Values specs have a shorthand notation where the
-value of the data element replaces the full spec. See examples below.
+There are three types of values specs: Constants, List, and Weighted. Values
+specs have a shorthand notation where the value of the data element replaces the
+full spec. See examples below.
 
 ### <a name="Constant_Values"></a>Constant Values
 
@@ -235,9 +322,10 @@ A Constant Value is just a single value that is used in every iteration
 
 ### <a name="List_Values"></a>List Values
 
-List values are rotated through in order. If the number of iterations is larger than the size of the list, we start over
-from the beginning of the list. Use the `sample` config param to specify that the values should be selected at random
-from the provided list.
+List values are rotated through in order. If the number of iterations is larger
+than the size of the list, we start over from the beginning of the list. Use
+the `sample` config param to specify that the values should be selected at
+random from the provided list.
 
 {{ show_example(examples.list_values_example_one) }}
 
@@ -247,23 +335,33 @@ Weighted values are generated according to their weights.
 
 {{ show_example(examples.weighted_values_example_one) }}
 
-The example above will generate 200 40% of the time and 400 and 403 5%. The higher the number of iterations the more
-likely the values will match their specified weights.
+The example above will generate 200 40% of the time and 400 and 403 5%. The
+higher the number of iterations the more likely the values will match their
+specified weights.
 
 ### <a name="Sample_Mode"></a>Sample Mode
 
-To increase the randomness of the data being generated you can configure a FieldSpec that contains a list of values to
-be sampled instead of iterated through incrementally. Normally the spec below would create the repeating
-sequence: `A1 B2 C3`, but since both fields `ONE` and `TWO` are in sample mode, we will get all nine combinations of
-values after a significant number of iterations. This would also be true if only one was set to sample mode. To turn
-sample mode on either use a URL param or config entry with one of `on`,  `yes`, or `true`. NOTE: Sample mode is only
-valid with entries that are lists.
+To increase the randomness of the data being generated, you can configure a
+FieldSpec that contains a list of values to be sampled instead of iterated
+through incrementally. Normally the spec below would create the repeating
+sequence: `A1 B2 C3`, but since both fields `ONE` and `TWO` are in sample mode,
+we will get all nine combinations of values after a significant number of
+iterations. This would also be true if only one was set to sample mode. To turn
+sample mode on either use a URL param or config entry with one of `on`,  `yes`,
+or `true`. NOTE: Sample mode is only valid with entries that are lists.
 
 {{ show_example(examples.sample_mode_example_one) }}
 
+#### Sample All
+
+If running from the command line, you cas specify the `--sample-lists` flag to
+make all list backed data to have sampling turned on by default. If using the
+python API, do `dataspec.types.set_default('sample_mode', True)`
+
 ## <a name="Combine"></a>Combine
 
-A combine Field Spec is used to concatenate or append two or more fields or reference to one another.
+A combine Field Spec is used to concatenate or append two or more fields or
+reference to one another.
 
 The combine Field Spec structure is:
 
@@ -274,7 +372,9 @@ The combine Field Spec structure is:
     "fields": ["valid field name1", "valid field name2"],
     OR
     "refs": ["valid ref1", "valid ref2"],
-    "config": {"join_with": "<optional string to use to join fields or refs, default is none>"}
+    "config": {
+      "join_with": "<optional string to use to join fields or refs, default is none>"
+    }
   }
 }
 ```
@@ -285,8 +385,9 @@ Example below uses the first and last refs to create a full name field.
 
 ## <a name="CombineList"></a>Combine List
 
-A combine-list Field Spec is used to specify a list of lists of refs to combine. This is useful if there are a lot of
-variations on the values that should be combined. This allows all the variations to be specified in one place. Note:
+A combine-list Field Spec is used to specify a list of lists of refs to combine.
+This is useful if there are a lot of variations on the values that should be
+combined. This allows all the variations to be specified in one place. Note:
 This approach requires the same join_with param for each set of refs.
 
 The combine Field Spec structure is:
@@ -311,24 +412,58 @@ This is a slight modification to the above combine Example.
 
 ## <a name="Date"></a>Date
 
-A Date Field Spec is used to generate date strings. The default format is day-month-year i.e. Christmas 2050 would be:
-25-12-2050. There is also a `date.iso` type that generates ISO8601 formatted date strings without microseconds and a
-`date.iso.us` for one that generates them with microseconds. We use
-the [format specification](https://docs.python.org/3/library/datetime.html#strftime-and-strptime-format-codes)
-from the datetime module. The default strategy is to create dates around a center date. The default is to use today with
-a spread of +-15 days. To make the base or anchor date the start or end of the date range, use the delta_days parameter
-with an array of two elements, where one is zero. If the first is zero then all generated dates will only be after the
-base/anchor date. If the second element is zero then all generated dates will be before the base/anchor date. There are
-a lot of configuration parameters for the date module. Each are described below.
+A Date Field Spec is used to generate date strings. The default format is
+day-month-year i.e. Christmas 2050 would be: 25-12-2050. There is also
+a `date.iso` type that generates ISO8601 formatted date strings without
+microseconds and a `date.iso.us` for one that generates them with microseconds.
+We use the
+[format specification](https://docs.python.org/3/library/datetime.html#strftime-and-strptime-format-codes)
+from the datetime module.
+
+### Uniformly Sampled Dates
+
+The default strategy is to create random dates within a 30 day range, where the
+start date is today. You can use the `start` parameter to set a specific start
+date for the dates. You can also explicitly specify an `end` date. The `start`
+and `end` parameters should conform to the specified date format, or the default
+if none is provided. The `offset` parameter can be used to shift the dates by a
+specified number of days. A positive `offset` will shift the start date back. A
+negative `offset` will shift the date forward. The `duration_days` parameter can
+be used to specify the number of days that should be covered in the date range,
+instead of the default 30 days. This parameter is usually specified as an
+integer constant.
 
-### Parameters
+```
+       start                              end (default start + 30 days)
+          |--------------------------------|
+  |+offset|                           start+duration_days
+  |--------------------------------|
+          |-offset|
+                  |--------------------------------|    
+```
+
+### Dates Distributed around a Center Point
+
+An alternative strategy is to specify a `center_date` parameter with an
+optional `stddev_days`. This will create a normal or gaussian distribution of
+dates around the center point.
+
+```  
+                   |
+                   |
+                |  |  |
+             |  |  |  |  |
+          |  |  |  |  |  |  |  
+ |  |  |  |  |  |  |  |  |  |  |  |  |
+|-------------------------------------|
+|         | stddev | stddev |         |
+                center
+```
 
-| param | description                                      | examples |
-|-------|--------------------------------------------------|----------|
-|format | datetime compatible format specification         | %Y-%m-%d, %m/%d/%Y, %H:%M:%S,... |
-|delta_days | The number of days +- from the base/anchor date to create date strings for | 1, 12, \[14, 0\] |
-|anchor | date string matching format or default format to use for base date | 22-02-2022 |
-|offset | number of days to shift base date by, positive means shift backwards, negative means forward | 30, -7, ...|
+There are a lot of configuration parameters for the date type. Each are
+described below.
+
+{{ show_params(schemas.date, definitions) }}
 
 The date Field Spec structure is:
 
@@ -347,30 +482,86 @@ The date Field Spec structure is:
 
 ### Examples
 
-To help with the number of variations of date formats, below is a table of examples. Assume today is 15 Jan 2050, so the
-default date formatted for today would be 15-01-2050
-
-|format  |delta_days|anchor     |offset|produces                 |spec|
-|--------|----------|-----------|------|-------------------------|----|
-|-       |-         |-          |-     |12-31-2049 ... 30-01-2050|`{"dates:date":{}}`|
-|-       |-         |-          |1     |12-30-2049 ... 29-01-2050|`{"dates:date?offset=1":{}}`|
-|-       |1         |-          |-     |14-01-2050 ... 16-01-2050|`{"dates:date?delta_days=1":{}}`|
-|-       |-1        |-          |-     |same as above            |`{"dates:date?delta_days=-1":{}}`|
-|-       |1         |-          |1     |13-01-2050 ... 15-01-2050|`{"dates:date?delta_days=1&offset=1":{}}`|
-|-       |1         |-          |-1    |15-01-2050 ... 17-01-2050|`{"dates:date?delta_days=1&offset=-1":{}}`|
-|-       |1         |15-12-2050 |1     |13-12-2050 ... 15-12-2050|`{"dates:date?delta_days=1&offset=1&anchor=15-12-2050":{}}`|
-|%d-%b-%Y|1         |15-Dec-2050|-     |14-Dec-2050 ... 16-Dec-2050|`{"dates:date?delta_days=1&anchor=15-Dec-2050&format=%d-%b-%Y":{}}`|
-|-       |\[1,2\]   |-          |-     |15-01-2050 ... 17-01-2050|`{"dates:date":{"config":{"delta_days":[0, 2]}}}`|
+To help with the number of variations of date formats, there are a number of
+examples below. They all assume today is 15 Jan 2050, so the default date
+formatted for today would be 15-01-2050. Click More Examples to see all
+examples.
+
+#### Uniform Dates Examples
+
+{{show_example(examples.uniform_date_example_exhaustive)}}
+
+{{show_command_and_output(examples.uniform_date_example_exhaustive, True)}}
+
+<details>
+<summary>More Examples</summary>
+
+{{show_example(examples.uniform_date_example_one, False)}}
+
+{{show_command_and_output(examples.uniform_date_example_one, True)}}
+
+{{show_example(examples.uniform_date_example_two, False)}}
+
+{{show_command_and_output(examples.uniform_date_example_two, True)}}
+
+{{show_example(examples.uniform_date_example_three, False)}}
+
+{{show_command_and_output(examples.uniform_date_example_three, True)}}
+
+{{show_example(examples.uniform_date_example_four, False)}}
+
+{{show_command_and_output(examples.uniform_date_example_four, True)}}
+
+{{show_example(examples.uniform_date_example_five, False)}}
+
+{{show_command_and_output(examples.uniform_date_example_five, True)}}
+
+{{show_example(examples.uniform_date_example_six, False)}}
+
+{{show_command_and_output(examples.uniform_date_example_six, True)}}
+
+{{show_example(examples.uniform_date_example_seven, False)}}
+
+{{show_command_and_output(examples.uniform_date_example_seven, True)}}
+
+{{show_example(examples.uniform_date_example_eight, False)}}
+
+{{show_command_and_output(examples.uniform_date_example_eight, True)}}
+
+</details>
+
+#### Centered Dates Examples
+
+{{show_example(examples.centered_date_example_exhaustive)}}
+
+{{show_command_and_output(examples.centered_date_example_exhaustive, True)}}
+
+<details>
+<summary>More Examples</summary>
+
+{{show_example(examples.centered_date_example_one, False)}}
+
+{{show_command_and_output(examples.centered_date_example_one, True)}}
+
+{{show_example(examples.centered_date_example_two, False)}}
+
+{{show_command_and_output(examples.centered_date_example_two, True)}}
+
+</details>
 
 ### ISO8601 formatted dates
 
-The type `date.iso` will produce a ISO8601 formatted date in the bounds configured without milliseconds. Use
-the `date.iso.us` type to generate them with microseconds.
+The type `date.iso` will produce a ISO8601 formatted date in the bounds
+configured without milliseconds. Use the `date.iso.us` type to generate them
+with microseconds.
 
 ## <a name="Range"></a>Range
 
-A `range` spec is used to generate a range of values. The ranges are inclusive for start and end. The start, stop, and
-step can be integers or floating point numbers.
+A `range` spec is used to generate a range of values. The ranges are inclusive
+for start and end. The start, stop, and step can be integers or floating-point
+numbers.
+
+{{ show_params(schemas.range, definitions) }}
 
 The range Field Spec structure is:
 
@@ -398,12 +589,14 @@ Example: Multiple Ranges One Field
 
 {{ show_example(examples.range_spec_example_two) }}
 
-This spec produces integer values for three different ranges each with different step sizes.
+This spec produces integer values for three different ranges each with different
+step sizes.
 
 ## <a name="RandRange"></a>Random Range
 
-A `rand_range` spec is used to generate a number with in a range. Use the `cast` param to explicitly cast the value to
-one of int, float, or string. The default is to return value as a string.
+A `rand_range` spec is used to generate a number with in a range. Use the `cast`
+param to explicitly cast the value to one of int, float, or string. The default
+is to return value as a string.
 
 The range Field Spec structure is:
 
@@ -420,7 +613,8 @@ The range Field Spec structure is:
 }
 ```
 
-If a single element is provided in the `data` array, it will be used as the upper bound and 0 will be the lower.
+If a single element is provided in the `data` array, it will be used as the
+upper bound and 0 will be the lower.
 
 ### Config Params
 
@@ -431,11 +625,14 @@ If a single element is provided in the `data` array, it will be used as the uppe
 
 Example:
 
-Two different population fields. The first generates an integer uniformly between 100 and 1000. The second generates a
-float between 200.2 and 1222.7 with two values after the decimal place. Note the abbreviation for cast.
+In this example we have two different population fields. The first generates an
+integer uniformly between 100 and 1000. The second generates a float between
+200.2 and 1222.7 with two values after the decimal place.
 
 {{ show_example(examples.rand_range_spec_example_one) }}
 
+{{show_command_and_output(examples.rand_range_spec_example_one, True)}}
+
 ## <a name="Uuid"></a>Uuid
 
 A standard uuid.
@@ -456,9 +653,10 @@ Example Spec
 
 ## <a name="CharClass"></a>Character Classes
 
-A `char_class` type is used to create strings that are made up of characters from specific character classes. The strings
-can be of fixed or variable length. There are several built in character classes. You can also provide your own set of
-characters to sample from. Below is the list of supported character classes:
+A `char_class` type is used to create strings that are made up of characters
+from specific character classes. The strings can be of fixed or variable length.
+There are several built in character classes. You can also provide your own set
+of characters to sample from. Below is the list of supported character classes:
 
 ### <a name="SupportedClasses"></a>Built In Classes
 
@@ -474,15 +672,17 @@ characters to sample from. Below is the list of supported character classes:
 |visible    |All printable ascii chars excluding whitespace|
 |punctuation|local specific punctuation|
 |special    |local specific punctuation|
-|hex        |Hexidecimal digits including upper and lower case a-f|
-|hex-lower  |Hexidecimal digits only including lower case a-f|
-|hex-upper  |Hexidecimal digits only including upper case A-F|
+|hex        |Hexadecimal digits including upper and lower case a-f|
+|hex-lower  |Hexadecimal digits only including lower case a-f|
+|hex-upper  |Hexadecimal digits only including upper case A-F|
 
 Helpful Links:
 
-  * https://en.wikipedia.org/wiki/ASCII#Character_groups
-  * https://www.cs.cmu.edu/~pattis/15-1XX/common/handouts/ascii.html
-  * https://docs.python.org/3/library/string.html
+* https://en.wikipedia.org/wiki/ASCII#Character_groups
+* https://www.cs.cmu.edu/~pattis/15-1XX/common/handouts/ascii.html
+* https://docs.python.org/3/library/string.html
+
+{{ show_params(schemas.char_class, definitions) }}
 
 ### Usage
 
@@ -500,7 +700,7 @@ A `char_class` Field Spec takes the form
     or
     "data": <string with custom set of characters to sample from>
     or
-    "data": [<char_class_name1>, <char_class_name2>, ..., <custom characters>, ...]
+    "data": [<char_class_name1>, <char_class_name2>, ..., <custom characters>]
     # configuration
     "config":{
       # General Parameters
@@ -522,26 +722,26 @@ A `char_class` Field Spec takes the form
 
 ### Shorthand Notation for Single Character Classes
 
-If a single character class is needed, the type can be specified with a `cc-` prefix: `cc-<char_class_name>`
-e.g. `"type": "cc-visible"` would only use characters from the `visible` class. If this format is used, the `data`
-element is ignored and only characters from the single character class are sampled from.
+If a single character class is needed, the type can be specified with a `cc-`
+prefix: `cc-<char_class_name>` e.g. `"type": "cc-visible"` would only use
+characters from the `visible` class. If this format is used, the `data` element
+is ignored and only characters from the single character class are sampled from.
 
 {{ show_example(examples.char_class_spec_example_one) }}
 
 ### Examples
 
-Below is an example selecting several character classes along with a set of custom ones to use to generate passwords.
-The generated passwords are between 10 and 18 characters in length with a mean size of 14 characters and a standard
+Below is an example selecting several character classes along with a set of
+custom ones to use to generate passwords. The generated passwords are between 10
+and 18 characters in length with a mean size of 14 characters and a standard
 deviation of 2.
 
 {{ show_example(examples.char_class_spec_example_two) }}
 
-If we run this example:
-
 {{ show_command_and_output(examples.char_class_spec_example_two) }}
 
-The `stddev` config parameters is not required, but without it the sizes will tend to stack on the edges of the allowed
-size range.
+The `stddev` config parameters is not required, but without it the sizes will
+tend to stack on the edges of the allowed size range.
 
 <details>
   <summary>Detailed Example</summary>
@@ -583,16 +783,17 @@ done | sort | uniq -c | sort -n -k2,2
 
 ## <a name="UnicodeRange"></a>Unicode Ranges
 
-The `unicode_range` type is similar to the `char_class` type, but it is used to generate characters from valid unicode
-ranges. See [UnicodeRanges](https://www.ling.upenn.edu/courses/Spring_2003/ling538/UnicodeRanges.html) for a list of the
-different valid ranges. One or more ranges can be specified in the `data` element by providing a list or list of lists
-with two elements each specifying the start and end hex code points. If we wanted to generate Japanese Hiragana (Code
-points 0x3040 to 0x30FF) characters as one of our fields we could use the following spec:
+The `unicode_range` type is similar to the `char_class` type, but it is used to
+generate characters from valid unicode ranges.
+See [UnicodeRanges](https://www.ling.upenn.edu/courses/Spring_2003/ling538/UnicodeRanges.html)
+for a list of the different valid ranges. One or more ranges can be specified in
+the `data` element by providing a list or list of lists with two elements each
+specifying the start and end hex code points. If we wanted to generate Japanese
+Hiragana (Code points 0x3040 to 0x30FF) characters as one of our fields we could
+use the following spec:
 
 {{ show_example(examples.unicode_range_example_one) }}
 
-If we run this example:
-
 {{ show_command_and_output(examples.unicode_range_example_one) }}
 
 ### Usage
@@ -632,41 +833,51 @@ A `unicode_range` Field Spec takes the form
 
 ## <a name="Geo"></a>Geo Related Types
 
-There are three main geo types: `geo.lat`, `geo.long`, and `geo.pair`. The defaults will create decimal string values in
-the valid ranges: -90 to 90 for latitude and -180 to 180 for longitude. You can bound the ranges in several ways. The
-first is with the `start_lat`, `end_lat`, `start_long`, `end_long` config params. These will set the individual bounds
-for each of the segments. You can use one or more of them. The other mechanism is by defining a `bbox` array which
-consists of the lower left geo point and the upper right one.
+There are three main geo types: `geo.lat`, `geo.long`, and `geo.pair`. The
+defaults will create decimal string values in the valid ranges: -90 to 90 for
+latitude and -180 to 180 for longitude. You can bound the ranges in several
+ways. The first is with the `start_lat`, `end_lat`, `start_long`, `end_long`
+config params. These will set the individual bounds for each of the segments.
+You can use one or more of them. The other mechanism is by defining a `bbox`
+array which consists of the lower left geo point and the upper right one.
 See: [Bounding_Box](https://wiki.openstreetmap.org/wiki/Bounding_Box#)
 
 Config Params:
 
-|type    |param     |description                                  |
-|--------|----------|---------------------------------------------|
-|all     |precision |number of decimal places for lat or long, default is 4          |
-|        |bbox      |array of \[min Longitude, min Latitude, max Longitude, max Latitude\]|
-|geo.lat |start_lat |lower bound for latitude                                        |
-|        |end_lat   |upper bound for latitude                                        |
-|geo.long|start_long|lower bound for longitude                                       |
-|        |end_long  |upper bound for longitude                                       |
-|geo.pair|join_with |delimiter to join long and lat with, default is comma           |
-|        |as_list   |One of yes, true, or on if the pair should be returned as a list instead of as a joined string|
-|        |lat_first |if latitude should be first in the generated pair, default is longitude first|
-|        |start_lat |lower bound for latitude                                        |
-|        |end_lat   |upper bound for latitude                                        |
-|        |start_long|lower bound for longitude                                       |
-|        |end_long  |upper bound for longitude                                       |
+|type    |param     |description
+|--------|----------|---------------------------------------------
+|all     |precision |number of decimal places for lat or long, default is 4
+|        |bbox      |array of \[min Longitude, min Latitude, max Longitude,</br> max Latitude\]
+|geo.lat |start_lat |lower bound for latitude
+|        |end_lat   |upper bound for latitude
+|geo.long|start_long|lower bound for longitude
+|        |end_long  |upper bound for longitude
+|geo.pair|join_with |delimiter to join long and lat with, default is comma
+|        |as_list   |One of yes, true, or on if the pair should be returned</br> as a list instead of as a joined string|
+|        |lat_first |if latitude should be first in the generated pair,</br> default is longitude first|
+|        |start_lat |lower bound for latitude
+|        |end_lat   |upper bound for latitude
+|        |start_long|lower bound for longitude
+|        |end_long  |upper bound for longitude
 
 Examples:
 
-Generates a `longitude,latitude` pair with in the bounding box defining Egypt with 3 decimal points of precision.
+Generates a `longitude,latitude` pair with in the bounding box defining Egypt
+with 3 decimal points of precision.
 
 {{ show_example(examples.geo_point_spec_example_one) }}
 
 ## <a name="IP_Addresses"></a>IP Addresses
 
-Ip addresses can be generated using [CIDR notation](https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing) or by
-specifying a base.
+Ip addresses can be generated
+using [CIDR notation](https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing)
+or by specifying a base. For example, if you wanted to generate ips in the
+10.0.0.0 to 10.0.0.255 range, you could either specify a `cidr` param of
+10.0.0.0/24 or a `base` param of 10.0.0.
+
+{{ show_params(schemas.ip, definitions) }}
+
+### Usage
 
 The ipv4 Field Spec structure is:
 
@@ -689,20 +900,25 @@ Example Spec:
 
 ### <a name="Precise_IP"></a> Precise CIDR Addresses
 
-The default ip type only supports cidr masks of /8 /16 and /24. If you want more precise ip ranges you need to use the
-`ip.precise` type. This type requires a cidr as the single config param. For some cidr values the number of generated
-ips becomes large, and the underlying module used becomes preventatively slow. Even with a /16 address it can take
-multiple seconds to generate only 1000 ips. Anything smaller than that may not be worth it. The default mode
-for `ip.precise` is to increment the ip addresses. Set config param `sample` to on of `true`, `on`, or `yes` to enable
-random ip addresses selected from the generated ranges.
+The default ip type only supports cidr masks of /8 /16 and /24. If you want more
+precise ip ranges you need to use the `ip.precise` type. This type requires a
+cidr as the single config param. For some cidr values the number of generated
+ips becomes large, and the underlying module used becomes preventatively slow.
+Even with a /16 address it can take multiple seconds to generate only 1000 ips.
+Anything smaller than that may not be worth it. The default mode
+for `ip.precise` is to increment the ip addresses. Set config param `sample` to
+on of `true`, `on`, or `yes` to enable random ip addresses selected from the
+generated ranges.
 
 #### Examples
 
-Ips in the 10.n.n.n range, extremely slow, this is around 16 Million unique ip addresses
+Ips in the 10.n.n.n range, extremely slow, this is around 16 Million unique ip
+addresses
 
 {{ show_example(examples.ip_precise_example_one) }}
 
-Ips in the 192.168.0.0 to 192.171.255.255 range, relatively slow, creates around 250K addresses
+Ips in the 192.168.0.0 to 192.171.255.255 range, relatively slow, creates around
+250K addresses
 
 {{ show_example(examples.ip_precise_example_two) }}
 
@@ -712,7 +928,8 @@ Ips in the 2.22.220.0 to 2.22.223.255 range, speed is tolerable
 
 ## <a name="Weighted_Ref"></a>Weighted Ref
 
-A weighted ref spec is used to select the values from a set of refs in a weighted fashion.
+A weighted ref spec is used to select the values from a set of refs in a
+weighted fashion.
 
 The weightedref Field Spec structure is:
 
@@ -725,14 +942,15 @@ The weightedref Field Spec structure is:
 }
 ```
 
-For example if we want to generate a set of HTTP response codes, but we want mostly success related codes we could use
-the follow spec.
+For example if we want to generate a set of HTTP response codes, but we want
+mostly success related codes we could use the follow spec.
 
 {{ show_example(examples.weighted_ref_example_one) }}
 
 ## <a name="Select_List_Subset"></a>Select List Subset
 
-A select list subset spec is used to select multiple values from a list to use as the value for a field.
+A select list subset spec is used to select multiple values from a list to use
+as the value for a field.
 
 The select_list_subset Field Spec structure is:
 
@@ -745,6 +963,8 @@ The select_list_subset Field Spec structure is:
       "stddev": N,
       "min": N,
       "max": N,
+      or
+      "count": N,
       "join_with": "<delimiter to join with>"
     },
     "data": ["data", "to", "select", "from"],
@@ -754,60 +974,41 @@ The select_list_subset Field Spec structure is:
 }
 ```
 
-The join_with config option is used to specify how the selected values should be combined. The mean and stddev config
-options tell how many items should be chosen. For example a mean of 2 and stddev of 1, would mostly choose 2 items then
-sometimes 1 or 3 or more. Set the stddev to 0 if only the exact number of items should be chosen.
-You can also set a min and max. Example:
+The join_with config option is used to specify how the selected values should be
+combined. The mean and stddev config options tell how many items should be
+chosen. For example a mean of 2 and stddev of 1, would mostly choose 2 items
+then sometimes 1 or 3 or more. There are two ways to produce an exact number of
+elements.  The first is to use the `count` param by itself.  The second is to set
+the stddev to 0. You can also set a min and max. Example:
 
 {{ show_example(examples.select_list_example_one) }}
 
-```shell script
-dataspec -s ~/scratch/ingredients.json -i 10
-garlic, onions
-garlic, spinach
-bell peppers, spinach
-mushrooms, bell peppers, carrots, potatoes
-mushrooms, potatoes, bell peppers
-potatoes, onions, garlic, bell peppers
-potatoes, bell peppers, onions, garlic
-spinach, bell peppers
-spinach, onions, garlic
-carrots, garlic, mushrooms, potatoes
-```
+{{ show_command_and_output(examples.select_list_example_one) }}
 
 ### <a name='quoting_sublist'></a> Quoting Sublist Elements
 
-The default `quote` parameter will only quote the whole combined list of elements. To quote each individual element of
-the sublist you need to use a special form of `join_with` along with the `quote` param. For example if we wanted all of
-our ingredients surrounded with double quotes. We would update our spec this way.
+The default `quote` parameter will only quote the whole combined list of
+elements. To quote each individual element of the sublist you need to use a
+special form of `join_with` along with the `quote` param. For example if we
+wanted all of our ingredients surrounded with double quotes. We would update our
+spec this way.
 
 {{ show_example(examples.select_list_example_two) }}
 
-Now when we run our datespec we get:
-
-```shell script
-dataspec -s ~/scratch/quoted_ingredients.json -i 10
-"spinach", "mushrooms", "bell peppers", "onions"
-"spinach", "onions", "mushrooms", "garlic"
-"carrots", "garlic", "mushrooms", "onions"
-"mushrooms", "bell peppers", "carrots"
-"carrots", "potatoes", "bell peppers", "onions"
-"spinach", "mushrooms"
-"mushrooms", "bell peppers", "onions"
-"potatoes", "carrots", "bell peppers", "spinach"
-"garlic", "mushrooms", "potatoes"
-"carrots", "spinach", "bell peppers", "potatoes"
-```
+{{ show_command_and_output(examples.select_list_example_two) }}
 
 ## <a name='CSV_Data'></a> CSV Data
 
-If you have an existing large set of data in a tabular format that you want to use, it would be burdensome to copy and
-paste the data into a spec. To make use of data already in a tabular format you can use a `csv` Field Spec. These specs
-allow you to identify a column from a tabular data file to use to provide the values for a field. Another advantage of
-using a csv spec is that it is easy to have fields that are correlated be generated together. All rows will be selected
-incrementally, unless any of the fields are configured to use `sample` mode. You can use `sample` mode on individual
-columns, or you can use it across all columns by creating a `configref` spec. See [csv_select](#csv_select) for an efficient way
-to select multiple columns from a csv file.
+If you have an existing large set of data in a tabular format that you want to
+use, it would be burdensome to copy and paste the data into a spec. To make use
+of data already in a tabular format you can use a `csv` Field Spec. These specs
+allow you to identify a column from a tabular data file to use to provide the
+values for a field. Another advantage of using a csv spec is that it is easy to
+have fields that are correlated be generated together. All rows will be selected
+incrementally, unless any of the fields are configured to use `sample` mode. You
+can use `sample` mode on individual columns, or you can use it across all
+columns by creating a `configref` spec. See [csv_select](#csv_select) for an
+efficient way to select multiple columns from a csv file.
 
 The `csv` Field Spec structure is:
 
@@ -834,7 +1035,7 @@ The `csv` Field Spec structure is:
 |-----------|---------|--------|-----------|
 |datafile   |no       |data.csv|filename in datandir to use|
 |headers    |no       |false   |yes, on, true for affirmative|
-|column     |no       |1       |1 based column number or field name if headers are present|
+|column     |no       |1       |1 based column number or field name if headers</br> are present|
 |delimiter  |no       |,       |how values are separated|
 |quotechar  |no       |"       |how values are quoted, default is double quote|
 |sample     |no       |False   |If the values should be selected at random|
@@ -844,10 +1045,11 @@ The `csv` Field Spec structure is:
 
 ##### Single Field
 
-The simplest example is a file with a single field that contains the values to generate for a field. For example if we
-have a known list of cities, we can put this in a file and reference it from our spec. The advantage of this approach is
-that it is easy to add new data points and to use small sets of data for testing by creating directories that have
-smaller input files.
+The simplest example is a file with a single field that contains the values to
+generate for a field. For example if we have a known list of cities, we can put
+this in a file and reference it from our spec. The advantage of this approach is
+that it is easy to add new data points and to use small sets of data for testing
+by creating directories that have smaller input files.
 
 {{ show_example(examples.csv_spec_example_one) }}
 
@@ -860,12 +1062,13 @@ Chicage
 London
 ```
 
-Note that if your data might have commas in it (the default delimiter), you should specify a delimiter that will not be
-found in your data.
+Note that if your data might have commas in it (the default delimiter), you
+should specify a delimiter that will not be found in your data.
 
 ##### Multiple Fields Non Comma Separated
 
-In this example we have a tab delimited file with multiple columns that we want to use.
+In this example we have a tab delimited file with multiple columns that we want
+to use.
 
 ```
 status	status_description	status_type
@@ -881,9 +1084,10 @@ Our Data Spec looks like:
 
 {{ show_example(examples.csv_spec_example_two) }}
 
-The `configref` exist so that we don't have to repeat ourselves for common configurations across multiple fields. If we
-use the following template {% raw %}`{{ status }},{{ description }},{{ status_type }}`{% endraw %} and run this spec we will get output
-similar to:
+The `configref` exist so that we don't have to repeat ourselves for common
+configurations across multiple fields. If we use the following template {% raw
+%}`{{ status }},{{ description }},{{ status_type }}`{% endraw %} and run this
+spec we will get output similar to:
 
 ```shell
 dataspec --spec tabs.yaml --datadir ./data -t template.jinja -i 5
@@ -896,21 +1100,26 @@ dataspec --spec tabs.yaml --datadir ./data -t template.jinja -i 5
 
 ## <a name="CSV_Select"></a>CSV Select
 
-A common process is to select subsets of the columns from a csv file to use. The `csv_select` type makes this more
-efficient than using the standard `csv` type. Below is an example that will Convert data from
-the [Geonames](http://www.geonames.org/) [allCountries.zip](http://download.geonames.org/export/dump/allCountries.zip)
-dataset by selecting a subset of the columns from the tab delimited file. The key in the data element is the new name
-for the field. The value can either be the 1 indexed column number or the name of the field if the data has `headers`.
+A common process is to select subsets of the columns from a csv file to use.
+The `csv_select` type makes this more efficient than using the standard `csv`
+type. Below is an example that will Convert data from the
+[Geonames](http://www.geonames.org/)
+[allCountries.zip](http://download.geonames.org/export/dump/allCountries.zip)
+dataset by selecting a subset of the columns from the tab delimited file. The
+key in the data element is the new name for the field. The value can either be
+the 1 indexed column number, or the name of the field if the data has `headers`.
 Our example doesn't have headers, so we are using the 1 based indexes.
 
 {{ show_example(examples.csv_select_example_one) }}
 
 ## <a name="nested"></a>Nested Fields
 
-Nested types are used to create fields that contain subfields. Nested types can also contain nested fields to allow
-multiple levels of nesting. Use the `nested` type to generate a field that contains subfields. The subfields are
-defined in the `fields` element of the nested spec. The `fields` element will be treated like a top level dataspec
-and has access to the `refs` and other elements of the root.
+Nested types are used to create fields that contain subfields. Nested types can
+also contain nested fields to allow multiple levels of nesting. Use the `nested`
+type to generate a field that contains subfields. The subfields are defined in
+the `fields` element of the nested spec. The `fields` element will be treated
+like a top level dataspec and has access to the `refs` and other elements of the
+root.
 
 The `nested` Field Spec structure is:
 
@@ -941,18 +1150,20 @@ Below is an example of the data we wish to generate:
     "user_id": "bad135dad987",
     "geo": {
       "place_id": 12345,
-      "coordinates": [118.2, 34.0]
+      "coordinates": [
+        118.2,
+        34.0
+      ]
     }
   }
 }
 ```
 
-The `user` is a nested object, which has a subfield `geo`, which is also a nested object. The `id` and `user_id` fields
-are uuids. The coordinates field is a list of longitude followed by latitude. Below are the specs that will generate
+The `user` is a nested object, which has a subfield `geo`, which is also a
+nested object. The `id` and `user_id` fields are uuids. The coordinates field is
+a list of longitude followed by latitude. Below are the specs that will generate
 data that matches this schema.
 
 {{ show_example(examples.nested_example_one) }}
 
-If we run this example:
-
 {{ show_command_and_output(examples.nested_example_one) }}
\ No newline at end of file
diff --git a/docs/templates/README.md b/docs/templates/README.md
new file mode 100644
index 0000000..8903253
--- /dev/null
+++ b/docs/templates/README.md
@@ -0,0 +1,49 @@
+## Process for Updating FIELDSPECS.md
+
+Instead of hard coding all the formats for each example, which is prone to error,
+we only hard code a small code fragment and some parameters.  These are then used
+to execute the DataSpec API and to generate the JSON, YAML, API, and to run the
+generated spec to capture the output of the example. This makes it easier to add
+new examples and to have all the formats for the example generated.
+
+### Add new Example to `examples.py`
+
+This should be the core piece of code used to populate the spec using the builder.
+
+Example
+
+```python
+    Example(
+        name="new_example_one",
+        iterations=10,
+        fragment="""
+spec_builder.new_type("name", ["data", "elements"], key1=5, key2="fluffy")
+""",
+        pipes="--format json-pretty"
+    ),
+```
+
+### Create the new `field_specs_template_data.json`
+
+Next we need to generate the pieces from the Example and add them to the field_specs_template_data.json
+file.
+
+```bash
+./build_examples.py > field_specs_template_data.json
+```
+
+Or just update the existing data with the new info
+
+```bash
+./build_examples.py | tail -20
+```
+
+### Apply Updates to the FIELDSPEC.md file
+
+The last step is to apply the updated template data to the FIELDSPEC.md file.
+There are several macros in the FIELDSPECS.jinja.md that will format the examples
+in a uniform manner.
+
+```bash
+./tool.py -i field_specs_template_data.json -m apply
+```
\ No newline at end of file
diff --git a/docs/templates/build_examples.py b/docs/templates/build_examples.py
new file mode 100644
index 0000000..c31da8c
--- /dev/null
+++ b/docs/templates/build_examples.py
@@ -0,0 +1,216 @@
+#!/bin/env python
+
+import json
+import logging
+import yaml
+from collections import OrderedDict
+import subprocess
+import argparse
+
+from examples import EXAMPLES
+
+logging.basicConfig(level=logging.INFO)
+log = logging.getLogger('example')
+
+template = """
+import dataspec
+
+spec_builder = dataspec.spec_builder()
+
+FRAGMENT
+
+spec = spec_builder.build()
+"""
+
+
+def build_example(name, spec, iterations=5, pipes=""):
+    if pipes is None:
+        pipes = ""
+    spec_string = json.dumps(spec.raw_spec)
+    cmd = f"dataspec --inline '{spec_string}' --log-level error -i {iterations} {pipes}"
+    out = subprocess.check_output(cmd, shell=True)
+    cmd_display = cmd
+    if len(spec_string) > 80:
+        cmd_display = f"dataspec -s dataspec.json --log-level error -i {iterations} {pipes}"
+    ordered = order_spec(spec.raw_spec)
+    dirty_yaml = yaml.dump(ordered, sort_keys=False, width=4096).strip()
+    cleaned_yaml = clean_semi_formatted_yaml(dirty_yaml)
+    try:
+        if yaml.load(cleaned_yaml) != spec.raw_spec:
+            print('yaml does not match raw')
+            print(name)
+            print(json.dumps(yaml.load(cleaned_yaml), indent=4))
+            print(json.dumps(spec.raw_spec, indent=4))
+    except Exception:
+        print('yaml does not load')
+        print(cleaned_yaml)
+        print(dirty_yaml)
+
+    example = {
+        "json": json.dumps(ordered, cls=MyEncoder, sort_keys=False, indent=2).strip(),
+        "yaml": cleaned_yaml,
+        "api": code.strip().replace('\n\n\n', '\n\n'),
+        "command": cmd_display.strip(),
+        "output": out.decode('utf-8').strip()
+    }
+    return example
+
+
+def clean_semi_formatted_yaml(toclean: str):
+    """
+    Our custom YAML formatter adds a bunch of stuff we need to clean up since it is working with
+    and OrderdDict as the underlying structure.
+    """
+    return toclean.replace('!!list ', '')
+
+
+def order_spec(raw_spec):
+    outer = {}
+    for field, spec in raw_spec.items():
+        if field == 'refs':
+            continue
+        outer[field] = order_field_spec(spec)
+    if 'refs' in raw_spec:
+        updated_refs = {}
+        for ref, spec in raw_spec['refs'].items():
+            updated_refs[ref] = order_field_spec(spec)
+        outer['refs'] = updated_refs
+    return outer
+
+
+def order_field_spec(field_spec):
+    if not isinstance(field_spec, dict):
+        return NoIndent(field_spec)
+    ordered = OrderedDict()
+    if 'type' in field_spec:
+        ordered['type'] = field_spec['type']
+    if 'data' in field_spec:
+        ordered['data'] = NoIndent(field_spec['data'])
+    if 'refs' in field_spec:
+        refs = field_spec['refs']
+        if isinstance(refs[0], list):
+            ordered['refs'] = [NoIndent(ref) for ref in refs]
+        else:
+            ordered['refs'] = NoIndent(refs)
+    for key in ['config', 'ref', 'fields']:
+        if key in field_spec:
+            ordered[key] = field_spec[key]
+    for key, val in field_spec.items():
+        if key not in ['type', 'data', 'config', 'ref', 'refs', 'fields']:
+            ordered[key] = val
+    return ordered
+
+
+##################################
+# from https://stackoverflow.com/questions/13249415/how-to-implement-custom-indentation-when-pretty-printing-with-the-json-module
+##################################
+from _ctypes import PyObj_FromPtr
+import json
+import re
+
+
+class NoIndent(object):
+    """ Value wrapper. """
+
+    def __init__(self, value):
+        self.value = value
+
+    @classmethod
+    def to_yaml(cls, representer, node):
+        """
+        Added to support formatting for YAML
+        """
+        return representer.represent_scalar(' ', u'{.value}'.format(node))
+
+
+class MyEncoder(json.JSONEncoder):
+    FORMAT_SPEC = '@@{}@@'
+    regex = re.compile(FORMAT_SPEC.format(r'(\d+)'))
+
+    def __init__(self, **kwargs):
+        # Save copy of any keyword argument values needed for use here.
+        self.__sort_keys = kwargs.get('sort_keys', None)
+        super(MyEncoder, self).__init__(**kwargs)
+
+    def default(self, obj):
+        return (self.FORMAT_SPEC.format(id(obj)) if isinstance(obj, NoIndent)
+                else super(MyEncoder, self).default(obj))
+
+    def encode(self, obj):
+        format_spec = self.FORMAT_SPEC  # Local var to expedite access.
+        json_repr = super(MyEncoder, self).encode(obj)  # Default JSON.
+
+        # Replace any marked-up object ids in the JSON repr with the
+        # value returned from the json.dumps() of the corresponding
+        # wrapped Python object.
+        for match in self.regex.finditer(json_repr):
+            # see https://stackoverflow.com/a/15012814/355230
+            id = int(match.group(1))
+            no_indent = PyObj_FromPtr(id)
+            json_obj_repr = json.dumps(no_indent.value, sort_keys=self.__sort_keys)
+
+            # Replace the matched id string with json formatted representation
+            # of the corresponding Python object.
+            json_repr = json_repr.replace(
+                '"{}"'.format(format_spec.format(id)), json_obj_repr)
+
+        return json_repr
+
+
+#########################
+# from https://til.simonwillison.net/python/style-yaml-dump
+# via: https://stackoverflow.com/a/8641732 and https://stackoverflow.com/a/16782282
+#########################
+def represent_ordereddict(dumper, data):
+    value = []
+
+    for item_key, item_value in data.items():
+        node_key = dumper.represent_data(item_key)
+        node_value = dumper.represent_data(item_value)
+
+        value.append((node_key, node_value))
+
+    return yaml.nodes.MappingNode(u"tag:yaml.org,2002:map", value)
+
+
+yaml.add_representer(OrderedDict, represent_ordereddict)
+
+
+def represent_noindent(dumper, data):
+    if isinstance(data.value, list):
+        return dumper.represent_sequence("tag:yaml.org,2002:list", data.value, flow_style=True)
+    if isinstance(data.value, dict):
+        return dumper.represent_mapping("tag:yaml.org,2002:map", data.value, flow_style=True)
+    return dumper.represent_data(data.value)
+
+
+yaml.add_representer(NoIndent, represent_noindent)
+######################
+# Begin Processing
+#####################
+parser = argparse.ArgumentParser(description='')
+parser.add_argument('-k', '--key-filter', dest='key_filter',
+                    help='where to get the input')
+
+args = parser.parse_args()
+
+built_examples = {}
+for name, iterations, fragment, pipes in EXAMPLES:
+    if args.key_filter and name not in args.key_filter:
+        continue
+    code = template.replace('FRAGMENT', fragment)
+    try:
+        log.info(name)
+        log.debug(code)
+        exec(code)
+        # spec should be created when running exec
+        data = build_example(name, spec, iterations, pipes)
+        built_examples[name] = data
+    except (TypeError, SyntaxError) as err:
+        print(err)
+        print(name)
+        print(code)
+        raise Exception() from err
+        # exit(-1)
+
+print(json.dumps({"examples": built_examples}, cls=MyEncoder, sort_keys=False, indent=2))
diff --git a/docs/templates/builder.py.jinja b/docs/templates/builder.py.jinja
new file mode 100644
index 0000000..c3deb47
--- /dev/null
+++ b/docs/templates/builder.py.jinja
@@ -0,0 +1,269 @@
+"""
+Module for building Data Specs programmatically
+"""
+{% macro func_name(type_name) -%}
+{{ type_name.replace('.', '_').replace('-', '_').replace('range', 'range_spec').replace('_range_spec', '_range') }}{%- endmacro %}
+{% macro build_params(type) -%}
+{% if type.params is defined -%}
+{% for param in type.params -%}{{ param.name }}, {%- endfor %}
+{%- endif %}
+{%- endmacro %}
+{% macro build_typed_params(type) -%}
+{% if type.params is defined -%}
+{% for param in type.params -%}{{ param.name }}: {{ param.type }}, {%- endfor %}
+{%- endif %}
+{%- endmacro %}
+{% macro build_param_docs(type, indent) -%}
+{% if type.params is defined -%}
+{% for param in type.params %}
+{{ indent }}:param {{ param.name }}: {{ param.description }}{% endfor %}
+{%- endif %}
+{%- endmacro %}
+{% macro build_spec(type) %}
+    spec = {
+        "type": "{{ type.type_name }}"{% if type.spec_params is defined -%},
+{% for param in type.spec_params -%}
+        "{{ param.name }}": {{ param.value }}{{ "," if not loop.last else "" }}
+{%- endfor %}
+{%- endif %}
+    }
+{% endmacro %}
+{% macro add_custom_logic(type) -%}
+{% if type.custom_logic is defined -%}
+{{ type.custom_logic }}
+{%- endif %}
+{% endmacro %}
+from typing import Any, Union, Dict, List
+import json
+import logging
+
+log = logging.getLogger(__name__)
+
+
+class FieldInfo:
+    """
+    Class for holding info after adding field or ref to spec. Can be passed to specs that use other specs
+    like combine
+    """
+
+    def __init__(self, key: str, type_name: str, builder=None):
+        self.key = key
+        self.type_name = type_name
+        self.builder = builder
+
+
+class Builder:
+    """
+    Container class for constructing the Data Spec by adding fields, refs, and field_groups
+
+    Basic Usage:
+
+    builder = dataspec.builder.Builder()
+    builder.values('names', ['amy', 'bob', 'cat', 'dan', 'earl'])
+    builder.range('ages', start=22, end=33)
+    spec = builder.build()
+    """
+
+    def __init__(self, has_refs=True):
+        if has_refs:
+            self.refs_builder = Builder(False)
+        else:
+            self.refs_builder = None
+        self.fields = {}
+        self.field_groups = []
+        self.keys = set()
+{% for type in types %}
+    def {{ func_name(type.type_name) }}(self, key: str, {{ build_typed_params(type) }}**config):
+        """
+        creates {{ type.type_name }} Field Spec and adds to Data Spec
+
+        :param key: name of ref/field{{ build_param_docs(type, "        ") }}
+        :param config: in **kwargs format
+        """
+        return self._add_field_spec(key, {{ func_name(type.type_name) }}({{ build_params(type) }}**config))
+{% endfor %}
+    def _add_field_spec(self, key, spec):
+        self.add_field(key, spec)
+        return FieldInfo(key, spec['type'], self)
+
+    def add_fields(self, **kwargs):
+        """
+        Add all fields to the spec. kwargs format should be:
+
+        builder = dataspec.builder.Builder()
+        builder.add_fields(
+            FIELDNAME1=builder.some_spec(with_args),
+            FIELDNAME2=builder.another_spec(with_args)
+        )
+
+        :param kwargs: where key is field name and value is a generated spec
+        :return: self for chaining invocations
+        """
+        for key, spec in kwargs.items():
+            self.add_field(key, spec)
+        return self
+
+    def add_field(self, key: str, spec: dict):
+        """
+        Add single field to the spec.
+
+        builder = dataspec.builder.Builder()
+        builder.add_field("field1", builder.some_spec(with_args)) \
+               .add_field("field2", builder.another_spec(with_args))
+
+        :param key: field name
+        :param spec: the generate spec for this field
+        :return: self for chaining invocations
+        """
+        if key in self.keys:
+            log.warning('%s key already defined, overwriting with %s',
+                        key, json.dumps(spec))
+        self.keys.add(key)
+        self.fields[key] = spec
+        return self
+
+    def add_refs(self, **kwargs):
+        """
+        Add all refs to the spec. kwargs format should be:
+
+        builder = dataspec.builder.Builder()
+        builder.add_refs(
+            REFNAME1=builder.some_spec(with_args),
+            REFNAME2=builder.another_spec(with_args)
+        )
+
+        :param kwargs: where key is ref name and value is a generated spec
+        :return: self for chaining invocations
+        """
+        for key, spec in kwargs.items():
+            self.add_ref(key, spec)
+        return self
+
+    def add_ref(self, key: str, spec: dict):
+        """
+        Add single ref to the spec.
+
+        builder = dataspec.builder.Builder()
+        builder.add_ref("ref1", builder.some_spec(with_args)) \
+               .add_ref("ref2", builder.another_spec(with_args))
+
+        :param key: ref name
+        :param spec: the generate spec for this ref
+        :return: self for chaining invocations
+        """
+        if key in self.keys:
+            log.warning('%s key already defined, overwriting with %s', key, json.dumps(spec))
+        self.keys.add(key)
+        self.refs_builder.add_field(key, spec)
+        return self
+
+    def weighted_field_group(self, key: str, fields: List[str], weight: float):
+        """
+        Creates a weighted field group for a single key and add to Spec
+        :param key: the name of the field group
+        :param fields: the fields in the group
+        :param weight: the weight for this group
+        :return: FieldInfo
+        """
+        field_group = {
+            key: {
+                "weight": weight,
+                "fields": fields
+            }
+        }
+        self.field_groups.append(field_group)
+        return FieldInfo(key, 'field_group')
+
+    def named_field_group(self, key: str, fields: List[str]):
+        """
+        Create a named field group for a single key and add to Spec
+        :param key: the name of the field group
+        :param fields: the fields in the group
+        :return: FieldInfo
+        """
+        field_group = {
+            key: fields
+        }
+        self.field_groups.append(field_group)
+        return FieldInfo(key, 'field_group')
+
+    def add_field_groups(self, field_groups: List[Union[List[str], Dict[str, Dict]]]):
+        """
+        Add a single field group
+        :param field_groups: to add
+        :return: self for chaining invocations
+        """
+        for field_group in field_groups:
+            self.add_field_group(field_group)
+        return self
+
+    def add_field_group(self, field_group: Union[List[str], Dict[str, Dict]]):
+        """
+        Add a single field group
+        :param field_group: to add
+        :return: self for chaining invocations
+        """
+        self.field_groups.append(field_group)
+        return self
+
+    def build(self):
+        """
+        Generates the spec from the provided fields, refs, and field_groups
+        :return: The built spec
+        """
+        spec = {}
+        spec.update(self.fields)
+        if len(self.refs_builder.fields) > 0:
+            spec['refs'] = self.refs_builder.fields
+        if len(self.field_groups) > 0:
+            self._configure_field_groups(spec)
+
+        return spec
+
+    def _configure_field_groups(self, spec):
+        """
+        Adds the field_groups element to the spec if needed and defined
+        """
+        all_dict = all(isinstance(entry, dict) for entry in self.field_groups)
+        if all_dict:
+            flattened = {}
+            for entry in self.field_groups:
+                flattened.update(entry)
+            spec['field_groups'] = flattened
+        all_list = all(isinstance(entry, list) for entry in self.field_groups)
+        if all_list:
+            spec['field_groups'] = self.field_groups
+
+
+def single_field(name: str, spec):
+    """ Creates Builder for single field and spec """
+    return Builder().add_field(name, spec)
+
+{% for type in types %}
+def {{ func_name(type.type_name) }}({{ build_typed_params(type) }}**config):
+    """
+    Constructs a {{ type.type_name }} Field Spec
+{{ build_param_docs(type, "    ") }}
+    :param config: in **kwargs format
+    :return: the {{ type.type_name }} spec
+    """
+{{ build_spec(type) }}{{ add_custom_logic(type) }}
+    if len(config) > 0:
+        spec['config'] = config
+    return spec
+
+{% endfor %}
+
+def _create_key_list(entries):
+    """
+    Checks if entries are from FieldInfo objects and extracts keys
+
+    :param entries: to create key list from
+    :return: the list of keys
+    """
+    if len(entries) == 0:
+        return []
+    if isinstance(entries[0], FieldInfo):
+        return [entry.key for entry in entries]
+    # this should be a regular list of strings
+    return entries
\ No newline at end of file
diff --git a/docs/templates/data/allCountries.txt b/docs/templates/data/allCountries.txt
new file mode 100644
index 0000000..a148cd8
--- /dev/null
+++ b/docs/templates/data/allCountries.txt
@@ -0,0 +1,100 @@
+2986043	Pic de Font Blanca	Pic de Font Blanca	Pic de Font Blanca,Pic du Port	42.64991	1.53335	T	PK	AD		00				0		2860	Europe/Andorra	2014-11-05
+2994701	Roc Mélé	Roc Mele	Roc Mele,Roc Meler,Roc Mélé	42.58765	1.74028	T	MT	AD	AD,FR	00				0		2803	Europe/Andorra	2020-06-10
+3007683	Pic des Langounelles	Pic des Langounelles	Pic des Langounelles	42.61203	1.47364	T	PK	AD	AD,FR	00				0		2685	Europe/Andorra	2014-11-05
+3017832	Pic de les Abelletes	Pic de les Abelletes	Pic de la Font-Negre,Pic de la Font-Nègre,Pic de les Abelletes	42.52535	1.73343	T	PK	AD	FR	A9	66	663	66146	0		2411	Europe/Andorra	2014-11-05
+3017833	Estany de les Abelletes	Estany de les Abelletes	Estany de les Abelletes,Etang de Font-Negre,Étang de Font-Nègre	42.52915	1.73362	H	LK	AD	FR	A9				0		2260	Europe/Andorra	2014-11-05
+3023203	Port Vieux de la Coume d’Ose	Port Vieux de la Coume d'Ose	Port Vieux de Coume d'Ose,Port Vieux de Coume d’Ose,Port Vieux de la Coume d'Ose,Port Vieux de la Coume d’Ose	42.62568	1.61823	T	PASS	AD		00				0		2687	Europe/Andorra	2014-11-05
+3029315	Port de la Cabanette	Port de la Cabanette	Port de la Cabanette,Porteille de la Cabanette	42.6	1.73333	T	PASS	AD	AD,FR	B3	09	091	09139	0		2379	Europe/Andorra	2014-11-05
+3034945	Port Dret	Port Dret	Port Dret,Port de Bareites,Port de las Bareytes,Port des Bareytes	42.60172	1.45562	T	PASS	AD		00				0		2660	Europe/Andorra	2014-11-05
+3038814	Costa de Xurius	Costa de Xurius		42.50692	1.47569	T	SLP	AD		07				0		1839	Europe/Andorra	2015-03-08
+3038815	Font de la Xona	Font de la Xona		42.55003	1.44986	H	SPNG	AD		04				0		1976	Europe/Andorra	2010-01-11
+3038816	Xixerella	Xixerella		42.55327	1.48736	P	PPL	AD		04				0		1417	Europe/Andorra	2009-04-24
+3038818	Riu Xic	Riu Xic		42.57165	1.67554	H	STM	AD		02				0		1851	Europe/Andorra	2014-12-03
+3038819	Pas del Xic	Pas del Xic		42.49766	1.57597	R	TRL	AD		08				0		1669	Europe/Andorra	2015-05-06
+3038820	Roc del Xeig	Roc del Xeig		42.56068	1.4898	T	RK	AD		04				0		1643	Europe/Andorra	2015-05-06
+3038821	Collada del Xeig	Collada del Xeig		42.57512	1.49009	T	PK	AD		04				0		1857	Europe/Andorra	2014-11-05
+3038822	Fonts Vives	Fonts Vives		42.49829	1.56428	H	SPNG	AD		08				0		1540	Europe/Andorra	2015-05-06
+3038823	Roc de Vista	Roc de Vista		42.49922	1.48118	T	RK	AD		07				0		1564	Europe/Andorra	2015-03-08
+3038824	Obaga de Vista	Obaga de Vista		42.47935	1.44621	T	SLP	AD		06				0		1749	Europe/Andorra	2015-03-08
+3038825	Coll de Vista	Coll de Vista		42.47611	1.60578	T	GAP	AD		06				0		2335	Europe/Andorra	2015-04-08
+3038826	Coll de Vista	Coll de Vista	Coll de Vista	42.47552	1.44173	T	PASS	AD	AD,ES	00				0		1967	Europe/Andorra	2014-11-05
+3038827	Visanseny	Visanseny	Visanceny,Visanseny	42.57488	1.61616	L	LCTY	AD		02				0		1706	Europe/Andorra	2014-12-03
+3038828	Roc de la Vinya	Roc de la Vinya		42.52776	1.56446	T	RK	AD		03				0		1260	Europe/Andorra	2015-02-06
+3038829	Canal de la Vinya	Canal de la Vinya		42.52232	1.52319	H	STM	AD		04				0		1183	Europe/Andorra	2015-05-06
+3038830	Bosc de Villar	Bosc de Villar		42.60135	1.5434	V	FRST	AD		05				0		1860	Europe/Andorra	2014-12-03
+3038831	Torrent de Vila	Torrent de Vila		42.53414	1.56678	H	STM	AD		03				0		1337	Europe/Andorra	2015-02-06
+3038832	Vila	Vila	Casas Vila,Vila	42.53176	1.56654	P	PPL	AD		03				0		1318	Europe/Andorra	2012-04-13
+3038833	Basers de Vicenç	Basers de Vicenc		42.47669	1.4829	T	CLF	AD		06				0		1019	Europe/Andorra	2015-03-08
+3038834	Pla de Viàs	Pla de Vias		42.59001	1.65996	T	UPLD	AD		02				0		2052	Europe/Andorra	2014-12-03
+3038835	Vial del Cardaire	Vial del Cardaire		42.56372	1.50503	L	LCTY	AD		04				0		1574	Europe/Andorra	2014-11-05
+3038836	Font del Vi	Font del Vi		42.52342	1.47698	H	SPNG	AD		04				0		1765	Europe/Andorra	2015-03-08
+3038837	Font Verda	Font Verda		42.5557	1.55859	H	SPNG	AD		05				0		1787	Europe/Andorra	2015-02-06
+3038838	Costa Verda	Costa Verda		42.48297	1.66086	T	SLP	AD		08				0		2572	Europe/Andorra	2015-05-06
+3038839	Costa Verda	Costa Verda		42.48749	1.57108	T	SLP	AD		08				0		2150	Europe/Andorra	2015-05-06
+3038840	Serrat de Ventader	Serrat de Ventader		42.49157	1.43183	T	SPUR	AD		06				0		1700	Europe/Andorra	2015-03-08
+3038841	Bony de Vellatocina	Bony de Vellatocina		42.61246	1.50594	T	PK	AD		05				0		2370	Europe/Andorra	2014-11-05
+3038842	Pleta Vella	Pleta Vella		42.51143	1.60976	L	GRAZ	AD		03				0		2258	Europe/Andorra	2015-05-06
+3038843	Solà Vell	Sola Vell		42.46645	1.47647	T	SLP	AD		06				0		1602	Europe/Andorra	2015-04-08
+3038844	Port Vell	Port Vell		42.65315	1.55207	T	PASS	AD		00				0		2432	Europe/Andorra	2014-11-05
+3038845	Port Negre del Pallars	Port Negre del Pallars	Port Negre del Pallars,Port Vell	42.56799	1.44412	T	PASS	AD	ES	00				0		2517	Europe/Andorra	2014-11-05
+3038846	Bancal Vedeller	Bancal Vedeller		42.60103	1.45896	R	TRL	AD		04				0		2451	Europe/Andorra	2014-11-05
+3038847	Vedat del Xeig	Vedat del Xeig		42.56223	1.48785	L	LCTY	AD		04				0		1609	Europe/Andorra	2015-05-06
+3038848	Vedat dels Plans	Vedat dels Plans		42.53983	1.50408	L	AREA	AD		04				0		1639	Europe/Andorra	2014-11-05
+3038849	Vedat de Coll de les Cases	Vedat de Coll de les Cases		42.57491	1.49849	L	LCTY	AD		04				0		1871	Europe/Andorra	2014-11-05
+3038850	Serra del Vedat	Serra del Vedat		42.46836	1.48497	T	RDGE	AD		06				0		1366	Europe/Andorra	2015-04-08
+3038851	Planades del Vedat	Planades del Vedat		42.53939	1.50116	T	UPLD	AD		04				0		1732	Europe/Andorra	2015-02-06
+3038852	Veda de Sorteny	Veda de Sorteny		42.62075	1.55042	L	LCTY	AD		05				0		1916	Europe/Andorra	2014-11-05
+3038853	Veda del Castellar	Veda del Castellar		42.62578	1.50914	L	AREA	AD		05				0		2216	Europe/Andorra	2014-11-05
+3038854	Canal de les Veces	Canal de les Veces		42.52405	1.52124	H	STM	AD		04				0		1129	Europe/Andorra	2015-05-06
+3038855	Coma de Varilles	Coma de Varilles		42.63797	1.52599	T	VAL	AD		05				0		2288	Europe/Andorra	2014-11-05
+3038856	Portella de les Vaques	Portella de les Vaques		42.56734	1.44421	T	PASS	AD		00				0		2544	Europe/Andorra	2014-11-05
+3038857	Pas de les Vaques	Pas de les Vaques		42.58129	1.70146	T	PASS	AD		02				0		2498	Europe/Andorra	2014-12-03
+3038858	Collada de les Vaques	Collada de les Vaques		42.56354	1.56884	T	PASS	AD		00				0		2096	Europe/Andorra	2014-12-03
+3038859	Roc del Vaquer	Roc del Vaquer		42.63442	1.47968	T	CLF	AD		05				0		2389	Europe/Andorra	2014-11-05
+3038860	Valls de la Coma	Valls de la Coma		42.60079	1.63691	L	LCTY	AD		02				0		1917	Europe/Andorra	2014-12-03
+3038861	Vall d'Incles	Vall d'Incles		42.58386	1.66331	L	LCTY	AD		02				0		1764	Europe/Andorra	2010-01-11
+3038862	Vall de Soldeu	Vall de Soldeu		42.56103	1.68234	T	VAL	AD		02				0		1943	Europe/Andorra	2015-03-08
+3038863	Riu de la Vall del Riu	Riu de la Vall del Riu		42.57178	1.61415	H	STM	AD		02				0		1569	Europe/Andorra	2014-11-05
+3038864	Estany Gran de la Vall del Riu	Estany Gran de la Vall del Riu		42.60065	1.59278	H	LK	AD		02				0		2559	Europe/Andorra	2014-11-05
+3038865	Cascada de la Vall del Riu	Cascada de la Vall del Riu		42.57326	1.61423	H	FLLS	AD		02				0		1617	Europe/Andorra	2014-12-03
+3038866	Canals de la Vall del Riu	Canals de la Vall del Riu		42.59059	1.60713	H	RVN	AD		02				0		2134	Europe/Andorra	2014-12-03
+3038867	Camí de la Vall del Riu	Cami de la Vall del Riu		42.58421	1.61129	R	TRL	AD		02				0		1890	Europe/Andorra	2014-12-03
+3038868	Bosc de la Vall del Riu	Bosc de la Vall del Riu		42.58333	1.61667	V	FRST	AD		02				0		2076	Europe/Andorra	2014-12-03
+3038869	Vall del Riu	Vall del Riu		42.59499	1.60747	L	AREA	AD		02				0		2197	Europe/Andorra	2014-11-05
+3038870	Coll de Vall Civera	Coll de Vall Civera	Coll de Valcibera,Coll de Vall Civera,Collado de Vall-Civera,Port de Vall Civera	42.48818	1.66373	T	PASS	AD	ES	00				0		2482	Europe/Andorra	2014-11-05
+3038871	Obac de la Vall	Obac de la Vall		42.46602	1.50498	T	SLP	AD		06				0		1254	Europe/Andorra	2015-04-08
+3038872	Riu Valira d’Orient	Riu Valira d'Orient	Rio Balira del Orien,Riu Valira d'Orient,Riu Valira d’Orient,Río Balira del Orien	42.51146	1.53647	H	STM	AD		08				0		1033	Europe/Andorra	2018-09-05
+3038873	Riu Valira del Nord	Riu Valira del Nord	Riu Valira d'Ordino,Riu Valira del Nord,Riu Valira del Nort,Riu Valira d’Ordino,Valira del Nord,Valira del Norte	42.5123	1.53617	H	STM	AD		08				0		1139	Europe/Andorra	2018-09-05
+3038874	Estanys de la Val del Riu	Estanys de la Val del Riu		42.60398	1.59622	H	LKS	AD		02				0		2567	Europe/Andorra	2014-11-05
+3038875	Vaca Morta	Vaca Morta		42.5681	1.76256	L	LCTY	AD		02				0		1858	Europe/Andorra	2014-11-05
+3038876	Riu d’Urina	Riu d'Urina		42.5594	1.58998	H	STM	AD		02				0		1450	Europe/Andorra	2014-12-03
+3038877	Serrat de la Uïna	Serrat de la Uina		42.55975	1.52393	T	RDGE	AD		05				0		1516	Europe/Andorra	2015-02-06
+3038878	Obaga de la Tuta	Obaga de la Tuta		42.48885	1.44734	T	SLP	AD		06				0		1387	Europe/Andorra	2015-03-08
+3038879	Cova de la Tuta	Cova de la Tuta		42.48678	1.44548	S	CAVE	AD		06				0		1567	Europe/Andorra	2015-03-08
+3038880	Roca del Tut	Roca del Tut		42.54941	1.45701	T	RK	AD		04				0		2010	Europe/Andorra	2015-02-06
+3038881	Coll de Turer	Coll de Turer		42.55784	1.46979	T	PASS	AD		04				0		2115	Europe/Andorra	2014-11-05
+3038882	Font del Tudó	Font del Tudo		42.44205	1.53489	H	SPNG	AD		06				0		1897	Europe/Andorra	2015-04-08
+3038883	Estany de les Truites	Estany de les Truites		42.57705	1.44801	H	LK	AD		04				0		2289	Europe/Andorra	2014-11-05
+3038884	Serra de Tristaina	Serra de Tristaina	Serra de Tristaina	42.65048	1.47705	T	RDGE	AD	AD,FR	00				0		2694	Europe/Andorra	2014-11-05
+3038885	Riu de Tristaina	Riu de Tristaina		42.615	1.53707	H	STM	AD		05				0		1536	Europe/Andorra	2014-11-05
+3038886	Pic de Tristaina	Pic de Tristaina	Pic de Triatagne,Pic de Tristagne,Pic de Tristaina,Pic des Tri-Stagnes,Tristaina	42.65265	1.49242	T	PK	AD	AD,FR	00				0		2813	Europe/Andorra	2007-04-29
+3038887	Clot de Tres Torrents	Clot de Tres Torrents		42.54063	1.53382	H	RVN	AD		04				0		1466	Europe/Andorra	2015-02-06
+3038888	Bony de Tres Corts	Bony de Tres Corts		42.52911	1.53538	T	SPUR	AD		04				0		1483	Europe/Andorra	2015-02-06
+3038889	Canal dels Trèmols	Canal dels Tremols		42.49814	1.49076	H	STM	AD		07				0		1214	Europe/Andorra	2015-03-08
+3038890	Travenc	Travenc		42.60215	1.69377	L	LCTY	AD		02				0		1911	Europe/Andorra	2014-12-03
+3038891	Pleta de la Trava	Pleta de la Trava		42.48464	1.62251	L	GRAZ	AD		08				0		2139	Europe/Andorra	2015-05-06
+3038892	Coll de la Trava	Coll de la Trava		42.47973	1.47555	T	SPUR	AD		06				0		1289	Europe/Andorra	2015-03-08
+3038893	Canal de la Trava	Canal de la Trava		42.57395	1.52496	H	RVN	AD		05				0		1521	Europe/Andorra	2014-12-03
+3038894	Bosc de la Trava	Bosc de la Trava		42.4844	1.63007	V	FRST	AD		08				0		2221	Europe/Andorra	2015-05-06
+3038895	Font dels Traginers	Font dels Traginers		42.43156	1.52845	H	SPNG	AD	AD,ES	00				0		2022	Europe/Andorra	2015-04-08
+3038896	Pla de les Toves	Pla de les Toves		42.46355	1.45008	T	UPLD	AD		06				0		1774	Europe/Andorra	2015-04-08
+3038897	Torrent del Tossalroi	Torrent del Tossalroi		42.47256	1.51244	H	STM	AD		06				0		1345	Europe/Andorra	2015-05-06
+3038898	Tossal Gran	Tossal Gran		42.47986	1.4892	L	LCTY	AD		07				0		941	Europe/Andorra	2015-05-06
+3038899	Tossalet i Vinyals	Tossalet i Vinyals	Tossalet i Vinyals	42.48597	1.4891	P	PPLL	AD		07				0		998	Europe/Andorra	2018-09-05
+3038900	Bosc del Tossal de les Poselles	Bosc del Tossal de les Poselles		42.53331	1.50326	V	FRST	AD		04				0		1509	Europe/Andorra	2015-02-06
+3038901	Tossal	Tossal		42.45927	1.49176	L	LCTY	AD		06				0		1050	Europe/Andorra	2015-04-08
+3038902	Tosquers	Tosquers		42.56587	1.48763	T	SLP	AD		04				0		1514	Europe/Andorra	2014-11-05
+3038903	Canal del Tosquer	Canal del Tosquer		42.57114	1.52067	H	STM	AD		05				0		1327	Europe/Andorra	2014-12-03
+3038904	Bosc del Tosquer	Bosc del Tosquer		42.57189	1.52369	V	FRST	AD		05				0		1416	Europe/Andorra	2014-12-03
+3038905	Bosc del Tosquer	Bosc del Tosquer		42.53793	1.6007	V	FRST	AD		03				0		1806	Europe/Andorra	2015-02-06
+3038906	Bosc de la Tosca	Bosc de la Tosca		42.43912	1.45592	V	FRST	AD		06				0		1428	Europe/Andorra	2015-04-08
diff --git a/docs/templates/data/cities.csv b/docs/templates/data/cities.csv
new file mode 100644
index 0000000..a9d30e6
--- /dev/null
+++ b/docs/templates/data/cities.csv
@@ -0,0 +1,8 @@
+Tokyo
+London
+New York
+Cambridge
+Oxford
+Seattle
+San Diego
+Springfield
diff --git a/docs/templates/data/tabs.csv b/docs/templates/data/tabs.csv
new file mode 100644
index 0000000..29803ab
--- /dev/null
+++ b/docs/templates/data/tabs.csv
@@ -0,0 +1,41 @@
+status	status_description	status_type
+100	Continue	Informational
+101	Switching Protocols	Informational
+200	OK	Successful
+201	Created	Successful
+202	Accepted	Successful
+203	Non-Authoritative Information	Successful
+204	No Content	Successful
+205	Reset Content	Successful
+206	Partial Content	Successful
+300	Multiple Choices	Redirection
+301	Moved Permanently	Redirection
+302	Found	Redirection
+303	See Other	Redirection
+304	Not Modified	Redirection
+305	Use Proxy	Redirection
+307	Temporary Redirect	Redirection
+400	Bad Request	Client Error
+401	Unauthorized	Client Error
+402	Payment Required	Client Error
+403	Forbidden	Client Error
+404	Not Found	Client Error
+405	Method Not Allowed	Client Error
+406	Not Acceptable	Client Error
+407	Proxy Authentication Required	Client Error
+408	Request Timeout	Client Error
+409	Conflict	Client Error
+410	Gone	Client Error
+411	Length Required	Client Error
+412	Precondition Failed	Client Error
+413	Request Entity Too Large	Client Error
+414	Request-URI Too Long	Client Error
+415	Unsupported Media Type	Client Error
+416	Requested Range Not Satisfiable	Client Error
+417	Expectation Failed	Client Error
+500	Internal Server Error	Server Error
+501	Not Implemented	Server Error
+502	Bad Gateway	Server Error
+503	Service Unavailable	Server Error
+504	Gateway Timeout	Server Error
+505	HTTP Version Not Supported	Server Error
diff --git a/docs/templates/examples.py b/docs/templates/examples.py
new file mode 100644
index 0000000..d8738d4
--- /dev/null
+++ b/docs/templates/examples.py
@@ -0,0 +1,463 @@
+from typing import NamedTuple
+
+
+class Example(NamedTuple):
+    """
+    :param name: of example
+    :param iterations: number of iterations to run to capture output
+    :param fragment: code fragment for api example, excludes import dataspec and spec_builder.build()
+    :param pipes: extra args or command line pipes i.e. sort | tail -3, to add to command line
+    """
+    name: str
+    iterations: int
+    fragment: str
+    pipes: str = ""
+
+
+EXAMPLES = [
+    Example(
+        name="overview_example_one",
+        iterations=7,
+        fragment="""
+refs = spec_builder.refs()
+one = refs.values('ONE', ["A", "B", "C"])
+two = refs.values('TWO', [1, 2, 3])
+
+spec_builder.combine('combine', refs=[one, two])
+"""
+    ),
+    Example(
+        name="overview_example_two",
+        iterations=12,
+        fragment="""
+refs = spec_builder.refs()
+one = refs.values('ONE', ["A", "B", "C"])
+two = refs.values('TWO', [1, 2, 3, 4])
+
+spec_builder.combine('combine', refs=[one, two])
+""",
+        pipes="\\\n  | sort"),
+    Example(
+        name="overview_example_three",
+        iterations=5,
+        fragment="""
+refs = spec_builder.refs()
+one = refs.values('ONE', ["A", "B", "C"], sample=True)
+two = refs.values('TWO', [1, 2, 3, 4], sample="yes")
+
+spec_builder.combine('combine', refs=[one, two])
+""",
+    ),
+    Example(
+        name="values_shorthand_one",
+        iterations=5,
+        fragment="""
+spec_builder.add_field('field1', dataspec.builder.values([1, 2, 3, 4, 5]))
+spec_builder.add_field('field2', dataspec.builder.values({"A": 0.5, "B": 0.3, "C": 0.2}))
+spec_builder.add_field('field3', dataspec.builder.values("CONSTANT"))
+""",
+    ),
+    Example(
+        name="values_shorthand_two",
+        iterations=5,
+        fragment="""
+spec_builder.add_field('field1', [1, 2, 3, 4, 5])
+spec_builder.add_field('field2', {"A": 0.5, "B": 0.3, "C": 0.2})
+spec_builder.add_field('field3', "CONSTANT")
+""",
+    ),
+    Example(
+        name="inline_key_example",
+        iterations=5,
+        fragment="""
+spec_builder.add_field("network:ipv4?cidr=192.168.0.0/16", {})
+"""),
+    Example(
+        name="config_example_one",
+        iterations=5,
+        fragment="""
+spec_builder.values('ONE', [1, 2, 3], prefix='TEST', suffix='@DEMO')
+spec_builder.values('TWO?prefix=TEST&suffix=@DEMO', [1, 2, 3])
+"""),
+    Example(
+        name="common_config_example_one",
+        iterations=5,
+        fragment="""
+spec_builder.values('field', 
+                    ["world", "beautiful", "destiny"], 
+                    prefix='hello ')
+"""),
+    Example(
+        name="count_dist_example_one",
+        iterations=5,
+        fragment="""
+spec_builder.char_class(key='field',
+                        data='visible',
+                        count_dist='normal(mean=5, stddev=2, min=1, max=9)')
+"""),
+    Example(
+        name="constants_example_one",
+        iterations=5,
+        fragment="""
+spec_builder.values('constant1', 42)
+spec_builder.add_field('shorthand_constant', "This is simulated data and should not be used for nefarious purposes")
+"""),
+    Example(
+        name="list_values_example_one",
+        iterations=5,
+        fragment="""
+spec_builder.values('list1', [200, 202, 303, 400, 404, 500])
+spec_builder.add_field("shorthand_list",  [200, 202, 303, 400, 404, 500])
+spec_builder.add_field("random_pet?sample=true", ["dog", "cat", "bunny", "pig", "rhino", "hedgehog"])
+"""),
+    Example(
+        name="weighted_values_example_one",
+        iterations=5,
+        fragment="""
+spec_builder.values('weighted1', {
+        "200": 0.4, "202": 0.3, "303": 0.1,
+        "400": 0.05, "403": 0.05, "404": 0.05, "500": 0.05
+})
+spec_builder.add_field("shorthand_weighted", {
+        "200": 0.4, "202": 0.3, "303": 0.1,
+        "400": 0.05, "403": 0.05, "404": 0.05, "500": 0.05
+})
+"""),
+    Example(
+        name="sample_mode_example_one",
+        iterations=5,
+        fragment="""
+refs = spec_builder.refs()
+refs.add_field('ONE?sample=true', ["A", "B", "C"])
+refs.add_field('TWO?sample=true', [1, 2, 3, 4])
+
+spec_builder.combine('combine', refs=['ONE', 'TWO'])
+"""),
+    Example(
+        name="combine_spec_example_one",
+        iterations=5,
+        fragment="""
+refs = spec_builder.refs()
+first = refs.values(key="first",
+                    data=["zebra", "hedgehog", "llama", "flamingo"])
+last = refs.values(key="last",
+                   data=["jones", "smith", "williams"])
+
+spec_builder.combine('combine', refs=[first, last], join_with=" ")
+"""),
+    Example(
+        name="combine_list_spec_example_one",
+        iterations=5,
+        fragment="""
+refs = spec_builder.refs()
+first = refs.values(
+    key="first",
+    data=["zebra", "hedgehog", "llama", "flamingo"])
+last = refs.values(
+    key="last",
+    data=["jones", "smith", "williams"])
+middle = refs.values(
+    key="middle",
+    data=["cloud", "sage", "river"])
+middle_initial = refs.values(
+    key="middle_initial",
+    data={"a": 0.3, "m": 0.3, "j": 0.1, "l": 0.1, "e": 0.1, "w": 0.1})
+
+spec_builder.combine_list(
+    key='full_name',
+    refs=[
+        [first, last],
+        [first, middle, last],
+        [first, middle_initial, last],
+        ],
+    join_with=" ")
+"""),
+    Example(
+        name="uniform_date_example_exhaustive",
+        iterations=1000,
+        fragment="""
+spec_builder.add_field("dates:date?duration_days=90&start=15-Dec-2050 12:00&format=%d-%b-%Y %H:%M", {})
+""",
+        pipes="\\\n  | sort -t- -k3n -k2n -k1n | uniq | sed -n '1p;$p'"),
+    Example(
+        name="uniform_date_example_one",
+        iterations=1000,
+        fragment="""
+spec_builder.add_field("dates:date", {})
+""",
+        pipes="\\\n  | sort -t- -k3n -k2n -k1n | uniq | sed -n '1p;$p'"),
+    Example(
+        name="uniform_date_example_two",
+        iterations=1000,
+        fragment="""
+spec_builder.add_field("dates:date?offset=1", {})
+""",
+        pipes="\\\n  | sort -t- -k3n -k2n -k1n | uniq | sed -n '1p;$p'"),
+    Example(
+        name="uniform_date_example_three",
+        iterations=1000,
+        fragment="""
+spec_builder.add_field("dates:date?duration_days=1", {})
+""",
+        pipes="\\\n  | sort -t- -k3n -k2n -k1n | uniq | sed -n '1p;$p'"),
+    Example(
+        name="uniform_date_example_four",
+        iterations=1000,
+        fragment="""
+spec_builder.add_field("dates:date?duration_days=10", {})
+""",
+        pipes="\\\n  | sort -t- -k3n -k2n -k1n | uniq | sed -n '1p;$p'"),
+    Example(
+        name="uniform_date_example_five",
+        iterations=1000,
+        fragment="""
+spec_builder.add_field("dates:date?duration_days=1&offset=1", {})
+""",
+        pipes="\\\n  | sort -t- -k3n -k2n -k1n | uniq | sed -n '1p;$p'"),
+    Example(
+        name="uniform_date_example_six",
+        iterations=1000,
+        fragment="""
+spec_builder.add_field("dates:date?duration_days=1&offset=-1", {})
+""",
+        pipes="\\\n  | sort -t- -k3n -k2n -k1n | uniq | sed -n '1p;$p'"),
+    Example(
+        name="uniform_date_example_seven",
+        iterations=1000,
+        fragment="""
+spec_builder.add_field("dates:date?duration_days=1&offset=1&start=15-12-2050", {})
+""",
+        pipes="\\\n  | sort -t- -k3n -k2n -k1n | uniq | sed -n '1p;$p'"),
+    Example(
+        name="uniform_date_example_eight",
+        iterations=1000,
+        fragment="""
+spec_builder.add_field("dates:date?duration_days=1&start=15-Dec-2050 12:00&format=%d-%b-%Y %H:%M", {})
+""",
+        pipes="\\\n  | sort -t- -k3n -k2M -k1n | uniq | sed -n '1p;$p'"),
+    Example(
+        name="centered_date_example_exhaustive",
+        iterations=1000,
+        fragment="""
+spec_builder.add_field("dates:date?center_date=20500601 12:00&format=%Y%m%d %H:%M&stddev_days=2", {})
+""",
+        pipes="\\\n  | sort -n | uniq | sed -n '1p;$p'"),
+    Example(
+        name="centered_date_example_one",
+        iterations=1000,
+        fragment="""
+spec_builder.add_field("dates:date?stddev_days=1", {})
+""",
+        pipes="\\\n  | sort -t- -k3n -k2n -k1n | uniq | sed -n '1p;$p'"),
+    Example(
+        name="centered_date_example_two",
+        iterations=1000,
+        fragment="""
+spec_builder.add_field("dates:date?stddev_days=15", {})
+""",
+        pipes="\\\n  | sort -t- -k3n -k2n -k1n | uniq | sed -n '1p;$p'"),
+    Example(
+        name="range_spec_example_one",
+        iterations=5,
+        fragment="""
+spec_builder.range_spec(key="zero_to_ten", data=[0, 10, 0.5])
+spec_builder.add_field(key="range_shorthand1:range", spec={"data": [0, 10, 0.5]})
+spec_builder.add_field(key="range_shorthand2:range", spec=[0, 10, 0.5])
+"""),
+    Example(
+        name="range_spec_example_two",
+        iterations=5,
+        fragment="""
+spec_builder.range_spec(
+    key="salaries",
+    data=[
+      [1000, 10000, 1000],
+      [10000, 55000, 5000],
+      [55000, 155000, 10000]
+    ])
+"""),
+    Example(
+        name="rand_range_spec_example_one",
+        iterations=5,
+        fragment="""
+spec_builder.rand_range(
+    key="population",
+    data=[100, 1000],
+    cast="int")
+spec_builder.add_field("pop:rand_range?cast=f", [200.2, 1222.7, 2])
+""",
+        pipes=" --format json -x"),
+    Example(
+        name="uuid_spec_example_one",
+        iterations=5,
+        fragment="""
+spec_builder.uuid(key="id")
+spec_builder.add_field("id_shorthand:uuid", {})
+"""),
+    Example(
+        name="char_class_spec_example_one",
+        iterations=5,
+        fragment="""
+spec_builder.add_field("one_to_five_digits:cc-digits?min=1&max=5", {})
+"""),
+    Example(
+        name="char_class_spec_example_two",
+        iterations=10,
+        fragment="""
+spec_builder.char_class(
+    key="password",
+    data=[
+      "word",
+      "special",
+      "hex-lower",
+      "M4$p3c!@l$@uc3"
+    ],
+    mean=14,
+    stddev=2,
+    min=10,
+    max=18,
+    exclude=["-", "\\\""])
+"""),
+    Example(
+        name="unicode_range_example_one",
+        iterations=10,
+        fragment="""
+spec_builder.unicode_range("text", ["3040", "309f"], mean=5)
+"""),
+    Example(
+        name="geo_point_spec_example_one",
+        iterations=5,
+        fragment="""
+spec_builder.geo_pair("egypt", bbox=[31.33134, 22.03795, 34.19295, 25.00562], precision=3)
+"""),
+    Example(
+        name="ip_spec_example_one",
+        iterations=5,
+        fragment="""
+spec_builder.ipv4(key="network", cidr="2.22.222.0/16")
+spec_builder.add_field("network_shorthand:ip?cidr=2.22.222.0/16", {})
+spec_builder.add_field("network_with_base:ip?base=192.168.0", {})
+"""),
+    Example(
+        name="ip_precise_example_one",
+        iterations=5,
+        fragment="""
+spec_builder.add_field("network:ip.precise?cidr=10.0.0.0/8", {})
+"""),
+    Example(
+        name="ip_precise_example_two",
+        iterations=5,
+        fragment="""
+spec_builder.add_field("network:ip.precise?cidr=192.168.0.0/14&sample=true", {})
+"""),
+    Example(
+        name="ip_precise_example_three",
+        iterations=5,
+        fragment="""
+spec_builder.add_field("network:ip.precise?cidr=2.22.0.0/22", {})
+"""),
+    Example(
+        name="weighted_ref_example_one",
+        iterations=5,
+        fragment="""
+refs = spec_builder.refs()
+refs.add_field('GOOD_CODES', {"200": 0.5, "202": 0.3, "203": 0.1, "300": 0.1})
+refs.add_field('BAD_CODES', {"400": 0.5, "403": 0.3, "404": 0.1, "500": 0.1})
+
+spec_builder.weightedref('http_code', data={"GOOD_CODES": 0.7, "BAD_CODES": 0.3})
+"""),
+    Example(
+        name="select_list_example_one",
+        iterations=5,
+        fragment="""
+spec_builder.select_list_subset(
+    key="ingredients",
+    data=["onions", "mushrooms", "garlic", "bell peppers", "spinach", "potatoes", "carrots"],
+    mean=3,
+    stddev=1,
+    min=2,
+    max=4,
+    join_with=", ")
+"""),
+    Example(
+        name="select_list_example_two",
+        iterations=5,
+        fragment="""
+spec_builder.select_list_subset(
+    key="ingredients",
+    data=["onions", "mushrooms", "garlic", "bell peppers", "spinach", "potatoes", "carrots"],
+    mean=3,
+    stddev=1,
+    min=2,
+    max=4,
+    join_with="\\", \\"",
+    quote="\\"")
+"""),
+    Example(
+        name="csv_spec_example_one",
+        iterations=5,
+        fragment="""
+spec_builder.csv(
+    key="cities",
+    datafile="cities.csv",
+    delimiter="~",
+    sample=True)
+""",
+        pipes="--datadir ./data"),
+    Example(
+        name="csv_spec_example_two",
+        iterations=5,
+        fragment="""
+spec_builder.configref(
+    key="tabs_config",
+    datafile="tabs.csv",
+    delimiter="\\t",
+    headers=True)
+spec_builder.csv(
+    key="status",
+    column=1,
+    configref="tabs_config")
+spec_builder.csv(
+    key="description",
+    column=2,
+    configref="tabs_config")
+spec_builder.add_field("status_type:csv?configref=tabs_config&column=3", {})
+""",
+        pipes="--datadir ./data"),
+    Example(
+        name="csv_select_example_one",
+        iterations=5,
+        fragment="""
+spec_builder.csv_select(
+    key="placeholder",
+    data={
+        "geonameid": 1,
+        "name": 2,
+        "latitude": 5,
+        "longitude": 6,
+        "country_code": 9,
+        "population": 15
+    },
+    datafile="allCountries.txt",
+    headers=False,
+    delimiter="\t")
+""",
+        pipes="--datadir ./data"),
+    Example(
+        name="nested_example_one",
+        iterations=1,
+        fragment="""
+geo_fields = dataspec.spec_builder()
+geo_fields.add_field("place_id:cc-digits?mean=5", {})
+geo_fields.add_field("coordinates:geo.pair?as_list=true", {})
+
+user_fields = dataspec.spec_builder()
+user_fields.uuid("user_id")
+user_fields.nested("geo", geo_fields.build())
+
+spec_builder.uuid("id")
+spec_builder.nested("user", user_fields.build())
+""",
+        pipes="--format json-pretty"
+    ),
+]
diff --git a/docs/templates/field_specs_template_data.json b/docs/templates/field_specs_template_data.json
index 2602f2c..0be4177 100644
--- a/docs/templates/field_specs_template_data.json
+++ b/docs/templates/field_specs_template_data.json
@@ -1,142 +1,326 @@
 {
   "examples": {
     "overview_example_one": {
-      "json": "{\n  \"combine\": {\"type\": \"combine\", \"refs\": [\"ONE\", \"TWO\"]},\n  \"refs\": {\n    \"ONE\": [\"A\", \"B\", \"C\"],\n    \"TWO\": [1, 2, 3]\n  }\n}",
-      "yaml": "---\ncombine:\n  type: combine\n  refs: [ONE, TWO]\nrefs:\n  ONE: [A, B, C]\n  TWO: [1, 2, 3]"
+      "json": "{\n  \"combine\": {\n    \"type\": \"combine\",\n    \"refs\": [\"ONE\", \"TWO\"]\n  },\n  \"refs\": {\n    \"ONE\": {\n      \"type\": \"values\",\n      \"data\": [\"A\", \"B\", \"C\"]\n    },\n    \"TWO\": {\n      \"type\": \"values\",\n      \"data\": [1, 2, 3]\n    }\n  }\n}",
+      "yaml": "combine:\n  type: combine\n  refs: [ONE, TWO]\nrefs:\n  ONE:\n    type: values\n    data: [A, B, C]\n  TWO:\n    type: values\n    data: [1, 2, 3]",
+      "api": "import dataspec\n\nspec_builder = dataspec.spec_builder()\n\nrefs = spec_builder.refs()\none = refs.values('ONE', [\"A\", \"B\", \"C\"])\ntwo = refs.values('TWO', [1, 2, 3])\n\nspec_builder.combine('combine', refs=[one, two])\n\nspec = spec_builder.build()",
+      "command": "dataspec -s dataspec.json --log-level error -i 7",
+      "output": "A1\nB2\nC3\nA1\nB2\nC3\nA1"
     },
     "overview_example_two": {
-      "json": "{\n  \"combine\": {\"type\": \"combine\", \"refs\": [\"ONE\", \"TWO\"]},\n  \"refs\": {\n    \"ONE\": [\"A\", \"B\", \"C\"],\n    \"TWO\": [1, 2, 3, 4]\n  }\n}",
-      "yaml": "---\ncombine:\n  type: combine\n  refs: [ONE, TWO]\nrefs:\n  ONE: [A, B, C]\n  TWO: [1, 2, 3 ,4]"
+      "json": "{\n  \"combine\": {\n    \"type\": \"combine\",\n    \"refs\": [\"ONE\", \"TWO\"]\n  },\n  \"refs\": {\n    \"ONE\": {\n      \"type\": \"values\",\n      \"data\": [\"A\", \"B\", \"C\"]\n    },\n    \"TWO\": {\n      \"type\": \"values\",\n      \"data\": [1, 2, 3, 4]\n    }\n  }\n}",
+      "yaml": "combine:\n  type: combine\n  refs: [ONE, TWO]\nrefs:\n  ONE:\n    type: values\n    data: [A, B, C]\n  TWO:\n    type: values\n    data: [1, 2, 3, 4]",
+      "api": "import dataspec\n\nspec_builder = dataspec.spec_builder()\n\nrefs = spec_builder.refs()\none = refs.values('ONE', [\"A\", \"B\", \"C\"])\ntwo = refs.values('TWO', [1, 2, 3, 4])\n\nspec_builder.combine('combine', refs=[one, two])\n\nspec = spec_builder.build()",
+      "command": "dataspec -s dataspec.json --log-level error -i 12 \\\n  | sort",
+      "output": "A1\nA2\nA3\nA4\nB1\nB2\nB3\nB4\nC1\nC2\nC3\nC4"
     },
     "overview_example_three": {
-      "json": "{\n  \"combine\": {\"type\": \"combine\", \"refs\": [\"ONE\", \"TWO\"]},\n  \"refs\": {\n    \"ONE?sample=true\": [\"A\", \"B\", \"C\"],\n    \"TWO?sample=true\": [1, 2, 3, 4]\n  }\n}",
-      "yaml": "---\ncombine:\n  type: combine\n  refs: [ONE, TWO]\nrefs:\n  ONE?sample=true: [A, B, C]\n  TWO?sample=true: [1, 2, 3 ,4]"
+      "json": "{\n  \"combine\": {\n    \"type\": \"combine\",\n    \"refs\": [\"ONE\", \"TWO\"]\n  },\n  \"refs\": {\n    \"ONE\": {\n      \"type\": \"values\",\n      \"data\": [\"A\", \"B\", \"C\"],\n      \"config\": {\n        \"sample\": true\n      }\n    },\n    \"TWO\": {\n      \"type\": \"values\",\n      \"data\": [1, 2, 3, 4],\n      \"config\": {\n        \"sample\": \"yes\"\n      }\n    }\n  }\n}",
+      "yaml": "combine:\n  type: combine\n  refs: [ONE, TWO]\nrefs:\n  ONE:\n    type: values\n    data: [A, B, C]\n    config:\n      sample: true\n  TWO:\n    type: values\n    data: [1, 2, 3, 4]\n    config:\n      sample: 'yes'",
+      "api": "import dataspec\n\nspec_builder = dataspec.spec_builder()\n\nrefs = spec_builder.refs()\none = refs.values('ONE', [\"A\", \"B\", \"C\"], sample=True)\ntwo = refs.values('TWO', [1, 2, 3, 4], sample=\"yes\")\n\nspec_builder.combine('combine', refs=[one, two])\n\nspec = spec_builder.build()",
+      "command": "dataspec -s dataspec.json --log-level error -i 5",
+      "output": "C1\nB3\nA2\nB1\nA1"
     },
     "values_shorthand_one": {
-      "json": "{\n  \"field1\": {\"type\": \"vaules\", \"data\": [1, 2, 3, 4, 5]},\n  \"field2\": {\"type\": \"values\", \"data\": {\"A\": 0.5, \"B\": 0.3, \"C\": 0.2}},\n  \"field3\": {\"type\": \"values\", \"data\": \"CONSTANT\"}\n}",
-      "yaml": "---\nfield1:\n  type: vaules\n  data: [1, 2, 3, 4, 5]\nfield2:\n  type: values\n  data:\n    A: 0.5\n    B: 0.3\n    C: 0.2\nfield3:\n  type: values\n  data: CONSTANT"
+      "json": "{\n  \"field1\": {\n    \"type\": \"values\",\n    \"data\": [1, 2, 3, 4, 5]\n  },\n  \"field2\": {\n    \"type\": \"values\",\n    \"data\": {\"A\": 0.5, \"B\": 0.3, \"C\": 0.2}\n  },\n  \"field3\": {\n    \"type\": \"values\",\n    \"data\": \"CONSTANT\"\n  }\n}",
+      "yaml": "field1:\n  type: values\n  data: [1, 2, 3, 4, 5]\nfield2:\n  type: values\n  data: {A: 0.5, B: 0.3, C: 0.2}\nfield3:\n  type: values\n  data: CONSTANT",
+      "api": "import dataspec\n\nspec_builder = dataspec.spec_builder()\n\nspec_builder.add_field('field1', dataspec.builder.values([1, 2, 3, 4, 5]))\nspec_builder.add_field('field2', dataspec.builder.values({\"A\": 0.5, \"B\": 0.3, \"C\": 0.2}))\nspec_builder.add_field('field3', dataspec.builder.values(\"CONSTANT\"))\n\nspec = spec_builder.build()",
+      "command": "dataspec -s dataspec.json --log-level error -i 5",
+      "output": "1\nA\nCONSTANT\n2\nA\nCONSTANT\n3\nB\nCONSTANT\n4\nA\nCONSTANT\n5\nC\nCONSTANT"
     },
     "values_shorthand_two": {
-      "json": "{\n  \"field1\": [1, 2, 3, 4, 5],\n  \"field2\": {\"A\": 0.5, \"B\": 0.3, \"C\": 0.2},\n  \"field3\": \"CONSTANT\"\n}",
-      "yaml": "---\nfield1: [1, 2, 3, 4, 5]\nfield2:\n  A: 0.5\n  B: 0.3\n  C: 0.2\nfield3: CONSTANT"
+      "json": "{\n  \"field1\": [1, 2, 3, 4, 5],\n  \"field2\": {\n    \"A\": 0.5,\n    \"B\": 0.3,\n    \"C\": 0.2\n  },\n  \"field3\": \"CONSTANT\"\n}",
+      "yaml": "field1: [1, 2, 3, 4, 5]\nfield2:\n  A: 0.5\n  B: 0.3\n  C: 0.2\nfield3: CONSTANT",
+      "api": "import dataspec\n\nspec_builder = dataspec.spec_builder()\n\nspec_builder.add_field('field1', [1, 2, 3, 4, 5])\nspec_builder.add_field('field2', {\"A\": 0.5, \"B\": 0.3, \"C\": 0.2})\nspec_builder.add_field('field3', \"CONSTANT\")\n\nspec = spec_builder.build()",
+      "command": "dataspec -s dataspec.json --log-level error -i 5",
+      "output": "1\nA\nCONSTANT\n2\nC\nCONSTANT\n3\nA\nCONSTANT\n4\nA\nCONSTANT\n5\nB\nCONSTANT"
     },
     "inline_key_example": {
       "json": "{\n  \"network:ipv4?cidr=192.168.0.0/16\": {}\n}",
-      "yaml": "---\nnetwork:ipv4?cidr=192.168.0.0/16: {}"
+      "yaml": "network:ipv4?cidr=192.168.0.0/16: {}",
+      "api": "import dataspec\n\nspec_builder = dataspec.spec_builder()\n\nspec_builder.add_field(\"network:ipv4?cidr=192.168.0.0/16\", {})\n\nspec = spec_builder.build()",
+      "command": "dataspec --inline '{\"network:ipv4?cidr=192.168.0.0/16\": {}}' --log-level error -i 5",
+      "output": "192.168.14.9\n192.168.161.229\n192.168.96.176\n192.168.114.209\n192.168.61.146"
     },
     "config_example_one": {
-      "json": "{\n  \"ONE\": {\n    \"type\": \"values\",\n    \"data\": [1, 2, 3],\n    \"config\": {\"prefix\": \"TEST\", \"suffix\": \"@DEMO\"}\n  },\n  \"TWO?prefix=TEST&suffix=@DEMO\": [1, 2, 3]\n}",
-      "yaml": "---\nONE:\n  type: values\n  config:\n    prefix: TEST\n    suffix: '@DEMO'\n  data: [1, 2, 3]\nTWO?prefix=TEST&suffix=@DEMO: [1, 2, 3]"
+      "json": "{\n  \"ONE\": {\n    \"type\": \"values\",\n    \"data\": [1, 2, 3],\n    \"config\": {\n      \"prefix\": \"TEST\",\n      \"suffix\": \"@DEMO\"\n    }\n  },\n  \"TWO?prefix=TEST&suffix=@DEMO\": {\n    \"type\": \"values\",\n    \"data\": [1, 2, 3]\n  }\n}",
+      "yaml": "ONE:\n  type: values\n  data: [1, 2, 3]\n  config:\n    prefix: TEST\n    suffix: '@DEMO'\nTWO?prefix=TEST&suffix=@DEMO:\n  type: values\n  data: [1, 2, 3]",
+      "api": "import dataspec\n\nspec_builder = dataspec.spec_builder()\n\nspec_builder.values('ONE', [1, 2, 3], prefix='TEST', suffix='@DEMO')\nspec_builder.values('TWO?prefix=TEST&suffix=@DEMO', [1, 2, 3])\n\nspec = spec_builder.build()",
+      "command": "dataspec -s dataspec.json --log-level error -i 5",
+      "output": "TEST1@DEMO\nTEST1@DEMO\nTEST2@DEMO\nTEST2@DEMO\nTEST3@DEMO\nTEST3@DEMO\nTEST1@DEMO\nTEST1@DEMO\nTEST2@DEMO\nTEST2@DEMO"
     },
     "common_config_example_one": {
-      "json": "{\n  \"field\": {\n      \"type\": \"values\",\n      \"config\": {\n        \"prefix\": \"Hello \"\n      },\n      \"data\": [\"world\", \"beautiful\", \"destiny\"]\n    }\n}",
-      "yaml": "---\nfield:\n  type: values\n  config:\n    prefix: 'Hello '\n  data: [world, beautiful, destiny]"
+      "json": "{\n  \"field\": {\n    \"type\": \"values\",\n    \"data\": [\"world\", \"beautiful\", \"destiny\"],\n    \"config\": {\n      \"prefix\": \"hello \"\n    }\n  }\n}",
+      "yaml": "field:\n  type: values\n  data: [world, beautiful, destiny]\n  config:\n    prefix: 'hello '",
+      "api": "import dataspec\n\nspec_builder = dataspec.spec_builder()\n\nspec_builder.values('field', \n                    [\"world\", \"beautiful\", \"destiny\"], \n                    prefix='hello ')\n\nspec = spec_builder.build()",
+      "command": "dataspec -s dataspec.json --log-level error -i 5",
+      "output": "hello world\nhello beautiful\nhello destiny\nhello world\nhello beautiful"
+    },
+    "count_dist_example_one": {
+      "json": "{\n  \"field\": {\n    \"type\": \"char_class\",\n    \"data\": \"visible\",\n    \"config\": {\n      \"count_dist\": \"normal(mean=5, stddev=2, min=1, max=9)\"\n    }\n  }\n}",
+      "yaml": "field:\n  type: char_class\n  data: visible\n  config:\n    count_dist: normal(mean=5, stddev=2, min=1, max=9)",
+      "api": "import dataspec\n\nspec_builder = dataspec.spec_builder()\n\nspec_builder.char_class(key='field',\n                        data='visible',\n                        count_dist='normal(mean=5, stddev=2, min=1, max=9)')\n\nspec = spec_builder.build()",
+      "command": "dataspec -s dataspec.json --log-level error -i 5",
+      "output": "vp}MHZh\n{^6_@\n$dY@:\n]\n);`~AQz"
     },
     "constants_example_one": {
-      "json": "{\n  \"constant1\": {\"type\": \"values\", \"data\": 42},\n  \"shorthand_constant\": \"This is simulated data and should not be used for nefarious purposes\"\n}",
-      "yaml": "constant1:\n  type: values\n  data: 42\nshorthand_constant: This is simulated data and should not be used for nefarious purposes"
+      "json": "{\n  \"constant1\": {\n    \"type\": \"values\",\n    \"data\": 42\n  },\n  \"shorthand_constant\": \"This is simulated data and should not be used for nefarious purposes\"\n}",
+      "yaml": "constant1:\n  type: values\n  data: 42\nshorthand_constant: This is simulated data and should not be used for nefarious purposes",
+      "api": "import dataspec\n\nspec_builder = dataspec.spec_builder()\n\nspec_builder.values('constant1', 42)\nspec_builder.add_field('shorthand_constant', \"This is simulated data and should not be used for nefarious purposes\")\n\nspec = spec_builder.build()",
+      "command": "dataspec -s dataspec.json --log-level error -i 5",
+      "output": "42\nThis is simulated data and should not be used for nefarious purposes\n42\nThis is simulated data and should not be used for nefarious purposes\n42\nThis is simulated data and should not be used for nefarious purposes\n42\nThis is simulated data and should not be used for nefarious purposes\n42\nThis is simulated data and should not be used for nefarious purposes"
     },
     "list_values_example_one": {
-      "json": "{\n  \"list1\": {\"type\": \"values\", \"data\": [200, 202, 303, 400, 404, 500]},\n  \"shorthand_list\":  [200, 202, 303, 400, 404, 500],\n  \"random_pet?sample=true\": [\"dog\", \"cat\", \"bunny\", \"pig\", \"rhino\", \"hedgehog\"]\n}",
-      "yaml": "---\nlist1:\n  type: values\n  data: [200, 202, 303, 400, 404, 500]\nshorthand_list: [200, 202, 303, 400, 404, 500]\nrandom_pet?sample=true: [dog, cat, bunny, pig, rhino, hedgehog]"
+      "json": "{\n  \"list1\": {\n    \"type\": \"values\",\n    \"data\": [200, 202, 303, 400, 404, 500]\n  },\n  \"shorthand_list\": [200, 202, 303, 400, 404, 500],\n  \"random_pet?sample=true\": [\"dog\", \"cat\", \"bunny\", \"pig\", \"rhino\", \"hedgehog\"]\n}",
+      "yaml": "list1:\n  type: values\n  data: [200, 202, 303, 400, 404, 500]\nshorthand_list: [200, 202, 303, 400, 404, 500]\nrandom_pet?sample=true: [dog, cat, bunny, pig, rhino, hedgehog]",
+      "api": "import dataspec\n\nspec_builder = dataspec.spec_builder()\n\nspec_builder.values('list1', [200, 202, 303, 400, 404, 500])\nspec_builder.add_field(\"shorthand_list\",  [200, 202, 303, 400, 404, 500])\nspec_builder.add_field(\"random_pet?sample=true\", [\"dog\", \"cat\", \"bunny\", \"pig\", \"rhino\", \"hedgehog\"])\n\nspec = spec_builder.build()",
+      "command": "dataspec -s dataspec.json --log-level error -i 5",
+      "output": "200\n200\nrhino\n202\n202\npig\n303\n303\ndog\n400\n400\npig\n404\n404\ncat"
     },
     "weighted_values_example_one": {
-      "json": "{\n  \"weighted1\": {\n    \"type\": \"values\",\n    \"data\": {\n      \"200\": 0.4, \"202\": 0.3, \"303\": 0.1,\n      \"400\": 0.05, \"403\": 0.05, \"404\": 0.05, \"500\": 0.05\n    }\n  },\n  \"shorthand_weighted\": {\n    \"200\": 0.4, \"202\": 0.3, \"303\": 0.1,\n    \"400\": 0.05, \"403\": 0.05, \"404\": 0.05, \"500\": 0.05\n  }\n}",
-      "yaml": "---\nweighted1:\n  type: values\n  data:\n    '200': 0.4\n    '202': 0.3\n    '303': 0.1\n    '400': 0.05\n    '403': 0.05\n    '404': 0.05\n    '500': 0.05\nshorthand_weighted:\n  '200': 0.4\n  '202': 0.3\n  '303': 0.1\n  '400': 0.05\n  '403': 0.05\n  '404': 0.05\n  '500': 0.05"
+      "json": "{\n  \"weighted1\": {\n    \"type\": \"values\",\n    \"data\": {\"200\": 0.4, \"202\": 0.3, \"303\": 0.1, \"400\": 0.05, \"403\": 0.05, \"404\": 0.05, \"500\": 0.05}\n  },\n  \"shorthand_weighted\": {\n    \"200\": 0.4,\n    \"202\": 0.3,\n    \"303\": 0.1,\n    \"400\": 0.05,\n    \"403\": 0.05,\n    \"404\": 0.05,\n    \"500\": 0.05\n  }\n}",
+      "yaml": "weighted1:\n  type: values\n  data: {'200': 0.4, '202': 0.3, '303': 0.1, '400': 0.05, '403': 0.05, '404': 0.05, '500': 0.05}\nshorthand_weighted:\n  '200': 0.4\n  '202': 0.3\n  '303': 0.1\n  '400': 0.05\n  '403': 0.05\n  '404': 0.05\n  '500': 0.05",
+      "api": "import dataspec\n\nspec_builder = dataspec.spec_builder()\n\nspec_builder.values('weighted1', {\n        \"200\": 0.4, \"202\": 0.3, \"303\": 0.1,\n        \"400\": 0.05, \"403\": 0.05, \"404\": 0.05, \"500\": 0.05\n})\nspec_builder.add_field(\"shorthand_weighted\", {\n        \"200\": 0.4, \"202\": 0.3, \"303\": 0.1,\n        \"400\": 0.05, \"403\": 0.05, \"404\": 0.05, \"500\": 0.05\n})\n\nspec = spec_builder.build()",
+      "command": "dataspec -s dataspec.json --log-level error -i 5",
+      "output": "200\n202\n200\n403\n202\n202\n202\n200\n200\n400"
     },
     "sample_mode_example_one": {
-      "json": "{\n  \"combine\": {\"type\": \"combine\", \"refs\": [\"ONE\", \"TWO\"]},\n  \"refs\": {\n    \"ONE?sample=true\": [\"A\", \"B\", \"C\"],\n    \"TWO?sample=true\": [1, 2, 3]\n  }\n}",
-      "yaml": "---\ncombine:\n  type: combine\n  refs:\n  - ONE\n  - TWO\nrefs:\n  ONE?sample=true: [A, B, C]\n  TWO?sample=true: [1, 2, 3]"
+      "json": "{\n  \"combine\": {\n    \"type\": \"combine\",\n    \"refs\": [\"ONE\", \"TWO\"]\n  },\n  \"refs\": {\n    \"ONE?sample=true\": [\"A\", \"B\", \"C\"],\n    \"TWO?sample=true\": [1, 2, 3, 4]\n  }\n}",
+      "yaml": "combine:\n  type: combine\n  refs: [ONE, TWO]\nrefs:\n  ONE?sample=true: [A, B, C]\n  TWO?sample=true: [1, 2, 3, 4]",
+      "api": "import dataspec\n\nspec_builder = dataspec.spec_builder()\n\nrefs = spec_builder.refs()\nrefs.add_field('ONE?sample=true', [\"A\", \"B\", \"C\"])\nrefs.add_field('TWO?sample=true', [1, 2, 3, 4])\n\nspec_builder.combine('combine', refs=['ONE', 'TWO'])\n\nspec = spec_builder.build()",
+      "command": "dataspec -s dataspec.json --log-level error -i 5",
+      "output": "A1\nC1\nA1\nC4\nC4"
     },
     "combine_spec_example_one": {
-      "json": "{\n  \"full name\": {\n    \"type\": \"combine\",\n    \"refs\": [\"first\", \"last\"],\n    \"config\": {\"join_with\": \" \"}\n  },\n  \"refs\": {\n    \"first\": {\n      \"type\": \"values\",\n      \"data\": [\"zebra\", \"hedgehog\", \"llama\", \"flamingo\"]\n    },\n    \"last\": {\n      \"type\": \"values\",\n      \"data\": [\"jones\", \"smith\", \"williams\"]\n    }\n  }\n}",
-      "yaml": "---\nfull name:\n  type: combine\n  refs:\n  - first\n  - last\n  config:\n    join_with: ' '\nrefs:\n  first:\n    type: values\n    data: [zebra, hedgehog, llama, flamingo]\n  last:\n    type: values\n    data: [jones, smith, williams]"
+      "json": "{\n  \"combine\": {\n    \"type\": \"combine\",\n    \"refs\": [\"first\", \"last\"],\n    \"config\": {\n      \"join_with\": \" \"\n    }\n  },\n  \"refs\": {\n    \"first\": {\n      \"type\": \"values\",\n      \"data\": [\"zebra\", \"hedgehog\", \"llama\", \"flamingo\"]\n    },\n    \"last\": {\n      \"type\": \"values\",\n      \"data\": [\"jones\", \"smith\", \"williams\"]\n    }\n  }\n}",
+      "yaml": "combine:\n  type: combine\n  refs: [first, last]\n  config:\n    join_with: ' '\nrefs:\n  first:\n    type: values\n    data: [zebra, hedgehog, llama, flamingo]\n  last:\n    type: values\n    data: [jones, smith, williams]",
+      "api": "import dataspec\n\nspec_builder = dataspec.spec_builder()\n\nrefs = spec_builder.refs()\nfirst = refs.values(key=\"first\",\n                    data=[\"zebra\", \"hedgehog\", \"llama\", \"flamingo\"])\nlast = refs.values(key=\"last\",\n                   data=[\"jones\", \"smith\", \"williams\"])\n\nspec_builder.combine('combine', refs=[first, last], join_with=\" \")\n\nspec = spec_builder.build()",
+      "command": "dataspec -s dataspec.json --log-level error -i 5",
+      "output": "zebra jones\nhedgehog smith\nllama williams\nflamingo jones\nzebra smith"
     },
     "combine_list_spec_example_one": {
-      "json": "{\n  \"full name\": {\n    \"type\": \"combine-list\",\n    \"refs\": [\n      [\"first\", \"last\"],\n      [\"first\", \"middle\", \"last\"],\n      [\"first\", \"middle_initial\", \"last\"]\n    ],\n    \"config\": {\"join_with\": \" \"}\n  },\n  \"refs\": {\n    \"first\": [\"zebra\", \"hedgehog\", \"llama\", \"flamingo\"],\n    \"last\": [\"jones\", \"smith\", \"williams\"],\n    \"middle\": [\"cloud\", \"sage\", \"river\"],\n    \"middle_initial\": {\"a\": 0.3, \"m\": 0.3, \"j\": 0.1, \"l\": 0.1, \"e\": 0.1, \"w\": 0.1}\n  }\n}",
-      "yaml": "---\nfull name:\n  type: combine-list\n  refs: [\n    [first, last],\n    [first, middle, last],\n    [first, middle_initial, last]\n  ]\n  config:\n    join_with: ' '\nrefs:\n  first: [zebra, hedgehog, llama, flamingo]\n  last: [jones, smith, williams]\n  middle: [cloud, sage, river]\n  middle_initial:\n    a: 0.3\n    m: 0.3\n    j: 0.1\n    l: 0.1\n    e: 0.1\n    w: 0.1"
+      "json": "{\n  \"full_name\": {\n    \"type\": \"combine-list\",\n    \"refs\": [\n      [\"first\", \"last\"],\n      [\"first\", \"middle\", \"last\"],\n      [\"first\", \"middle_initial\", \"last\"]\n    ],\n    \"config\": {\n      \"join_with\": \" \"\n    }\n  },\n  \"refs\": {\n    \"first\": {\n      \"type\": \"values\",\n      \"data\": [\"zebra\", \"hedgehog\", \"llama\", \"flamingo\"]\n    },\n    \"last\": {\n      \"type\": \"values\",\n      \"data\": [\"jones\", \"smith\", \"williams\"]\n    },\n    \"middle\": {\n      \"type\": \"values\",\n      \"data\": [\"cloud\", \"sage\", \"river\"]\n    },\n    \"middle_initial\": {\n      \"type\": \"values\",\n      \"data\": {\"a\": 0.3, \"m\": 0.3, \"j\": 0.1, \"l\": 0.1, \"e\": 0.1, \"w\": 0.1}\n    }\n  }\n}",
+      "yaml": "full_name:\n  type: combine-list\n  refs:\n  - [first, last]\n  - [first, middle, last]\n  - [first, middle_initial, last]\n  config:\n    join_with: ' '\nrefs:\n  first:\n    type: values\n    data: [zebra, hedgehog, llama, flamingo]\n  last:\n    type: values\n    data: [jones, smith, williams]\n  middle:\n    type: values\n    data: [cloud, sage, river]\n  middle_initial:\n    type: values\n    data: {a: 0.3, m: 0.3, j: 0.1, l: 0.1, e: 0.1, w: 0.1}",
+      "api": "import dataspec\n\nspec_builder = dataspec.spec_builder()\n\nrefs = spec_builder.refs()\nfirst = refs.values(\n    key=\"first\",\n    data=[\"zebra\", \"hedgehog\", \"llama\", \"flamingo\"])\nlast = refs.values(\n    key=\"last\",\n    data=[\"jones\", \"smith\", \"williams\"])\nmiddle = refs.values(\n    key=\"middle\",\n    data=[\"cloud\", \"sage\", \"river\"])\nmiddle_initial = refs.values(\n    key=\"middle_initial\",\n    data={\"a\": 0.3, \"m\": 0.3, \"j\": 0.1, \"l\": 0.1, \"e\": 0.1, \"w\": 0.1})\n\nspec_builder.combine_list(\n    key='full_name',\n    refs=[\n        [first, last],\n        [first, middle, last],\n        [first, middle_initial, last],\n        ],\n    join_with=\" \")\n\nspec = spec_builder.build()",
+      "command": "dataspec -s dataspec.json --log-level error -i 5",
+      "output": "zebra jones\nzebra cloud jones\nzebra a jones\nhedgehog smith\nhedgehog sage smith"
+    },
+    "uniform_date_example_exhaustive": {
+      "json": "{\n  \"dates:date?duration_days=90&start=15-Dec-2050 12:00&format=%d-%b-%Y %H:%M\": {}\n}",
+      "yaml": "dates:date?duration_days=90&start=15-Dec-2050 12:00&format=%d-%b-%Y %H:%M: {}",
+      "api": "import dataspec\n\nspec_builder = dataspec.spec_builder()\n\nspec_builder.add_field(\"dates:date?duration_days=90&start=15-Dec-2050 12:00&format=%d-%b-%Y %H:%M\", {})\n\nspec = spec_builder.build()",
+      "command": "dataspec -s dataspec.json --log-level error -i 1000 \\\n  | sort -t- -k3n -k2n -k1n | uniq | sed -n '1p;$p'",
+      "output": "15-Dec-2050 13:41\n31-Jan-2051 23:32"
+    },
+    "uniform_date_example_one": {
+      "json": "{\n  \"dates:date\": {}\n}",
+      "yaml": "dates:date: {}",
+      "api": "import dataspec\n\nspec_builder = dataspec.spec_builder()\n\nspec_builder.add_field(\"dates:date\", {})\n\nspec = spec_builder.build()",
+      "command": "dataspec --inline '{\"dates:date\": {}}' --log-level error -i 1000 \\\n  | sort -t- -k3n -k2n -k1n | uniq | sed -n '1p;$p'",
+      "output": "02-07-2021\n01-08-2021"
+    },
+    "uniform_date_example_two": {
+      "json": "{\n  \"dates:date?offset=1\": {}\n}",
+      "yaml": "dates:date?offset=1: {}",
+      "api": "import dataspec\n\nspec_builder = dataspec.spec_builder()\n\nspec_builder.add_field(\"dates:date?offset=1\", {})\n\nspec = spec_builder.build()",
+      "command": "dataspec --inline '{\"dates:date?offset=1\": {}}' --log-level error -i 1000 \\\n  | sort -t- -k3n -k2n -k1n | uniq | sed -n '1p;$p'",
+      "output": "01-07-2021\n31-07-2021"
+    },
+    "uniform_date_example_three": {
+      "json": "{\n  \"dates:date?duration_days=1\": {}\n}",
+      "yaml": "dates:date?duration_days=1: {}",
+      "api": "import dataspec\n\nspec_builder = dataspec.spec_builder()\n\nspec_builder.add_field(\"dates:date?duration_days=1\", {})\n\nspec = spec_builder.build()",
+      "command": "dataspec --inline '{\"dates:date?duration_days=1\": {}}' --log-level error -i 1000 \\\n  | sort -t- -k3n -k2n -k1n | uniq | sed -n '1p;$p'",
+      "output": "02-07-2021\n03-07-2021"
+    },
+    "uniform_date_example_four": {
+      "json": "{\n  \"dates:date?duration_days=10\": {}\n}",
+      "yaml": "dates:date?duration_days=10: {}",
+      "api": "import dataspec\n\nspec_builder = dataspec.spec_builder()\n\nspec_builder.add_field(\"dates:date?duration_days=10\", {})\n\nspec = spec_builder.build()",
+      "command": "dataspec --inline '{\"dates:date?duration_days=10\": {}}' --log-level error -i 1000 \\\n  | sort -t- -k3n -k2n -k1n | uniq | sed -n '1p;$p'",
+      "output": "02-07-2021\n12-07-2021"
+    },
+    "uniform_date_example_five": {
+      "json": "{\n  \"dates:date?duration_days=1&offset=1\": {}\n}",
+      "yaml": "dates:date?duration_days=1&offset=1: {}",
+      "api": "import dataspec\n\nspec_builder = dataspec.spec_builder()\n\nspec_builder.add_field(\"dates:date?duration_days=1&offset=1\", {})\n\nspec = spec_builder.build()",
+      "command": "dataspec --inline '{\"dates:date?duration_days=1&offset=1\": {}}' --log-level error -i 1000 \\\n  | sort -t- -k3n -k2n -k1n | uniq | sed -n '1p;$p'",
+      "output": "01-07-2021\n02-07-2021"
+    },
+    "uniform_date_example_six": {
+      "json": "{\n  \"dates:date?duration_days=1&offset=-1\": {}\n}",
+      "yaml": "dates:date?duration_days=1&offset=-1: {}",
+      "api": "import dataspec\n\nspec_builder = dataspec.spec_builder()\n\nspec_builder.add_field(\"dates:date?duration_days=1&offset=-1\", {})\n\nspec = spec_builder.build()",
+      "command": "dataspec --inline '{\"dates:date?duration_days=1&offset=-1\": {}}' --log-level error -i 1000 \\\n  | sort -t- -k3n -k2n -k1n | uniq | sed -n '1p;$p'",
+      "output": "03-07-2021\n04-07-2021"
+    },
+    "uniform_date_example_seven": {
+      "json": "{\n  \"dates:date?duration_days=1&offset=1&start=15-12-2050\": {}\n}",
+      "yaml": "dates:date?duration_days=1&offset=1&start=15-12-2050: {}",
+      "api": "import dataspec\n\nspec_builder = dataspec.spec_builder()\n\nspec_builder.add_field(\"dates:date?duration_days=1&offset=1&start=15-12-2050\", {})\n\nspec = spec_builder.build()",
+      "command": "dataspec --inline '{\"dates:date?duration_days=1&offset=1&start=15-12-2050\": {}}' --log-level error -i 1000 \\\n  | sort -t- -k3n -k2n -k1n | uniq | sed -n '1p;$p'",
+      "output": "14-12-2050\n14-12-2050"
+    },
+    "uniform_date_example_eight": {
+      "json": "{\n  \"dates:date?duration_days=1&start=15-Dec-2050 12:00&format=%d-%b-%Y %H:%M\": {}\n}",
+      "yaml": "dates:date?duration_days=1&start=15-Dec-2050 12:00&format=%d-%b-%Y %H:%M: {}",
+      "api": "import dataspec\n\nspec_builder = dataspec.spec_builder()\n\nspec_builder.add_field(\"dates:date?duration_days=1&start=15-Dec-2050 12:00&format=%d-%b-%Y %H:%M\", {})\n\nspec = spec_builder.build()",
+      "command": "dataspec --inline '{\"dates:date?duration_days=1&start=15-Dec-2050 12:00&format=%d-%b-%Y %H:%M\": {}}' --log-level error -i 1000 \\\n  | sort -t- -k3n -k2M -k1n | uniq | sed -n '1p;$p'",
+      "output": "15-Dec-2050 12:00\n16-Dec-2050 11:58"
+    },
+    "centered_date_example_exhaustive": {
+      "json": "{\n  \"dates:date?center_date=20500601 12:00&format=%Y%m%d %H:%M&stddev_days=2\": {}\n}",
+      "yaml": "dates:date?center_date=20500601 12:00&format=%Y%m%d %H:%M&stddev_days=2: {}",
+      "api": "import dataspec\n\nspec_builder = dataspec.spec_builder()\n\nspec_builder.add_field(\"dates:date?center_date=20500601 12:00&format=%Y%m%d %H:%M&stddev_days=2\", {})\n\nspec = spec_builder.build()",
+      "command": "dataspec --inline '{\"dates:date?center_date=20500601 12:00&format=%Y%m%d %H:%M&stddev_days=2\": {}}' --log-level error -i 1000 \\\n  | sort -n | uniq | sed -n '1p;$p'",
+      "output": "20500525 20:43\n20500607 00:36"
+    },
+    "centered_date_example_one": {
+      "json": "{\n  \"dates:date?stddev_days=1\": {}\n}",
+      "yaml": "dates:date?stddev_days=1: {}",
+      "api": "import dataspec\n\nspec_builder = dataspec.spec_builder()\n\nspec_builder.add_field(\"dates:date?stddev_days=1\", {})\n\nspec = spec_builder.build()",
+      "command": "dataspec --inline '{\"dates:date?stddev_days=1\": {}}' --log-level error -i 1000 \\\n  | sort -t- -k3n -k2n -k1n | uniq | sed -n '1p;$p'",
+      "output": "29-06-2021\n05-07-2021"
+    },
+    "centered_date_example_two": {
+      "json": "{\n  \"dates:date?stddev_days=15\": {}\n}",
+      "yaml": "dates:date?stddev_days=15: {}",
+      "api": "import dataspec\n\nspec_builder = dataspec.spec_builder()\n\nspec_builder.add_field(\"dates:date?stddev_days=15\", {})\n\nspec = spec_builder.build()",
+      "command": "dataspec --inline '{\"dates:date?stddev_days=15\": {}}' --log-level error -i 1000 \\\n  | sort -t- -k3n -k2n -k1n | uniq | sed -n '1p;$p'",
+      "output": "19-05-2021\n16-08-2021"
     },
     "range_spec_example_one": {
-      "json": "{\n  \"zero_to_ten\": {\n    \"type\": \"range\",\n    \"data\": [0, 10, 0.5]\n  },\n  \"range_shorthand1:range\": {\"data\": [0, 10, 0.5]},\n  \"range_shorthand2:range\": [0, 10, 0.5]\n}",
-      "yaml": "---\nrange_shorthand1:range:\n  data:\n  - 0\n  - 10\n  - 0.5\nrange_shorthand2:range:\n- 0\n- 10\n- 0.5\nzero_to_ten:\n  data:\n  - 0\n  - 10\n  - 0.5\n  type: range"
+      "json": "{\n  \"zero_to_ten\": {\n    \"type\": \"range\",\n    \"data\": [0, 10, 0.5]\n  },\n  \"range_shorthand1:range\": {\n    \"data\": [0, 10, 0.5]\n  },\n  \"range_shorthand2:range\": [0, 10, 0.5]\n}",
+      "yaml": "zero_to_ten:\n  type: range\n  data: [0, 10, 0.5]\nrange_shorthand1:range:\n  data: [0, 10, 0.5]\nrange_shorthand2:range: [0, 10, 0.5]",
+      "api": "import dataspec\n\nspec_builder = dataspec.spec_builder()\n\nspec_builder.range_spec(key=\"zero_to_ten\", data=[0, 10, 0.5])\nspec_builder.add_field(key=\"range_shorthand1:range\", spec={\"data\": [0, 10, 0.5]})\nspec_builder.add_field(key=\"range_shorthand2:range\", spec=[0, 10, 0.5])\n\nspec = spec_builder.build()",
+      "command": "dataspec -s dataspec.json --log-level error -i 5",
+      "output": "0.0\n0.0\n0.0\n0.5\n0.5\n0.5\n1.0\n1.0\n1.0\n1.5\n1.5\n1.5\n2.0\n2.0\n2.0"
     },
     "range_spec_example_two": {
-      "json": "{\n  \"salaries\": {\n    \"type\": \"range\",\n    \"data\": [\n      [1000, 10000, 1000],\n      [10000, 55000, 5000],\n      [55000, 155000, 10000]\n    ]\n  }\n}",
-      "yaml": "---\nsalaries:\n  type: range\n  data: [\n    [1000, 10000, 1000],\n    [10000, 55000, 5000],\n    [55000, 155000, 10000]\n  ]"
+      "json": "{\n  \"salaries\": {\n    \"type\": \"range\",\n    \"data\": [[1000, 10000, 1000], [10000, 55000, 5000], [55000, 155000, 10000]]\n  }\n}",
+      "yaml": "salaries:\n  type: range\n  data: [[1000, 10000, 1000], [10000, 55000, 5000], [55000, 155000, 10000]]",
+      "api": "import dataspec\n\nspec_builder = dataspec.spec_builder()\n\nspec_builder.range_spec(\n    key=\"salaries\",\n    data=[\n      [1000, 10000, 1000],\n      [10000, 55000, 5000],\n      [55000, 155000, 10000]\n    ])\n\nspec = spec_builder.build()",
+      "command": "dataspec -s dataspec.json --log-level error -i 5",
+      "output": "1000\n10000\n55000\n2000\n15000"
     },
     "rand_range_spec_example_one": {
-      "json": "{\n  \"population\": {\n    \"type\": \"rand_range\",\n    \"data\": [100, 1000],\n    \"config\": {\"cast\": \"int\"}\n  },\n  \"pop:rand_range?cast=f\": [200.2, 1222.7, 2]\n}",
-      "yaml": "---\npopulation:\n  type: rand_range\n  data: [100, 1000]\n  config:\n    cast: int\npop:rand_range?cast=f: [200.2, 1222.7, 2]"
+      "json": "{\n  \"population\": {\n    \"type\": \"rand_range\",\n    \"data\": [100, 1000],\n    \"config\": {\n      \"cast\": \"int\"\n    }\n  },\n  \"pop:rand_range?cast=f\": [200.2, 1222.7, 2]\n}",
+      "yaml": "population:\n  type: rand_range\n  data: [100, 1000]\n  config:\n    cast: int\npop:rand_range?cast=f: [200.2, 1222.7, 2]",
+      "api": "import dataspec\n\nspec_builder = dataspec.spec_builder()\n\nspec_builder.rand_range(\n    key=\"population\",\n    data=[100, 1000],\n    cast=\"int\")\nspec_builder.add_field(\"pop:rand_range?cast=f\", [200.2, 1222.7, 2])\n\nspec = spec_builder.build()",
+      "command": "dataspec -s dataspec.json --log-level error -i 5  --format json -x",
+      "output": "{\"population\": 828, \"pop\": 630.87}\n{\"population\": 339, \"pop\": 361.01}\n{\"population\": 254, \"pop\": 549.29}\n{\"population\": 509, \"pop\": 261.89}\n{\"population\": 980, \"pop\": 594.94}"
     },
     "uuid_spec_example_one": {
       "json": "{\n  \"id\": {\n    \"type\": \"uuid\"\n  },\n  \"id_shorthand:uuid\": {}\n}",
-      "yaml": "---\nid:\n  type: uuid\nid_shorthand:uuid: {}"
+      "yaml": "id:\n  type: uuid\nid_shorthand:uuid: {}",
+      "api": "import dataspec\n\nspec_builder = dataspec.spec_builder()\n\nspec_builder.uuid(key=\"id\")\nspec_builder.add_field(\"id_shorthand:uuid\", {})\n\nspec = spec_builder.build()",
+      "command": "dataspec --inline '{\"id\": {\"type\": \"uuid\"}, \"id_shorthand:uuid\": {}}' --log-level error -i 5",
+      "output": "130a7ce9-172d-4a5e-9245-e302c7115c68\nc7faf24d-dc8c-4535-bc19-cccaffeba945\n9de09fa7-cb2e-4d09-9346-54a1cb573383\nc3035653-d787-40c4-9007-211a78184743\n0038703f-8435-4ff2-bfd2-564cec464b84\n69b754d2-e73c-4f98-8497-3efabc13e6cd\nbd28c1cf-d66a-4d3c-bec2-7985ba999726\na23187cf-33ba-4917-a7d3-ba0e589889a8\n6efa948f-e9e8-4fd2-81f7-1385adaba4ed\n8534c17c-5eb9-418a-807d-c60a510f28c0"
+    },
+    "char_class_spec_example_one": {
+      "json": "{\n  \"one_to_five_digits:cc-digits?min=1&max=5\": {}\n}",
+      "yaml": "one_to_five_digits:cc-digits?min=1&max=5: {}",
+      "api": "import dataspec\n\nspec_builder = dataspec.spec_builder()\n\nspec_builder.add_field(\"one_to_five_digits:cc-digits?min=1&max=5\", {})\n\nspec = spec_builder.build()",
+      "command": "dataspec --inline '{\"one_to_five_digits:cc-digits?min=1&max=5\": {}}' --log-level error -i 5",
+      "output": "3062\n69470\n560\n49613\n368"
+    },
+    "char_class_spec_example_two": {
+      "json": "{\n  \"password\": {\n    \"type\": \"char_class\",\n    \"data\": [\"word\", \"special\", \"hex-lower\", \"M4$p3c!@l$@uc3\"],\n    \"config\": {\n      \"mean\": 14,\n      \"stddev\": 2,\n      \"min\": 10,\n      \"max\": 18,\n      \"exclude\": [\n        \"-\",\n        \"\\\"\"\n      ]\n    }\n  }\n}",
+      "yaml": "password:\n  type: char_class\n  data: [word, special, hex-lower, M4$p3c!@l$@uc3]\n  config:\n    mean: 14\n    stddev: 2\n    min: 10\n    max: 18\n    exclude:\n    - '-'\n    - '\"'",
+      "api": "import dataspec\n\nspec_builder = dataspec.spec_builder()\n\nspec_builder.char_class(\n    key=\"password\",\n    data=[\n      \"word\",\n      \"special\",\n      \"hex-lower\",\n      \"M4$p3c!@l$@uc3\"\n    ],\n    mean=14,\n    stddev=2,\n    min=10,\n    max=18,\n    exclude=[\"-\", \"\\\"\"])\n\nspec = spec_builder.build()",
+      "command": "dataspec -s dataspec.json --log-level error -i 10",
+      "output": "c3cFwpv!7c>(\n@qf`4;3yF7d#DM\n;'&5]$8pu3_7,E?\n|$aULq73cJY\nhb2cM4Zl2pPMnX_\nNV3TDhFsyQ)|4c\npd01u|859!p)iT\ni7$8F93x+3uKG\nd8hb@_SfHaP!\n,ps]`Sbw;k<3o["
+    },
+    "unicode_range_example_one": {
+      "json": "{\n  \"text\": {\n    \"type\": \"unicode_range\",\n    \"data\": [\"3040\", \"309f\"],\n    \"config\": {\n      \"mean\": 5\n    }\n  }\n}",
+      "yaml": "text:\n  type: unicode_range\n  data: ['3040', 309f]\n  config:\n    mean: 5",
+      "api": "import dataspec\n\nspec_builder = dataspec.spec_builder()\n\nspec_builder.unicode_range(\"text\", [\"3040\", \"309f\"], mean=5)\n\nspec = spec_builder.build()",
+      "command": "dataspec -s dataspec.json --log-level error -i 10",
+      "output": "\u3058\n\u3093\u3058\n\u3080\u306c\n\u3067\u3069\u3097\n\u3042\u3049\u3054\u305d\u306f\u3076\u3075\n\u306c\u308a\u3088\u3096\u3073\n\u3093\u309c\u308c\u3086\u3072\u3064\u3069\n\u3041\u305d\u309d\u3069\u305e\u304a\u3057\u308d\n\u3047\u3083\u3074\u3051\n\u3081\u304d\u309a"
     },
     "geo_point_spec_example_one": {
-      "json": "{\n  \"egypt\": {\n    \"type\": \"geo.point\",\n    \"config\": {\n      \"bbox\": [31.33134, 22.03795, 34.19295, 25.00562],\n      \"precision\": 3\n    }\n  }\n}",
-      "yaml": "---\negypt:\n  type: geo.point\n  config:\n    bbox:\n    - 31.33134\n    - 22.03795\n    - 34.19295\n    - 25.00562\n    precision: 3"
+      "json": "{\n  \"egypt\": {\n    \"type\": \"geo.pair\",\n    \"config\": {\n      \"bbox\": [\n        31.33134,\n        22.03795,\n        34.19295,\n        25.00562\n      ],\n      \"precision\": 3\n    }\n  }\n}",
+      "yaml": "egypt:\n  type: geo.pair\n  config:\n    bbox:\n    - 31.33134\n    - 22.03795\n    - 34.19295\n    - 25.00562\n    precision: 3",
+      "api": "import dataspec\n\nspec_builder = dataspec.spec_builder()\n\nspec_builder.geo_pair(\"egypt\", bbox=[31.33134, 22.03795, 34.19295, 25.00562], precision=3)\n\nspec = spec_builder.build()",
+      "command": "dataspec -s dataspec.json --log-level error -i 5",
+      "output": "33.341, 22.072\n 33.920, 24.315\n 32.019, 22.702\n 32.135, 24.033\n 33.032, 24.534"
     },
     "ip_spec_example_one": {
       "json": "{\n  \"network\": {\n    \"type\": \"ipv4\",\n    \"config\": {\n      \"cidr\": \"2.22.222.0/16\"\n    }\n  },\n  \"network_shorthand:ip?cidr=2.22.222.0/16\": {},\n  \"network_with_base:ip?base=192.168.0\": {}\n}",
-      "yaml": "---\nnetwork:\n  type: ipv4\n  config:\n    cidr: 2.22.222.0/16\nnetwork_shorthand:ip?cidr=2.22.222.0/16: {}\nnetwork_with_base:ip?base=192.168.0: {}"
+      "yaml": "network:\n  type: ipv4\n  config:\n    cidr: 2.22.222.0/16\nnetwork_shorthand:ip?cidr=2.22.222.0/16: {}\nnetwork_with_base:ip?base=192.168.0: {}",
+      "api": "import dataspec\n\nspec_builder = dataspec.spec_builder()\n\nspec_builder.ipv4(key=\"network\", cidr=\"2.22.222.0/16\")\nspec_builder.add_field(\"network_shorthand:ip?cidr=2.22.222.0/16\", {})\nspec_builder.add_field(\"network_with_base:ip?base=192.168.0\", {})\n\nspec = spec_builder.build()",
+      "command": "dataspec -s dataspec.json --log-level error -i 5",
+      "output": "2.22.45.93\n2.22.173.141\n192.168.0.15\n2.22.110.18\n2.22.137.49\n192.168.0.131\n2.22.111.100\n2.22.4.235\n192.168.0.64\n2.22.164.39\n2.22.193.130\n192.168.0.70\n2.22.23.245\n2.22.215.27\n192.168.0.88"
     },
     "ip_precise_example_one": {
-      "json": "{\"network:ip.precise?cidr=10.0.0.0/8\":{}}",
-      "yaml": "---\nnetwork:ip.precise?cidr=10.0.0.0/8: {}"
+      "json": "{\n  \"network:ip.precise?cidr=10.0.0.0/8\": {}\n}",
+      "yaml": "network:ip.precise?cidr=10.0.0.0/8: {}",
+      "api": "import dataspec\n\nspec_builder = dataspec.spec_builder()\n\nspec_builder.add_field(\"network:ip.precise?cidr=10.0.0.0/8\", {})\n\nspec = spec_builder.build()",
+      "command": "dataspec --inline '{\"network:ip.precise?cidr=10.0.0.0/8\": {}}' --log-level error -i 5",
+      "output": "10.0.0.0\n10.0.0.1\n10.0.0.2\n10.0.0.3\n10.0.0.4"
     },
     "ip_precise_example_two": {
-      "json": "{\"network:ip.precise?cidr=192.168.0.0/14&sample=true\": {}}",
-      "yaml": "---\nnetwork:ip.precise?cidr=192.168.0.0/14&sample=true: {}"
+      "json": "{\n  \"network:ip.precise?cidr=192.168.0.0/14&sample=true\": {}\n}",
+      "yaml": "network:ip.precise?cidr=192.168.0.0/14&sample=true: {}",
+      "api": "import dataspec\n\nspec_builder = dataspec.spec_builder()\n\nspec_builder.add_field(\"network:ip.precise?cidr=192.168.0.0/14&sample=true\", {})\n\nspec = spec_builder.build()",
+      "command": "dataspec --inline '{\"network:ip.precise?cidr=192.168.0.0/14&sample=true\": {}}' --log-level error -i 5",
+      "output": "192.169.145.223\n192.168.179.53\n192.168.91.96\n192.169.196.71\n192.170.176.96"
     },
     "ip_precise_example_three": {
-      "json": "{\"network:ip.precise?cidr=2.22.222.0/22\": {}}",
-      "yaml": "---\nnetwork:ip.precise?cidr=2.22.222.0/22: {}"
+      "json": "{\n  \"network:ip.precise?cidr=2.22.0.0/22\": {}\n}",
+      "yaml": "network:ip.precise?cidr=2.22.0.0/22: {}",
+      "api": "import dataspec\n\nspec_builder = dataspec.spec_builder()\n\nspec_builder.add_field(\"network:ip.precise?cidr=2.22.0.0/22\", {})\n\nspec = spec_builder.build()",
+      "command": "dataspec --inline '{\"network:ip.precise?cidr=2.22.0.0/22\": {}}' --log-level error -i 5",
+      "output": "2.22.0.0\n2.22.0.1\n2.22.0.2\n2.22.0.3\n2.22.0.4"
     },
     "weighted_ref_example_one": {
-      "json": "{\n  \"http_code\": {\n    \"type\": \"weightedref\",\n    \"data\": {\n      \"GOOD_CODES\": 0.7,\n      \"BAD_CODES\": 0.3\n    }\n  },\n  \"refs\": {\n    \"GOOD_CODES\": {\"200\": 0.5, \"202\": 0.3, \"203\": 0.1, \"300\": 0.1},\n    \"BAD_CODES\": {\"400\": 0.5, \"403\": 0.3, \"404\": 0.1, \"500\": 0.1}\n  }\n}",
-      "yaml": "---\nhttp_code:\n  type: weightedref\n  data:\n    GOOD_CODES: 0.7\n    BAD_CODES: 0.3\nrefs:\n  BAD_CODES:\n    '400': 0.5\n    '403': 0.3\n    '404': 0.1\n    '500': 0.1\n  GOOD_CODES:\n    '200': 0.5\n    '202': 0.3\n    '203': 0.1\n    '300': 0.1"
+      "json": "{\n  \"http_code\": {\n    \"type\": \"weightedref\",\n    \"data\": {\"GOOD_CODES\": 0.7, \"BAD_CODES\": 0.3}\n  },\n  \"refs\": {\n    \"GOOD_CODES\": {\n      \"200\": 0.5,\n      \"202\": 0.3,\n      \"203\": 0.1,\n      \"300\": 0.1\n    },\n    \"BAD_CODES\": {\n      \"400\": 0.5,\n      \"403\": 0.3,\n      \"404\": 0.1,\n      \"500\": 0.1\n    }\n  }\n}",
+      "yaml": "http_code:\n  type: weightedref\n  data: {GOOD_CODES: 0.7, BAD_CODES: 0.3}\nrefs:\n  GOOD_CODES:\n    '200': 0.5\n    '202': 0.3\n    '203': 0.1\n    '300': 0.1\n  BAD_CODES:\n    '400': 0.5\n    '403': 0.3\n    '404': 0.1\n    '500': 0.1",
+      "api": "import dataspec\n\nspec_builder = dataspec.spec_builder()\n\nrefs = spec_builder.refs()\nrefs.add_field('GOOD_CODES', {\"200\": 0.5, \"202\": 0.3, \"203\": 0.1, \"300\": 0.1})\nrefs.add_field('BAD_CODES', {\"400\": 0.5, \"403\": 0.3, \"404\": 0.1, \"500\": 0.1})\n\nspec_builder.weightedref('http_code', data={\"GOOD_CODES\": 0.7, \"BAD_CODES\": 0.3})\n\nspec = spec_builder.build()",
+      "command": "dataspec -s dataspec.json --log-level error -i 5",
+      "output": "300\n200\n200\n400\n202"
     },
     "select_list_example_one": {
-      "json": "{\n  \"ingredients\": {\n    \"type\": \"select_list_subset\",\n    \"config\": {\n      \"mean\": 3,\n      \"stddev\": 1,\n      \"min\": 2,\n      \"max\": 4,\n      \"join_with\": \", \"\n    },\n    \"data\": [\"onions\", \"mushrooms\", \"garlic\", \"bell peppers\", \"spinach\", \"potatoes\", \"carrots\"]\n  }\n}",
-      "yaml": "---\ningredients:\n  config:\n    join_with: ', '\n    max: 4\n    mean: 3\n    min: 2\n    stddev: 1\n  data:\n  - onions\n  - mushrooms\n  - garlic\n  - bell peppers\n  - spinach\n  - potatoes\n  - carrots\n  type: select_list_subset"
+      "json": "{\n  \"ingredients\": {\n    \"type\": \"select_list_subset\",\n    \"data\": [\"onions\", \"mushrooms\", \"garlic\", \"bell peppers\", \"spinach\", \"potatoes\", \"carrots\"],\n    \"config\": {\n      \"mean\": 3,\n      \"stddev\": 1,\n      \"min\": 2,\n      \"max\": 4,\n      \"join_with\": \", \"\n    }\n  }\n}",
+      "yaml": "ingredients:\n  type: select_list_subset\n  data: [onions, mushrooms, garlic, bell peppers, spinach, potatoes, carrots]\n  config:\n    mean: 3\n    stddev: 1\n    min: 2\n    max: 4\n    join_with: ', '",
+      "api": "import dataspec\n\nspec_builder = dataspec.spec_builder()\n\nspec_builder.select_list_subset(\n    key=\"ingredients\",\n    data=[\"onions\", \"mushrooms\", \"garlic\", \"bell peppers\", \"spinach\", \"potatoes\", \"carrots\"],\n    mean=3,\n    stddev=1,\n    min=2,\n    max=4,\n    join_with=\", \")\n\nspec = spec_builder.build()",
+      "command": "dataspec -s dataspec.json --log-level error -i 5",
+      "output": "mushrooms, garlic\ncarrots, potatoes\ngarlic, onions\ncarrots, potatoes, mushrooms\ngarlic, bell peppers, mushrooms"
     },
     "select_list_example_two": {
-      "json": "{\n  \"ingredients\": {\n    \"type\": \"select_list_subset\",\n    \"config\": {\n      \"mean\": 3, \"stddev\": 1, \"min\": 2, \"max\": 4,\n      \"join_with\": \"\\\", \\\"\",\n      \"quote\": \"\\\"\"\n    },\n    \"data\": [\"onions\", \"mushrooms\", \"garlic\", \"bell peppers\", \"spinach\", \"potatoes\", \"carrots\"]\n  }\n}",
-      "yaml": "---\ningredients:\n  type: select_list_subset\n  config:\n    join_with: '\", \"'\n    mean: 3\n    stddev: 1\n    min: 2\n    max: 4\n    quote: '\"'\n  data:\n  - onions\n  - mushrooms\n  - garlic\n  - bell peppers\n  - spinach\n  - potatoes\n  - carrots"
+      "json": "{\n  \"ingredients\": {\n    \"type\": \"select_list_subset\",\n    \"data\": [\"onions\", \"mushrooms\", \"garlic\", \"bell peppers\", \"spinach\", \"potatoes\", \"carrots\"],\n    \"config\": {\n      \"mean\": 3,\n      \"stddev\": 1,\n      \"min\": 2,\n      \"max\": 4,\n      \"join_with\": \"\\\", \\\"\",\n      \"quote\": \"\\\"\"\n    }\n  }\n}",
+      "yaml": "ingredients:\n  type: select_list_subset\n  data: [onions, mushrooms, garlic, bell peppers, spinach, potatoes, carrots]\n  config:\n    mean: 3\n    stddev: 1\n    min: 2\n    max: 4\n    join_with: '\", \"'\n    quote: '\"'",
+      "api": "import dataspec\n\nspec_builder = dataspec.spec_builder()\n\nspec_builder.select_list_subset(\n    key=\"ingredients\",\n    data=[\"onions\", \"mushrooms\", \"garlic\", \"bell peppers\", \"spinach\", \"potatoes\", \"carrots\"],\n    mean=3,\n    stddev=1,\n    min=2,\n    max=4,\n    join_with=\"\\\", \\\"\",\n    quote=\"\\\"\")\n\nspec = spec_builder.build()",
+      "command": "dataspec -s dataspec.json --log-level error -i 5",
+      "output": "\"onions\", \"bell peppers\"\n\"carrots\", \"spinach\"\n\"mushrooms\", \"bell peppers\", \"carrots\"\n\"bell peppers\", \"garlic\"\n\"potatoes\", \"spinach\""
     },
     "csv_spec_example_one": {
       "json": "{\n  \"cities\": {\n    \"type\": \"csv\",\n    \"config\": {\n      \"datafile\": \"cities.csv\",\n      \"delimiter\": \"~\",\n      \"sample\": true\n    }\n  }\n}",
-      "yaml": "---\ncities:\n  type: csv\n  config:\n    datafile: cities.csv\n    delimiter: '~'\n    sample: true"
+      "yaml": "cities:\n  type: csv\n  config:\n    datafile: cities.csv\n    delimiter: '~'\n    sample: true",
+      "api": "import dataspec\n\nspec_builder = dataspec.spec_builder()\n\nspec_builder.csv(\n    key=\"cities\",\n    datafile=\"cities.csv\",\n    delimiter=\"~\",\n    sample=True)\n\nspec = spec_builder.build()",
+      "command": "dataspec -s dataspec.json --log-level error -i 5 --datadir ./data",
+      "output": "Oxford\nOxford\nLondon\nSeattle\nTokyo"
     },
     "csv_spec_example_two": {
-      "json": "{\n    \"status\": {\n        \"type\": \"csv\",\n        \"config\": {\n            \"configref\": \"tabs_config\",\n            \"column\": 1\n        }\n    },\n    \"description\": {\n        \"type\": \"csv\",\n        \"config\": {\n            \"configref\": \"tabs_config\",\n            \"column\": 2\n        }\n    },\n    \"status_type:csv?configref=tabs_config&column=3\": {},\n    \"refs\": {\n        \"tabs_config\": {\n            \"type\": \"configref\",\n            \"config\": {\n                \"datafile\": \"tabs.csv\",\n                \"delimiter\": \"\\\\t\",\n                \"headers\": true\n            }\n        }\n    }\n}",
-      "yaml": "---\nstatus:\n  type: csv\n  config:\n    configref: tabs_config\n    column: 1\ndescription:\n  type: csv\n  config:\n    configref: tabs_config\n    column: 2\n# shorthand notation\nstatus_type:csv?configref=tabs_config&column=3: { }\nrefs:\n  tabs_config:\n    type: configref\n    config:\n      datafile: tabs.csv\n      delimiter: '\\t'\n      headers: true"
+      "json": "{\n  \"status\": {\n    \"type\": \"csv\",\n    \"config\": {\n      \"column\": 1,\n      \"configref\": \"tabs_config\"\n    }\n  },\n  \"description\": {\n    \"type\": \"csv\",\n    \"config\": {\n      \"column\": 2,\n      \"configref\": \"tabs_config\"\n    }\n  },\n  \"status_type:csv?configref=tabs_config&column=3\": {},\n  \"refs\": {\n    \"tabs_config\": {\n      \"type\": \"configref\",\n      \"config\": {\n        \"datafile\": \"tabs.csv\",\n        \"delimiter\": \"\\t\",\n        \"headers\": true\n      }\n    }\n  }\n}",
+      "yaml": "status:\n  type: csv\n  config:\n    column: 1\n    configref: tabs_config\ndescription:\n  type: csv\n  config:\n    column: 2\n    configref: tabs_config\nstatus_type:csv?configref=tabs_config&column=3: {}\nrefs:\n  tabs_config:\n    type: configref\n    config:\n      datafile: tabs.csv\n      delimiter: \"\\t\"\n      headers: true",
+      "api": "import dataspec\n\nspec_builder = dataspec.spec_builder()\n\nspec_builder.configref(\n    key=\"tabs_config\",\n    datafile=\"tabs.csv\",\n    delimiter=\"\\t\",\n    headers=True)\nspec_builder.csv(\n    key=\"status\",\n    column=1,\n    configref=\"tabs_config\")\nspec_builder.csv(\n    key=\"description\",\n    column=2,\n    configref=\"tabs_config\")\nspec_builder.add_field(\"status_type:csv?configref=tabs_config&column=3\", {})\n\nspec = spec_builder.build()",
+      "command": "dataspec -s dataspec.json --log-level error -i 5 --datadir ./data",
+      "output": "100\nContinue\nInformational\n101\nSwitching Protocols\nInformational\n200\nOK\nSuccessful\n201\nCreated\nSuccessful\n202\nAccepted\nSuccessful"
     },
     "csv_select_example_one": {
-      "json": "{\n    \"placeholder\": {\n        \"type\": \"csv_select\",\n        \"data\": {\n            \"geonameid\": 1,\n            \"name\": 2,\n            \"latitude\": 5,\n            \"longitude\": 6,\n            \"country_code\": 9,\n            \"population\": 15\n        },\n        \"config\": {\n            \"datafile\": \"allCountries.txt\",\n            \"headers\": false,\n            \"delimiter\": \"\\t\"\n        }\n    }\n}",
-      "yaml": "---\nplaceholder:\n  type: csv_select\n  data:\n    geonameid: 1\n    name: 2\n    latitude: 5\n    longitude: 6\n    country_code: 9\n    population: 15\n  config:\n    datafile: allCountries.txt\n    headers: no\n    delimiter: \"\\t\""
+      "json": "{\n  \"placeholder\": {\n    \"type\": \"csv_select\",\n    \"data\": {\"geonameid\": 1, \"name\": 2, \"latitude\": 5, \"longitude\": 6, \"country_code\": 9, \"population\": 15},\n    \"config\": {\n      \"datafile\": \"allCountries.txt\",\n      \"headers\": false,\n      \"delimiter\": \"\\t\"\n    }\n  }\n}",
+      "yaml": "placeholder:\n  type: csv_select\n  data: {geonameid: 1, name: 2, latitude: 5, longitude: 6, country_code: 9, population: 15}\n  config:\n    datafile: allCountries.txt\n    headers: false\n    delimiter: \"\\t\"",
+      "api": "import dataspec\n\nspec_builder = dataspec.spec_builder()\n\nspec_builder.csv_select(\n    key=\"placeholder\",\n    data={\n        \"geonameid\": 1,\n        \"name\": 2,\n        \"latitude\": 5,\n        \"longitude\": 6,\n        \"country_code\": 9,\n        \"population\": 15\n    },\n    datafile=\"allCountries.txt\",\n    headers=False,\n    delimiter=\"\t\")\n\nspec = spec_builder.build()",
+      "command": "dataspec -s dataspec.json --log-level error -i 5 --datadir ./data",
+      "output": "2986043\nPic de Font Blanca\n42.64991\n1.53335\nAD\n0\n2994701\nRoc M\u00e9l\u00e9\n42.58765\n1.74028\nAD\n0\n3007683\nPic des Langounelles\n42.61203\n1.47364\nAD\n0\n3017832\nPic de les Abelletes\n42.52535\n1.73343\nAD\n0\n3017833\nEstany de les Abelletes\n42.52915\n1.73362\nAD\n0"
     },
     "nested_example_one": {
-      "json": "{\n  \"id:uuid\": {},\n  \"user:nested\": {\n    \"fields\": {\n      \"user_id:uuid\": {},\n      \"geo:nested\": {\n        \"fields\": {\n          \"place_id:cc-digits?mean=5\": {},\n          \"coordinates:geo.pair?as_list=true\": {}\n        }\n      }\n    }\n  }\n}",
-      "yaml": "id:uuid: {}\nuser:nested:\n  fields:\n    user_id:uuid: {}\n    geo:nested:\n      fields:\n        place_id:cc-digits?mean=5: {}\n        coordinates:geo.pair?as_list=true: {}",
-      "command": "dataspec -s double-nested.json -i 1 --format json-pretty -x",
-      "output": "{\n    \"id\": \"4278b060-442d-4558-bf2c-5f1df68cb265\",\n    \"user\": {\n        \"geo\": {\n            \"coordinates\": [\n                \"-167.4324\",\n                \" 84.6883\"\n            ],\n            \"place_id\": \"42018569\"\n        },\n        \"user_id\": \"13d5c2a6-80c8-4bdb-89b2-7da9699cd0fb\"\n    }\n} "
-    },
-    "char_class_spec_example_one": {
-      "json": "{\n  \"one_to_five_digits:cc-digits?min=1&max=5\": {}\n}",
-      "yaml": "one_to_five_digits:cc-digits?min=1&max=5: {}"
-    },
-    "char_class_spec_example_two": {
-      "json": "{\n  \"password\": {\n    \"type\": \"char_class\",\n    \"data\": [\n      \"word\",\n      \"special\",\n      \"hex-lower\",\n      \"M4$p3c!@l$@uc3\"\n    ],\n    \"config\": {\n      \"mean\": 14,\n      \"stddev\": 2,\n      \"min\": 10,\n      \"max\": 18,\n      \"exclude\": [\"'\", \"\\\"\"]\n    }\n  }\n}",
-      "yaml": "---\npassword:\n  type: char_class\n  data:\n  - word\n  - special\n  - hex-lower\n  - M4$p3c!@l$@uc3\n  config:\n    mean: 14\n    stddev: 2\n    min: 10\n    max: 18\n    exclude: ['''', '\"']",
-      "command": "dataspec -s password.json -i 10",
-      "output": "61Nl=U35LVY^*\npe576Z*P198&f4t\n1p}=HA9b?iJDc\n7$C])[o<BDh&W9kz\nF%iGot-j~eUfpT3qR\n(TM9b$o$A4KG*{&\n{WPm1;]O:w@y27l\\\nJeTmpZ@zYN2Ms3,\n96i@FPA$^Q#%(|7Cba\nQ=+6D*8I36R%<n`$8"
-    },
-    "unicode_range_example_one": {
-      "json": "{\n  \"text\": {\n    \"type\": \"unicode_range\",\n    \"data\": [\"3040\", \"309f\"],\n    \"config\": {\n      \"mean\": 5\n    }\n  }\n}",
-      "yaml": "text:\n  type: unicode_range\n  data: ['3040', '309f']\n  config:\n    mean: 5\n",
-      "command": "dataspec -s unicode.yaml -i 10 -l off",
-      "output": "そぬをく\nが\nとしぶ\nぉけべ゙\nゃれげえ\nっぴ\nつざめよがぽ\nえろぱづゖごそ\u3040べかっ\nい\u3098\nぱまる"
+      "json": "{\n  \"id\": {\n    \"type\": \"uuid\"\n  },\n  \"user\": {\n    \"type\": \"nested\",\n    \"fields\": {\n      \"user_id\": {\n        \"type\": \"uuid\"\n      },\n      \"geo\": {\n        \"type\": \"nested\",\n        \"fields\": {\n          \"place_id:cc-digits?mean=5\": {},\n          \"coordinates:geo.pair?as_list=true\": {}\n        }\n      }\n    }\n  }\n}",
+      "yaml": "id:\n  type: uuid\nuser:\n  type: nested\n  fields:\n    user_id:\n      type: uuid\n    geo:\n      type: nested\n      fields:\n        place_id:cc-digits?mean=5: {}\n        coordinates:geo.pair?as_list=true: {}",
+      "api": "import dataspec\n\nspec_builder = dataspec.spec_builder()\n\ngeo_fields = dataspec.spec_builder()\ngeo_fields.add_field(\"place_id:cc-digits?mean=5\", {})\ngeo_fields.add_field(\"coordinates:geo.pair?as_list=true\", {})\n\nuser_fields = dataspec.spec_builder()\nuser_fields.uuid(\"user_id\")\nuser_fields.nested(\"geo\", geo_fields.build())\n\nspec_builder.uuid(\"id\")\nspec_builder.nested(\"user\", user_fields.build())\n\nspec = spec_builder.build()",
+      "command": "dataspec -s dataspec.json --log-level error -i 1",
+      "output": "{\n    \"id\": \"327658cd-b3de-477a-a902-742efd03ef89\",\n    \"user\": {\n        \"user_id\": \"c14c7709-d0f6-4cd8-b9ff-a936960ca63f\",\n        \"geo\": {\n            \"place_id\": \"59283706\",\n            \"coordinates\": [\n                \"-142.8146\",\n                \" 66.3702\"\n            ]\n        }\n    },\n    \"_internal\": {\n        \"_iteration\": 0,\n        \"_field_group\": \"ALL\"\n    }\n}"
     }
   }
-}
+}
\ No newline at end of file
diff --git a/docs/templates/tool.py b/docs/templates/tool.py
index d3364ce..0d0ce0d 100755
--- a/docs/templates/tool.py
+++ b/docs/templates/tool.py
@@ -3,9 +3,10 @@
 Utiltity to validate json and yaml example specs and apply them to templates for the READMEs
 """
 import os
+import glob
 import json
-import yaml
 import argparse
+import yaml
 from jinja2 import Environment, FileSystemLoader, select_autoescape
 
 
@@ -21,12 +22,19 @@ def main():
     parser.add_argument('-m', '--mode', default="verify",
                         choices=['verify', 'test', 'apply', 'dump', 'dump-yaml', 'all'],
                         help='what mode to run in')
+    parser.add_argument('-d', '--debug-data', dest='debug_data', action='store_true',
+                        help='Dump out the combined data with schemas')
 
     args = parser.parse_args()
 
     with open(args.input, encoding='utf-8') as f:
         data = json.load(f)
 
+    data = append_schemas(data)
+    if args.debug_data:
+        print(json.dumps(data, indent=4))
+        return
+
     if args.mode == 'verify' or args.mode == 'all':
         verify_data(data.get('examples', {}), args.key_filter)
     if args.mode == 'apply' or args.mode == 'all':
@@ -37,6 +45,22 @@ def main():
         dump_yaml(data.get('examples', {}), args.key_filter)
 
 
+def append_schemas(data):
+    for f in glob.glob(os.sep.join(['..', '..', 'dataspec', 'schema', '*.schema.json'])):
+        with open(f) as handle:
+            name = f.split(os.sep)[-1].replace('.schema.json', '')
+            schema = json.load(handle)
+            schemas = data.get('schemas', {})
+            schemas[name] = schema
+            data['schemas'] = schemas
+    with open(os.sep.join(['..', '..', 'dataspec', 'schema', 'definitions.json'])) as handle:
+        definitions = json.load(handle)
+        schemas = data.get('schemas', {})
+        schemas[name] = schema
+        data["definitions"] = definitions["definitions"]
+    return data
+
+
 def dump_yaml(data, specific_keys):
     """
     Dumps the data for as yaml for inspection
@@ -75,6 +99,11 @@ def dump_data(data, specific_keys):
             print(value['yaml'])
             print('```')
             print()
+        if 'api' in value:
+            print('```')
+            print(value['api'])
+            print('```')
+            print()
         print()
 
 
diff --git a/docs/templates/type-defs.json b/docs/templates/type-defs.json
new file mode 100644
index 0000000..22cc894
--- /dev/null
+++ b/docs/templates/type-defs.json
@@ -0,0 +1,194 @@
+{
+  "types": [
+    {
+      "type_name": "values",
+      "params": [
+        {
+          "name": "data",
+          "type": "Union[int, float, str, bool, List, Dict[str, float]]",
+          "description": "to use to supply values"
+        }
+      ],
+      "spec_params": [{"name": "data", "value": "data"}]
+    },
+    {
+      "type_name": "combine",
+      "params": [
+        {
+          "name": "refs",
+          "type": "List[Union[str, FieldInfo]] = None",
+          "description": "refs to combine"
+        },
+        {
+          "name": "fields",
+          "type": "List[Union[str, FieldInfo]] = None",
+          "description": "fields to combine"
+        }
+      ],
+      "custom_logic": "    if refs is not None:\n        spec['refs'] = _create_key_list(refs)\n    if fields is not None:\n        spec['fields'] = _create_key_list(fields)"
+    },
+    {
+      "type_name": "combine-list",
+      "params": [
+        {
+          "name": "refs",
+          "type": "List[List[Union[str, FieldInfo]]] = None",
+          "description": "lists of lists of refs to combine"
+        }
+      ],
+      "spec_params": [{"name": "refs", "value": "[_create_key_list(ref_list) for ref_list in refs]"}]
+    },
+    {
+      "type_name": "range",
+      "params": [
+        {
+          "name": "start",
+          "type": "Union[int, float]",
+          "description": "start of range inclusive"
+        },
+        {
+          "name": "end",
+          "type": "Union[int, float]",
+          "description": "end of range inclusive"
+        },
+        {
+          "name": "step",
+          "type": "Union[int, float]",
+          "description": "step for range"
+        }
+      ],
+      "spec_params": [{"name": "data", "value": "[start, end, step]"}]
+    },
+    {
+      "type_name": "rand_range",
+      "params": [
+        {
+          "name": "start",
+          "type": "Union[int, float]",
+          "description": "start of range inclusive"
+        },
+        {
+          "name": "end",
+          "type": "Union[int, float]",
+          "description": "end of range inclusive"
+        }
+      ],
+      "spec_params": [{"name": "data", "value": "[start, end]"}]
+    },
+    {
+      "type_name": "date"
+    },
+    {
+      "type_name": "date.iso"
+    },
+    {
+      "type_name": "date.iso.us"
+    },
+    {
+      "type_name": "uuid"
+    },
+    {
+      "type_name": "char_class",
+      "params": [
+        {
+          "name": "data",
+          "type": "Union[str, List[str]]",
+          "description": "either known character class or set of characters to use for sampling from"
+        }
+      ],
+      "spec_params": [{"name": "data", "value": "data"}]
+    },
+    {
+      "type_name": "char_class_abbrev",
+      "params": [
+        {
+          "name": "cc_abbrev",
+          "type": "str",
+          "description": "alternative type abbreviation i.e. ascii, cc-ascii, visible, cc-visible"
+        }
+      ],
+      "custom_logic": "    if cc_abbrev.startswith('cc-'):\n        spec['type'] = cc_abbrev\n    else:\n        spec['type'] = 'cc-' + cc_abbrev"
+    },
+    {
+      "type_name": "unicode_range",
+      "params": [
+        {
+          "name": "data",
+          "type": "Union[List[str], List[List[str]]]",
+          "description": "hex start and end unicode ranges or lists of these"
+        }
+      ],
+      "spec_params": [{"name": "data", "value": "data"}]
+    },
+    {
+      "type_name": "geo.lat"
+    },
+    {
+      "type_name": "geo.long"
+    },
+    {
+      "type_name": "geo.pair"
+    },
+    {
+      "type_name": "ip"
+    },
+    {
+      "type_name": "ipv4"
+    },
+    {
+      "type_name": "ip.precise"
+    },
+    {
+      "type_name": "weightedref",
+      "params": [
+        {
+          "name": "data",
+          "type": "Dict[str, float]",
+          "description": "Mapping of ref name to weight"
+        }
+      ],
+      "spec_params": [{"name": "data", "value": "data"}]
+    },
+    {
+      "type_name": "select_list_subset",
+      "params": [
+        {
+          "name": "data",
+          "type": "List[Any] = None",
+          "description": "to select from"
+        },
+        {
+          "name": "ref",
+          "type": "str = None",
+          "description": "that contains data to select from"
+        }
+      ],
+      "custom_logic": "    if data is not None:\n        spec['data'] = data\n    if ref is not None:\n        spec['ref'] = ref"
+    },
+    {
+      "type_name": "csv"
+    },
+    {
+      "type_name": "csv_select",
+      "params": [
+        {
+          "name": "data",
+          "type": "Dict[str, int] = None",
+          "description": "param data: Mapping of field name to one based column number"
+        }
+      ],
+      "custom_logic": "    if data is not None:\n        spec['data'] = data"
+    },
+    {
+      "type_name": "nested",
+      "params": [
+        {
+          "name": "fields",
+          "type": "Dict[str, Dict]",
+          "description": "sub field specifications"
+        }
+      ],
+      "spec_params": [{"name": "fields", "value": "fields"}]
+    }
+  ]
+}


Test patch:
diff --git a/dataspec/schema/tests/date.tests.json b/dataspec/schema/tests/date.tests.json
deleted file mode 100644
index b19b228..0000000
--- a/dataspec/schema/tests/date.tests.json
+++ /dev/null
@@ -1,29 +0,0 @@
-{
-  "date.schema.json": {
-    "valid": [
-      {"instance": {"type": "date"}, "note": "empty date spec is valid"},
-      {"instance": {"type": "date", "config": {"offset": 1}}},
-      {"instance": {"type": "date", "config": {"delta_days": 1}}},
-      {"instance": {"type": "date", "config": {"delta_days": -1}}},
-      {"instance": {"type": "date", "config": {"delta_days": 1, "offset": 1}}},
-      {"instance": {"type": "date", "config": {"delta_days": 1, "offset": -1}}},
-      {"instance": {"type": "date", "config": {"delta_days": 1, "offset": 1, "anchor": "15-12-2050"}}},
-      {"instance": {"type": "date", "config": {"delta_days": 1, "anchor": "15-Dec-2050", "format": "%d-%b-%Y"}}},
-      {"instance": {"type": "date", "config": {"delta_days": [0, 2]}}}
-    ],
-    "invalid": [
-      {"instance": {}, "note": "empty"},
-      {"instance": {"type": "dates"}, "note": "misspelled"},
-      {"instance": {"type": "date,iso"}, "note": "misspelled"},
-      {"instance": {"type": "date", "config": {"offset": 1.5}}, "note": "only integer offsets supported"},
-      {
-        "note": "only string formatted anchor dates supported",
-        "instance": {"type": "date", "config": {"anchor": 1615477175}}
-      },
-      {
-        "note": "delta days only supports array with two elements",
-        "instance": {"type": "date", "config": {"delta_days": [0, 2, 4]}}
-      }
-    ]
-  }
-}
\ No newline at end of file
diff --git a/dataspec/schema/tests/EXAMPLE.tests.json b/tests/data/EXAMPLE.tests.json
similarity index 100%
rename from dataspec/schema/tests/EXAMPLE.tests.json
rename to tests/data/EXAMPLE.tests.json
diff --git a/dataspec/schema/tests/char_class.tests.json b/tests/data/char_class.tests.json
similarity index 100%
rename from dataspec/schema/tests/char_class.tests.json
rename to tests/data/char_class.tests.json
diff --git a/dataspec/schema/tests/combine.tests.json b/tests/data/combine.tests.json
similarity index 100%
rename from dataspec/schema/tests/combine.tests.json
rename to tests/data/combine.tests.json
diff --git a/dataspec/schema/tests/combine_list.tests.json b/tests/data/combine_list.tests.json
similarity index 100%
rename from dataspec/schema/tests/combine_list.tests.json
rename to tests/data/combine_list.tests.json
diff --git a/tests/data/date.tests.json b/tests/data/date.tests.json
new file mode 100644
index 0000000..3ca6af5
--- /dev/null
+++ b/tests/data/date.tests.json
@@ -0,0 +1,58 @@
+{
+  "date.schema.json": {
+    "valid": [
+      {"instance": {"type": "date"}, "note": "empty date spec is valid"},
+      {"instance": {"type": "date", "config": {"offset": 1}}},
+      {"instance": {"type": "date", "config": {"duration_days": 1}}},
+      {"instance": {"type": "date", "config": {"duration_days": "22"}}},
+      {"instance": {"type": "date", "config": {"duration_days": 1, "offset": 1}}},
+      {"instance": {"type": "date", "config": {"duration_days": 1, "offset": -1}}},
+      {"instance": {"type": "date", "config": {"duration_days": 1, "offset": 1, "start": "15-12-2050"}}},
+      {"instance": {"type": "date", "config": {"duration_days": 1, "start": "15-Dec-2050", "format": "%d-%b-%Y"}}},
+      {"instance": {"type": "date", "config": {"duration_days": 1, "end": "15-Dec-2050"}}},
+      {"instance": {"type": "date", "config": {"duration_days": 1, "offset": 1, "start": "15-12-2050", "end": "15-12-2051" }}},
+      {"instance": {"type": "date", "config": {"duration_days": 1, "start": "15-Dec-2050", "end": "15-Dec-2051", "format": "%d-%b-%Y"}}},
+      {"instance": {"type": "date", "config": {"center_date": "15-Dec-2050"}}},
+      {"instance": {"type": "date", "config": {"center_date": "15-Dec-2050", "stddev_days": 30}}},
+      {"instance": {"type": "date", "config": {"stddev_days": 30}}}
+    ],
+    "invalid": [
+      {"instance": {}, "note": "empty"},
+      {"instance": {"type": "dates"}, "note": "misspelled"},
+      {"instance": {"type": "date,iso"}, "note": "misspelled"},
+      {"instance": {"type": "date", "config": {"offset": 1.5}}, "note": "only integer offsets supported"},
+      {
+        "note": "only string formatted start dates supported",
+        "instance": {"type": "date", "config": {"start": 1615477175}}
+      },
+      {
+        "note": "only string formatted end dates supported",
+        "instance": {"type": "date", "config": {"end": 1615477175}}
+      },
+      {
+        "note": "cannot supply start and center_date",
+        "instance": {"type": "date", "config": {"start": "15-12-2050", "center_date": "25-12-2050"}}
+      },
+      {
+        "note": "cannot supply end and center_date",
+        "instance": {"type": "date", "config": {"center_date": "25-12-2050", "end": "31-12-2050"}}
+      },
+      {
+        "note": "cannot supply duration_days and center_date",
+        "instance": {"type": "date", "config": {"center_date": "25-12-2050", "duration_days": 22}}
+      },
+      {
+        "note": "cannot supply start and stddev_days",
+        "instance": {"type": "date", "config": {"start": "15-12-2050", "stddev_days": 30}}
+      },
+      {
+        "note": "cannot supply end and stddev_days",
+        "instance": {"type": "date", "config": {"stddev_days": 30, "end": "31-12-2050"}}
+      },
+      {
+        "note": "cannot supply duration_days and stddev_days",
+        "instance": {"type": "date", "config": {"stddev_days": 30, "duration_days": 22}}
+      }
+    ]
+  }
+}
\ No newline at end of file
diff --git a/dataspec/schema/tests/geo.lat.tests.json b/tests/data/geo.lat.tests.json
similarity index 100%
rename from dataspec/schema/tests/geo.lat.tests.json
rename to tests/data/geo.lat.tests.json
diff --git a/dataspec/schema/tests/geo.long.tests.json b/tests/data/geo.long.tests.json
similarity index 100%
rename from dataspec/schema/tests/geo.long.tests.json
rename to tests/data/geo.long.tests.json
diff --git a/dataspec/schema/tests/geo.pair.tests.json b/tests/data/geo.pair.tests.json
similarity index 100%
rename from dataspec/schema/tests/geo.pair.tests.json
rename to tests/data/geo.pair.tests.json
diff --git a/dataspec/schema/tests/ip.precise.tests.json b/tests/data/ip.precise.tests.json
similarity index 100%
rename from dataspec/schema/tests/ip.precise.tests.json
rename to tests/data/ip.precise.tests.json
diff --git a/dataspec/schema/tests/ip.tests.json b/tests/data/ip.tests.json
similarity index 100%
rename from dataspec/schema/tests/ip.tests.json
rename to tests/data/ip.tests.json
diff --git a/dataspec/schema/tests/range.tests.json b/tests/data/range.tests.json
similarity index 100%
rename from dataspec/schema/tests/range.tests.json
rename to tests/data/range.tests.json
diff --git a/tests/data/select_list_subset.tests.json b/tests/data/select_list_subset.tests.json
new file mode 100644
index 0000000..6da06f8
--- /dev/null
+++ b/tests/data/select_list_subset.tests.json
@@ -0,0 +1,44 @@
+{
+  "select_list_subset.schema.json": {
+    "valid": [
+      {"instance": {"type": "select_list_subset", "data": ["one"]}},
+      {"instance": {"type": "select_list_subset", "data": ["one", "two"]}},
+      {"instance": {"type": "select_list_subset", "data": ["one", "two", "tre"]}},
+      {"instance": {"type": "select_list_subset", "ref": "has_the_list"}},
+      {
+        "note": "count by itself is valid",
+        "instance": {"type": "select_list_subset", "data": ["a", "b", "c"], "config": {"count": 5}}
+      },
+      {
+        "instance": {"type": "select_list_subset", "data": ["a", "b", "c"], "config": {"mean": 2, "min": 1, "max": 3}}
+      },
+      {
+        "note": "mean by itself is valid",
+        "instance": {"type": "select_list_subset", "data": ["a", "b", "c"], "config": {"mean": 2}}
+      },
+      {
+        "note": "mean with stddev is valid",
+        "instance": {"type": "select_list_subset", "data": ["a", "b", "c"], "config": {"mean": 2, "stddev":  1}}
+      }
+    ],
+    "invalid": [
+      {"instance": {}, "note": "empty"},
+      {"instance": {"type": "select_list_subsets"}, "note": "misspelled"},
+      {"instance": {"type": "select_list_subset"}, "note": "no data element defined"},
+      {"instance": {"type": "select_list_subset", "data": 42}, "note": "data not a list"},
+      {"instance": {"type": "select_list_subset", "data": "foobar"}, "note": "data not a list"},
+      {
+        "note": "count and min together is invalid",
+        "instance": {"type": "select_list_subset", "data": ["a", "b", "c"], "config": {"count": 5, "min": 3}}
+      },
+      {
+        "note": "count and max together is invalid",
+        "instance": {"type": "select_list_subset", "data": ["a", "b", "c"], "config": {"count": 5, "max": 5}}
+      },
+      {
+        "note": "count and mean together is invalid",
+        "instance": {"type": "select_list_subset", "data": ["a", "b", "c"], "config": {"count": 5, "mean": 5}}
+      }
+    ]
+  }
+}
\ No newline at end of file
diff --git a/dataspec/schema/tests/unicode_range.tests.json b/tests/data/unicode_range.tests.json
similarity index 100%
rename from dataspec/schema/tests/unicode_range.tests.json
rename to tests/data/unicode_range.tests.json
diff --git a/dataspec/schema/tests/uuid.tests.json b/tests/data/uuid.tests.json
similarity index 100%
rename from dataspec/schema/tests/uuid.tests.json
rename to tests/data/uuid.tests.json
diff --git a/dataspec/schema/tests/values.tests.json b/tests/data/values.tests.json
similarity index 100%
rename from dataspec/schema/tests/values.tests.json
rename to tests/data/values.tests.json
diff --git a/tests/test_buffering.py b/tests/test_buffering.py
new file mode 100644
index 0000000..b314e59
--- /dev/null
+++ b/tests/test_buffering.py
@@ -0,0 +1,74 @@
+import pytest
+
+import dataspec.supplier.common
+from dataspec import builder, suppliers, Loader
+
+
+def test_buffered_supplier_one_behind():
+    """
+    Tests simple case that multiple calls to the same index will return the same result
+    """
+    wrapped = suppliers.values(builder.values(['a', 'b', 'c', 'd', 'e', 'f', 'g'], sample=True))
+    buffered = dataspec.supplier.common.BufferedValueSuppier(wrapped, buffer_size=3)
+
+    for i in range(10):
+        value = buffered.next(i)
+        assert value == buffered.next(i)
+        assert value == buffered.next(i)
+
+
+def test_buffered_supplier_more_than_one_behind():
+    """
+    Tests that we will the buffered gets filled in an expected way
+    """
+    wrapped = suppliers.values(builder.values(['a', 'b', 'c', 'd', 'e', 'f', 'g'], sample=True))
+    buffered = dataspec.supplier.common.BufferedValueSuppier(wrapped, buffer_size=4)
+
+    vals = [buffered.next(i) for i in range(5)]
+
+    # buffer only holds 4 items so should only be able to reach back to third element
+    assert buffered.next(4) == vals[-1]
+    assert buffered.next(3) == vals[-2]
+    assert buffered.next(2) == vals[-3]
+    assert buffered.next(1) == vals[-4]
+
+    with pytest.raises(ValueError):
+        buffered.next(0)
+
+
+def test_buffered_supplier_size_one():
+    """
+    Tests simple case but with a buffer of size one
+    """
+    wrapped = suppliers.values(builder.values(['a', 'b', 'c', 'd', 'e', 'f', 'g'], sample=True))
+    buffered = dataspec.supplier.common.BufferedValueSuppier(wrapped, buffer_size=1)
+
+    for i in range(10):
+        value = buffered.next(i)
+        assert value == buffered.next(i)
+
+
+def test_is_buffered():
+    values_spec = builder.values(['a', 'b', 'c', 'd', 'e', 'f', 'g'], sample=True, buffer="on")
+    assert suppliers.is_buffered(values_spec)
+
+    values_spec = builder.values(['a', 'b', 'c', 'd', 'e', 'f', 'g'], sample=True, buffer_size="20")
+    assert suppliers.is_buffered(values_spec)
+
+
+def test_buffered_supplier_from_spec():
+    """
+    Tests interpreting specs for buffering
+    """
+    values_spec = builder.values(['a', 'b', 'c', 'd', 'e', 'f', 'g'], sample=True, buffer_size="20")
+    assert suppliers.is_buffered(values_spec)
+
+    data_spec = builder.single_field('field', values_spec).build()
+    loader = Loader(data_spec)
+
+    supplier = loader.get('field')
+
+    for i in range(10):
+        value = supplier.next(i)
+        assert value == supplier.next(i)
+
diff --git a/tests/test_builder.py b/tests/test_builder.py
new file mode 100644
index 0000000..4315340
--- /dev/null
+++ b/tests/test_builder.py
@@ -0,0 +1,272 @@
+import pytest
+import dataspec.builder as builder
+from dataspec import distributions, DataSpec, SpecException
+
+
+def test_api_builder():
+    # raw data for both specs
+    animal_names = ['zebra', 'hedgehog', 'llama', 'flamingo']
+    action_list = ['fling', 'jump', 'launch', 'dispatch']
+    domain_weights = {
+        "gmail.com": 0.6,
+        "yahoo.com": 0.3,
+        "hotmail.com": 0.1
+    }
+
+    # first build uses direct build methods
+    builder1 = builder.Builder()
+    refs1 = builder1.refs_builder
+    domains = refs1.values('DOMAINS', domain_weights)
+    animals = refs1.values('ANIMALS', animal_names)
+    actions = refs1.values('ACTIONS', action_list, sample=True)
+    handles = refs1.combine('HANDLE', refs=[animals, actions], join_with='_')
+    builder1.combine('email', refs=[handles, domains])
+
+    spec1 = builder1.build()
+
+    # second builder uses the fluent api style
+    builder2 = builder.Builder()
+
+    animals_spec = builder.values(data=animal_names)
+    actions_spec = builder.values(data=action_list, sample=True)
+    domains_spec = builder.values(data=domain_weights)
+    # combines ANIMALS and ACTIONS
+    handle_spec = builder.combine(refs=['ANIMALS', 'ACTIONS'], join_with='_')
+
+    builder2.add_ref('DOMAINS', domains_spec) \
+        .add_ref('ANIMALS', animals_spec) \
+        .add_ref('ACTIONS', actions_spec) \
+        .add_ref('HANDLE', handle_spec)
+
+    builder2.add_field('email', builder.combine(refs=['HANDLE', 'DOMAINS']))
+
+    spec2 = builder2.build()
+
+    assert spec1 == spec2
+
+
+field_spec_build_tests = [
+    (builder.values([1, 2, 3], prefix="foo"),
+     {"type": "values", "data": [1, 2, 3], "config": {"prefix": "foo"}}),
+    (builder.combine(refs=["ONE", "TWO"], join_with='@'),
+     {"type": "combine", "refs": ["ONE", "TWO"], "config": {"join_with": "@"}}),
+    (builder.combine(fields=["ONE", "TWO"], join_with='-'),
+     {"type": "combine", "fields": ["ONE", "TWO"], "config": {"join_with": "-"}}),
+    (builder.combine_list(refs=[["A", "B"], ["A", "B", "C"]], join_with=","),
+     {"type": "combine-list", "config": {"join_with": ","}, "refs": [["A", "B"], ["A", "B", "C"]]}),
+    (builder.range_spec([1, 5, 1], as_list=True, count=2),
+     {"type": "range", "config": {"as_list": True, "count": 2}, "data": [1, 5, 1]}),
+    (builder.rand_range([20, 44], count=[2, 3, 4]),
+     {"type": "rand_range", "config": {"count": [2, 3, 4]}, "data": [20, 44]}),
+    (builder.date(duration_days=3, offset=4),
+     {"type": "date", "config": {"duration_days": 3, "offset": 4}}),
+    (builder.date_iso(duration_days=4, offset=5),
+     {"type": "date.iso", "config": {"duration_days": 4, "offset": 5}}),
+    (builder.date_iso_us(duration_days=5, offset=6),
+     {"type": "date.iso.us", "config": {"duration_days": 5, "offset": 6}}),
+    (builder.uuid(quote="'"),
+     {"type": "uuid", "config": {"quote": "'"}}),
+    (builder.char_class("visible", min=5, max=7),
+     {"type": "char_class", "config": {"min": 5, "max": 7}, "data": "visible"}),
+    (builder.unicode_range(["3040", "309f"], mean=3),
+     {"type": "unicode_range", "config": {"mean": 3}, "data": ["3040", "309f"]}),
+    (builder.unicode_range([["3040", "309f"]], mean=3),
+     {"type": "unicode_range", "config": {"mean": 3}, "data": [["3040", "309f"]]}),
+    (builder.geo_lat(start_lat=75.5),
+     {"type": "geo.lat", "config": {"start_lat": 75.5}}),
+    (builder.geo_long(bbox=[31.3, 22.0, 34.1, 25.0]),
+     {"type": "geo.long", "config": {"bbox": [31.3, 22.0, 34.1, 25.0]}}),
+    (builder.geo_pair(join_with=":", as_list="yes"),
+     {"type": "geo.pair", "config": {"join_with": ":", "as_list": "yes"}}),
+    (builder.ip(base="192.168"),
+     {"type": "ip", "config": {"base": "192.168"}}),
+    (builder.ipv4(cidr="2.22.222.0/16"),
+     {"type": "ipv4", "config": {"cidr": "2.22.222.0/16"}}),
+    (builder.ip_precise(cidr="10.0.0.0/8"),
+     {"type": "ip.precise", "config": {"cidr": "10.0.0.0/8"}}),
+    (builder.weightedref({"One": 0.5, "Two": 0.3, "Three": 0.2}),
+     {"type": "weightedref", "data": {"One": 0.5, "Two": 0.3, "Three": 0.2}}),
+    (builder.select_list_subset(data=["A", "B", "C"], mean=5, stddev=2),
+     {"type": "select_list_subset", "config": {"mean": 5, "stddev": 2}, "data": ["A", "B", "C"]}),
+    (builder.select_list_subset(ref="LIST", mean=5, stddev=2),
+     {"type": "select_list_subset", "config": {"mean": 5, "stddev": 2}, "ref": "LIST"}),
+    (builder.csv(datafile="demo.csv", sample="on"),
+     {"type": "csv", "config": {"datafile": "demo.csv", "sample": "on"}}),
+    (builder.csv_select(data={"one": 1, "two": 2}, headers=False),
+     {"type": "csv_select", "config": {"headers": False}, "data": {"one": 1, "two": 2}}),
+    (builder.nested(fields={"one": {"type": "values", "data": 1}}),
+     {"type": "nested", "fields": {"one": {"type": "values", "data": 1}}}),
+]
+
+
+@pytest.mark.parametrize("generated_spec,expected_spec", field_spec_build_tests)
+def test_spec_builder(generated_spec, expected_spec):
+    assert generated_spec == expected_spec
+
+
+full_spec_build_tests = [
+    (builder.Builder().values('name', [1, 2, 3], prefix="foo"),
+     {"name": {"type": "values", "data": [1, 2, 3], "config": {"prefix": "foo"}}}),
+    (builder.Builder().combine('name', refs=["ONE", "TWO"], join_with='@'),
+     {"name": {"type": "combine", "refs": ["ONE", "TWO"], "config": {"join_with": "@"}}}),
+    (builder.Builder().combine('name', fields=["ONE", "TWO"], join_with='-'),
+     {"name": {"type": "combine", "fields": ["ONE", "TWO"], "config": {"join_with": "-"}}}),
+    (builder.Builder().combine_list('name', refs=[["A", "B"], ["A", "B", "C"]], join_with=","),
+     {"name": {"type": "combine-list", "config": {"join_with": ","}, "refs": [["A", "B"], ["A", "B", "C"]]}}),
+    (builder.Builder().range_spec('name', [1, 5, 1], as_list=True, count=2),
+     {"name": {"type": "range", "config": {"as_list": True, "count": 2}, "data": [1, 5, 1]}}),
+    (builder.Builder().rand_range('name', [20, 44], count=[2, 3, 4]),
+     {"name": {"type": "rand_range", "config": {"count": [2, 3, 4]}, "data": [20, 44]}}),
+    (builder.Builder().date('name', duration_days=3, offset=4),
+     {"name": {"type": "date", "config": {"duration_days": 3, "offset": 4}}}),
+    (builder.Builder().date_iso('name', duration_days=4, offset=5),
+     {"name": {"type": "date.iso", "config": {"duration_days": 4, "offset": 5}}}),
+    (builder.Builder().date_iso_us('name', duration_days=5, offset=6),
+     {"name": {"type": "date.iso.us", "config": {"duration_days": 5, "offset": 6}}}),
+    (builder.Builder().uuid('name', quote="'"),
+     {"name": {"type": "uuid", "config": {"quote": "'"}}}),
+    (builder.Builder().char_class('name', "visible", min=5, max=7),
+     {"name": {"type": "char_class", "config": {"min": 5, "max": 7}, "data": "visible"}}),
+    (builder.Builder().char_class_abbrev('name', "visible", min=3, max=10),
+     {"name": {"type": "cc-visible", "config": {"min": 3, "max": 10}}}),
+    (builder.Builder().unicode_range('name', ["3040", "309f"], mean=3),
+     {"name": {"type": "unicode_range", "config": {"mean": 3}, "data": ["3040", "309f"]}}),
+    (builder.Builder().unicode_range('name', [["3040", "309f"]], mean=3),
+     {"name": {"type": "unicode_range", "config": {"mean": 3}, "data": [["3040", "309f"]]}}),
+    (builder.Builder().geo_lat('name', start_lat=75.5),
+     {"name": {"type": "geo.lat", "config": {"start_lat": 75.5}}}),
+    (builder.Builder().geo_long('name', bbox=[31.3, 22.0, 34.1, 25.0]),
+     {"name": {"type": "geo.long", "config": {"bbox": [31.3, 22.0, 34.1, 25.0]}}}),
+    (builder.Builder().geo_pair('name', join_with=":", as_list="yes"),
+     {"name": {"type": "geo.pair", "config": {"join_with": ":", "as_list": "yes"}}}),
+    (builder.Builder().ip('name', base="192.168"),
+     {"name": {"type": "ip", "config": {"base": "192.168"}}}),
+    (builder.Builder().ipv4('name', cidr="2.22.222.0/16"),
+     {"name": {"type": "ipv4", "config": {"cidr": "2.22.222.0/16"}}}),
+    (builder.Builder().ip_precise('name', cidr="10.0.0.0/8"),
+     {"name": {"type": "ip.precise", "config": {"cidr": "10.0.0.0/8"}}}),
+    (builder.Builder().weightedref('name', {"One": 0.5, "Two": 0.3, "Three": 0.2}),
+     {"name": {"type": "weightedref", "data": {"One": 0.5, "Two": 0.3, "Three": 0.2}}}),
+    (builder.Builder().select_list_subset('name', data=["A", "B", "C"], mean=5, stddev=2),
+     {"name": {"type": "select_list_subset", "config": {"mean": 5, "stddev": 2}, "data": ["A", "B", "C"]}}),
+    (builder.Builder().select_list_subset('name', ref="LIST", mean=5, stddev=2),
+     {"name": {"type": "select_list_subset", "config": {"mean": 5, "stddev": 2}, "ref": "LIST"}}),
+    (builder.Builder().csv('name', datafile="demo.csv", sample="on"),
+     {"name": {"type": "csv", "config": {"datafile": "demo.csv", "sample": "on"}}}),
+    (builder.Builder().csv_select('name', data={"one": 1, "two": 2}, headers=False),
+     {"name": {"type": "csv_select", "config": {"headers": False}, "data": {"one": 1, "two": 2}}}),
+    (builder.Builder().nested('name', fields={"one": {"type": "values", "data": 1}}),
+     {"name": {"type": "nested", "fields": {"one": {"type": "values", "data": 1}}}}),
+]
+
+
+@pytest.mark.parametrize("field_info,expected_spec", full_spec_build_tests)
+def test_full_spec_builder(field_info, expected_spec):
+    generated_spec = field_info.builder.build().raw_spec
+    assert generated_spec == expected_spec
+
+
+invalid_spec_build_tests = [
+    builder.Builder().values('name', data=[1, 2, 3], count={}),  # invalid count
+    builder.Builder().combine('name', refs=["ONE", "TWO"], join_with='@'),  # no refs defined
+    builder.Builder().combine('name', fields=["ONE", "TWO"], join_with='-'),  # no refs defined
+    builder.Builder().combine_list('name', refs=[["A", "B"], ["A", "B", "C"]], join_with=","),
+    builder.Builder().range_spec('name', [1, 5, 1], as_list=True, count={}),  # invalid count
+    builder.Builder().rand_range('name', [20, 44], count=None),  # invalid count
+    builder.Builder().date('name', duration_days={1: 0.5, 2: 0.5}, offset=4),  # invalid duration_days
+    builder.Builder().date_iso('name', duration_days=4, offset="1"),  # invalid offset
+    builder.Builder().date_iso_us('name', duration_days=5, offset=6, start=42),  # invalid start
+    builder.Builder().char_class('name', "visible", min="5", max=7),  # min should be number
+    builder.Builder().char_class_abbrev('name', "visible", min=3, max="10"),  # max should be number
+    builder.Builder().unicode_range('name', ["3040", "309f"], count=4, mean=3),  # can't have count and mean
+    builder.Builder().unicode_range('name', [["3040", "309f"]], count=5, max=6),  # can't have count and max
+    builder.Builder().geo_lat('name', start_lat=175.5),  # lat out of range
+    builder.Builder().geo_long('name', bbox=[31.3, 22.0]),  # bbox wrong dimensions
+    builder.Builder().geo_pair('name', join_with=":", precision="yes"),  # invalid precision
+    builder.Builder().ip('name', base="192.1680"),  # type in base
+    builder.Builder().ipv4('name', cidr="2.22.222.0/22"),  # not one of supported bases
+    builder.Builder().select_list_subset('name', ref="LIST", mean=5, stddev=2),  # ref not defined
+    # TODO: when schemas for these are created
+    # builder.Builder().weightedref('name', {"One": 0.5, "Two": 0.3, "Three": 0.2}),
+    # builder.Builder().csv('name', datafile="demo.csv", sample="on"),
+    # builder.Builder().csv_select('name', data={"one": 1, "two": 2}, headers=False),
+    # builder.Builder().nested('name', fields={"one": {"type": "values", "data": 1}}),
+]
+
+
+@pytest.mark.parametrize("field_info", invalid_spec_build_tests)
+def test_invalid_spec_builder(field_info):
+    spec = field_info.builder.build()
+    with pytest.raises(SpecException):
+        next(spec.generator(1, enforce_schema=True))
+
+
+def test_api_change():
+    animal_names = ['zebra', 'hedgehog', 'llama', 'flamingo']
+    action_list = ['fling', 'jump', 'launch', 'dispatch']
+    domain_weights = {
+        "gmail.com": 0.6,
+        "yahoo.com": 0.3,
+        "hotmail.com": 0.1
+    }
+
+    # for building the final spec
+    spec_builder = builder.Builder()
+
+    # for building the regs section of the spec
+    refs = spec_builder.refs_builder
+
+    # spec for each field and reference
+    animals = refs.values('ANIMALS', data=animal_names)
+    actions = refs.values('ACTIONS', data=action_list)
+    domains = refs.values('DOMAINS', data=domain_weights)
+    # combines ANIMALS and ACTIONS
+    handles = refs.combine('HANDLE', refs=[animals, actions], join_with='_')
+
+    spec_builder.combine('email', refs=[handles, domains], join_with='@')
+
+    spec = spec_builder.build()
+
+    first = next(spec.generator(1))
+    assert first['email'].startswith('zebra_fling@')
+
+
+def test_create_key_list():
+    entries = [builder.FieldInfo('key1', 'value1'), builder.FieldInfo('key2', 'value2')]
+    keys = builder._create_key_list(entries)
+    assert keys == ['key1', 'key2']
+
+
+short_hand_tests = [
+    ("network:ipv4?cidr=192.168.0.0/16", "network", "192.168."),
+    ("dates:date?duration_days=1&start=15-Dec-2050 12:00&format=%d-%b-%Y %H:%M", "dates", "-Dec-2050")
+]
+
+
+@pytest.mark.parametrize("key_as_spec,field_name,first_value_contains", short_hand_tests)
+def test_shorthand_key_support(key_as_spec, field_name, first_value_contains):
+    spec_builder = builder.Builder()
+    spec_builder.add_field(key_as_spec, {})
+    spec = spec_builder.build()
+    first = next(spec.generator(1, enforce_schema=True))
+    assert field_name in first
+    assert first_value_contains in str(first[field_name])
+
+
+def test_spec_builder():
+    spec_builder = builder.spec_builder()
+    spec = spec_builder.values('name', ['a', 'b', 'c']).to_spec()
+    assert isinstance(spec, DataSpec)
+
+    single = next(spec.generator(1))
+    assert single == {'name': 'a'}
+
+
+def test_distribution_as_count():
+    spec_builder = builder.spec_builder()
+    distribution = distributions.uniform(1, 3)
+    spec = spec_builder.values('name', ['a', 'b', 'c'], count=distribution).to_spec()
+    assert isinstance(spec, DataSpec)
+
+    single = next(spec.generator(1))
+    assert 1 <= len(single['name']) < 3
diff --git a/tests/test_char_class.py b/tests/test_char_class.py
index 72a7b47..dcf38fb 100644
--- a/tests/test_char_class.py
+++ b/tests/test_char_class.py
@@ -1,16 +1,12 @@
 import string
 import pytest
-from dataspec import Loader, SpecException
-from dataspec.type_handlers import char_class_handler
+from dataspec import builder, Loader, SpecException
+from dataspec.supplier.core import char_class
 
 
 def test_char_class_no_data_element():
-    spec = {
-        "name": {
-            "type": "char_class",
-            "config": {"count": 4}
-        }
-    }
+    spec = _char_class_spec(data="special", count=4)
+    spec['name'].pop('data')
 
     with pytest.raises(SpecException):
         Loader(spec).get('name')
@@ -18,80 +14,38 @@ def test_char_class_no_data_element():
 
 def test_char_class_special_exclude():
     exclude = "&?!."
-    spec = {
-        "name": {
-            "type": "char_class",
-            "data": "special",
-            "config": {
-                "min": 1,
-                "max": 5,
-                "exclude": exclude
-            }
-        }
-    }
+    spec = _char_class_spec(data="special", min=1, max=5, exclude=exclude)
 
     supplier = Loader(spec).get('name')
     _verify_values(supplier, 1, 5, exclude)
 
 
 def test_char_class_word():
-    spec = {
-        "name": {
-            "type": "char_class",
-            "data": "word",
-            "config": {"count": 4}
-        }
-    }
+    spec = _char_class_spec(data="special", count=4)
 
     supplier = Loader(spec).get('name')
     _verify_values(supplier, 4, 4)
 
 
 def test_char_class_stats_config():
-    spec = {
-        "name": {
-            "type": "char_class",
-            "data": "word",
-            "config": {
-                "mean": 5,
-                "stddev": 2,
-                "min": 3,
-                "max": 8
-            }
-        }
-    }
+    spec = _char_class_spec(data="word", mean=5, stddev=2, min=3, max=8)
 
     supplier = Loader(spec).get('name')
     _verify_values(supplier, 3, 8)
 
 
 def test_char_class_printable():
-    spec = {
-        "name": {
-            "type": "cc-printable",
-            "config": {
-                "mean": 3,
-                "stddev": 2,
-                "min": 1,
-                "max": 5
-            }
-        }
-    }
+    spec = _cc_abbrev_spec(abbrev="printable", mean=3, stddev=2, min=1, max=5)
 
     supplier = Loader(spec).get('name')
     _verify_values(supplier, 1, 5)
 
 
 def test_char_class_abbreviations():
-    abbreviations = ['cc-' + key for key in char_class_handler._CLASS_MAPPING.keys()]
+    abbreviations = ['cc-' + key for key in char_class._CLASS_MAPPING.keys()]
 
     for abbreviation in abbreviations:
-        spec = {
-            "name": {
-                "type": abbreviation,
-                "config": {"count": 7}
-            }
-        }
+        spec = _cc_abbrev_spec(abbrev=abbreviation, count=7)
 
         supplier = Loader(spec).get('name')
         _verify_values(supplier, 7, 7)
@@ -99,17 +53,11 @@ def test_char_class_abbreviations():
 
 def test_char_class_multiple_classes():
     exclude = "CUSTOM"
-    spec = {
-        "name": {
-            "type": "char_class",
-            "data": ["lower", "digits", "CUSTOM"],
-            "config": {
-                "exclude": exclude
-            }
-        }
-    }
-
-    value = Loader(spec).get('name').next(0)
+    spec = _char_class_spec(data=["lower", "digits", "CUSTOM"], exclude=exclude)
+
+    supplier = Loader(spec).get('name')
+    value = supplier.next(0)
+    assert isinstance(value, str)
     for char in value:
         assert char in string.ascii_lowercase or char in string.digits
 
@@ -120,3 +68,15 @@ def _verify_values(supplier, min_size, max_size, exclude='', iterations=100):
         assert min_size <= len(value) <= max_size
         for excluded in exclude:
             assert excluded not in value
+
+
+def _char_class_spec(data, **config):
+    return builder.Builder() \
+        .add_field("name", builder.char_class(data=data, **config)) \
+        .build()
+
+
+def _cc_abbrev_spec(abbrev, **config):
+    return builder.Builder() \
+        .add_field("name", builder.char_class_abbrev(cc_abbrev=abbrev, **config)) \
+        .build()
diff --git a/tests/test_combine.py b/tests/test_combine.py
index 770ddc5..aeaa657 100644
--- a/tests/test_combine.py
+++ b/tests/test_combine.py
@@ -1,68 +1,49 @@
 import pytest
 from dataspec.loader import Loader
 import dataspec.suppliers as suppliers
-from dataspec import SpecException
-
-
-# to trigger registration
+from dataspec import builder, SpecException
 
 
 def test_neither_refs_nor_fields_specified():
-    spec = {"field:combine": {}}
+    spec = _combine_spec_refs(["ONE", "TWO"])
+    spec['field'].pop("refs")
     _test_invalid_combine_spec(spec)
 
 
 def test_refs_specified_but_not_defined():
-    spec = {"field:combine": {"refs": ["ONE", "TWO"]}}
+    spec = _combine_spec_refs(["ONE", "TWO"])
+    spec['refs'].pop('ONE')
+    spec['refs'].pop('TWO')
     _test_invalid_combine_spec(spec)
 
 
 def test_refs_specified_but_not_all_defined():
-    spec = {
-        "field:combine": {"refs": ["ONE", "TWO"]},
-        "refs": {
-            "ONE": ["a", "b", "c"]
-        }
-    }
+    spec = _combine_spec_refs(["ONE", "TWO"])
+    spec['refs'].pop('ONE')
     _test_invalid_combine_spec(spec)
 
 
 def test_fields_specified_but_not_all_defined():
-    spec = {
-        "field:combine": {"fields": ["ONE", "TWO"]},
-        "ONE": ["a", "b", "c"]
-    }
+    spec = _combine_spec_fields(["one", "two"])
+    spec.pop('one')
     _test_invalid_combine_spec(spec)
 
 
 def test_combine_list_no_refs_invalid():
-    spec = {"field": {"type": "combine-list"}}
+    spec = _combine_list_spec([["ONE", "TWO"]])
+    spec.pop('refs')
     _test_invalid_combine_spec(spec)
 
 
 def test_combine_list_empty_refs_invalid():
-    spec = {"field": {"type": "combine-list", "refs": []}}
-    _test_invalid_combine_spec(spec)
-
-
-def test_combine_list_single_list_refs_invalid():
-    spec = {"field": {"type": "combine-list", "refs": ["ONE"]}, "refs": {"ONE": "uno"}}
+    spec = _combine_list_spec([["ONE", "TWO"]])
+    spec['refs'] = {}
     _test_invalid_combine_spec(spec)
 
 
 def test_refs_specified_but_invalid_type():
-    spec = {
-        "field:combine": {"refs": ["ONE", "TWO"]},
-        "refs": {
-            "ONE": ["a", "b", "c"],
-            "TWO:configref": {
-                "config": {
-                    "prefix": "foo",
-                    "suffix": "@foo.bar"
-                }
-            }
-        }
-    }
+    spec = _combine_list_spec([["ONE", "TWO"]])
+    spec['refs']['TWO'] = builder.configref(prefix='foo', suffix='@bar')
     _test_invalid_combine_spec(spec)
 
 
@@ -86,46 +67,69 @@ def test_combine_lists():
 
 
 def test_combine_fields():
-    spec = {
-        "full_name:combine?join_with= ": {"fields": ["first", "last"]},
-        "first": ["bob", "rob", "ann", "sue"],
-        "last": ["smith", "jones", "frank", "wee"]
-    }
+    spec = builder.Builder() \
+        .add_field("first", builder.values(["bob", "rob", "ann", "sue"])) \
+        .add_field("last", builder.values(["smith", "jones", "frank", "wee"])) \
+        .add_field("full_name", builder.combine(fields=["first", "last"], join_with=' ')) \
+        .build()
+
     supplier = Loader(spec).get('full_name')
     assert supplier.next(0) == 'bob smith'
 
 
 def test_combine_list_spec_valid_but_weird1():
-    spec = {"field": {"type": "combine-list", "refs": [["ONE"]]}, "refs": {"ONE": "uno"}}
+    spec = _combine_list_spec([["uno"]])
     supplier = Loader(spec).get('field')
     assert supplier.next(0) == 'uno'
     assert supplier.next(1) == 'uno'
 
 
 def test_combine_list_spec_valid_but_weird2():
-    spec = {"field": {"type": "combine-list", "refs": [["ONE", "TWO"]]}, "refs": {"ONE": "uno", "TWO": "dos"}}
+    spec = _combine_list_spec([["uno", "dos"]])
     supplier = Loader(spec).get('field')
     assert supplier.next(0) == 'unodos'
     assert supplier.next(1) == 'unodos'
 
 
 def test_combine_list_spec_valid_normal():
-    spec = {
-        "field": {
-            "type": "combine-list",
-            "refs": [
-                ["ONE", "TWO"],
-                ["TWO", "TRE"],
-                ["TRE", "ONE"]
-            ]
-        },
-        "refs": {
-            "ONE": "uno",
-            "TWO": "dos",
-            "TRE": "tres"
-        }
-    }
+    ref_lists = [
+        ["ONE", "TWO"],
+        ["TWO", "TRE"],
+        ["TRE", "ONE"]
+    ]
+    spec = builder.Builder() \
+        .add_field("field", builder.combine_list(refs=ref_lists)) \
+        .add_ref("ONE", builder.values('uno')) \
+        .add_ref("TWO", builder.values('dos')) \
+        .add_ref("TRE", builder.values('tres')) \
+        .build()
+
     supplier = Loader(spec).get('field')
     assert supplier.next(0) == 'unodos'
     assert supplier.next(1) == 'dostres'
     assert supplier.next(2) == 'tresuno'
+
+
+def _combine_spec_refs(ref_names, **config):
+    build = builder.Builder() \
+        .add_field("field", builder.combine(refs=ref_names, **config))
+    for name in ref_names:
+        build.add_ref(name, builder.values(name))
+    return build.build()
+
+
+def _combine_spec_fields(field_names, **config):
+    build = builder.Builder() \
+        .add_field("field", builder.combine(fields=field_names, **config))
+    for name in field_names:
+        build.add_field(name, builder.values(name))
+    return build.build()
+
+
+def _combine_list_spec(ref_lists, **config):
+    build = builder.Builder() \
+        .add_field("field", builder.combine_list(refs=ref_lists, **config))
+    for ref_list in ref_lists:
+        for name in ref_list:
+            build.add_ref(name, builder.values(name))
+    return build.build()
diff --git a/tests/test_csv.py b/tests/test_csv.py
index 98cdf6f..d1b1e36 100644
--- a/tests/test_csv.py
+++ b/tests/test_csv.py
@@ -1,18 +1,18 @@
 import os
 import pytest
 from dataspec.loader import Loader
-from dataspec import SpecException
+from dataspec import builder, SpecException
 # need this to trigger registration
-from dataspec.type_handlers import csv_handler
+from dataspec.supplier.core import csv
 
-test_dir = f'{os.path.dirname(os.path.realpath(__file__))}/data'
+test_dir = os.sep.join([os.path.dirname(os.path.realpath(__file__)), 'data'])
 
 
 # test data from https://wiki.splunk.com/Http_status.csv
 
 def test_csv_valid_with_header_indexed_column():
-    spec = _build_csv_spec('status', {})
-    loader = Loader(spec, datadir=test_dir)
+    spec = _build_csv_spec('status')
+    loader = Loader(spec, data_dir=test_dir)
     supplier = loader.get('status')
 
     assert supplier.next(0) == '100'
@@ -23,16 +23,16 @@ def test_csv_valid_with_header_indexed_column():
 
 
 def test_csv_valid_no_header_indexed_column():
-    spec = _build_csv_spec('status', {"datafile": "test_no_headers.csv", "headers": False})
-    loader = Loader(spec, datadir=test_dir)
+    spec = _build_csv_spec('status', datafile="test_no_headers.csv", headers=False)
+    loader = Loader(spec, data_dir=test_dir)
     supplier = loader.get('status')
 
     assert supplier.next(0) == '100'
 
 
 def test_csv_valid_with_header_field_name_column():
-    spec = _build_csv_spec('status', {"column": "status"})
-    loader = Loader(spec, datadir=test_dir)
+    spec = _build_csv_spec('status', column="status")
+    loader = Loader(spec, data_dir=test_dir)
     supplier = loader.get('status')
 
     assert supplier.next(0) == '100'
@@ -40,7 +40,7 @@ def test_csv_valid_with_header_field_name_column():
 
 def test_csv_valid_with_header_field_name_column_shorthand():
     spec = {"status_desc:csv?datafile=test.csv&headers=true&column=2": {}}
-    loader = Loader(spec, datadir=test_dir)
+    loader = Loader(spec, data_dir=test_dir)
     supplier = loader.get('status_desc')
 
     assert supplier.next(0) == 'Continue'
@@ -48,7 +48,7 @@ def test_csv_valid_with_header_field_name_column_shorthand():
 
 def test_csv_valid_sample_mode():
     spec = {"status_desc:csv?datafile=test.csv&headers=true&column=2&sample=true": {}}
-    loader = Loader(spec, datadir=test_dir)
+    loader = Loader(spec, data_dir=test_dir)
     supplier = loader.get('status_desc')
 
     assert supplier.next(0) is not None
@@ -56,15 +56,15 @@ def test_csv_valid_sample_mode():
 
 def test_csv_valid_sample_mode_with_count():
     spec = {"status_desc:csv?datafile=test.csv&headers=true&column=2&sample=true&count=2": {}}
-    loader = Loader(spec, datadir=test_dir)
+    loader = Loader(spec, data_dir=test_dir)
     supplier = loader.get('status_desc')
 
     assert len(supplier.next(0)) == 2
 
 
-def test_csv_valid_sample_mode_with_count_as_array():
+def test_csv_valid_sample_mode_with_count_as_list():
     spec = {"status_desc:csv?datafile=test.csv&headers=true&column=2&sample=true": {"config": {"count": [4, 3, 2]}}}
-    loader = Loader(spec, datadir=test_dir)
+    loader = Loader(spec, data_dir=test_dir)
     supplier = loader.get('status_desc')
 
     assert len(supplier.next(0)) == 4
@@ -74,20 +74,20 @@ def test_csv_valid_sample_mode_with_count_as_array():
 
 def test_invalid_csv_config_unknown_key():
     # column name should be status not status_code
-    spec = _build_csv_spec('status', {"column": "status_code"})
+    spec = _build_csv_spec('status', column="status_code")
     _test_invalid_csv_config('status', spec)
 
 
 def _test_invalid_csv_config(key, spec):
-    loader = Loader(spec)
-    with pytest.raises(SpecException):
-        loader.get(key)
+    loader = Loader(spec, data_dir=test_dir)
+    with pytest.raises(SpecException) as err:
+        next(spec.generator(1, data_dir=test_dir))
 
 
 def test_csv_single_column():
     # we don't specify the column number or name, so default is to expect single column of values
     spec = {"user_agent:csv?datafile=single_column.csv&headers=false": {}}
-    loader = Loader(spec, datadir=test_dir)
+    loader = Loader(spec, data_dir=test_dir)
     supplier = loader.get('user_agent')
 
     expected = 'Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.1.2) Gecko/20090803 Firefox/3.5.2 Slackware'
@@ -95,39 +95,36 @@ def test_csv_single_column():
     assert supplier.next(4) == expected
 
 
-def _build_csv_spec(field_name, config_changes):
+def _build_csv_spec(field_name, **config):
     base = {
-        field_name: {
-            "type": "csv",
-            "config": {
-                "datafile": "test.csv",
-                "headers": True,
-                "column": 1
-            }
-        }
+        "datafile": "test.csv",
+        "headers": True,
+        "column": 1
     }
-    base[field_name]['config'].update(config_changes)
-    return base
+    base.update(config)
+    return builder.Builder() \
+        .add_field(field_name, builder.csv(**base)) \
+        .build()
 
 
-def test_buffred_csv_end_of_data_raises_spec_exception():
+def test_buffered_csv_end_of_data_raises_spec_exception():
     csv_path = f'{test_dir}/test.csv'
-    csv_data = csv_handler.BufferedCsvData(csv_path, ',', '"', True, 5)
+    csv_data = csv.BufferedCsvData(csv_path, ',', '"', True, 5)
     with pytest.raises(SpecException):
         csv_data.next('status', 100, False, 1)
 
 
-def test_buffred_csv_does_not_support_sample_mode():
+def test_buffered_csv_does_not_support_sample_mode():
     csv_path = f'{test_dir}/test.csv'
     do_sampling = True
-    csv_data = csv_handler.BufferedCsvData(csv_path, ',', '"', True, 5)
+    csv_data = csv.BufferedCsvData(csv_path, ',', '"', True, 5)
     with pytest.raises(SpecException):
         csv_data.next('status', 0, do_sampling, 1)
 
 
-def test_buffred_csv_does_not_support_count_greater_than_one():
+def test_buffered_csv_does_not_support_count_greater_than_one():
     csv_path = f'{test_dir}/test.csv'
     invalid_count = 2
-    csv_data = csv_handler.BufferedCsvData(csv_path, ',', '"', True, 5)
+    csv_data = csv.BufferedCsvData(csv_path, ',', '"', True, 5)
     with pytest.raises(SpecException):
         csv_data.next('status', 0, False, invalid_count)
diff --git a/tests/test_date.py b/tests/test_date.py
index 3eda003..957183d 100644
--- a/tests/test_date.py
+++ b/tests/test_date.py
@@ -1,64 +1,81 @@
+import datetime
 import re
+
+import pytest
+
+import dataspec
 from dataspec.loader import Loader
+from dataspec import builder
 # need this to trigger type handler registration
-from dataspec.type_handlers import date_handler
+from dataspec.supplier.core import date
 
 
 def test_basic_spec():
-    spec = {"foo": {"type": "date"}}
+    spec = _date_spec()
     values = _get_unique_values(spec, 'foo')
     assert len(values) > 0
 
 
-def test_date_delta():
-    config = {"delta_days": 1}
-    spec = {"foo": {"type": "date", "config": config}}
-    values = _get_unique_values(spec, 'foo')
-    # this should create three unique dates
-    assert len(values) == 3
+date_duration_tests = [
+    (0, 1, 2),
+    (1, 1, 2),
+    (5, 5, 6),
+    (22, 22, 23),
+    (55, 55, 56),
+]
+
 
+@pytest.mark.parametrize("duration, min, max", date_duration_tests)
+def test_spec_builder(duration, min, max):
+    spec = _date_spec(duration_days=duration)
+    values = _get_unique_values(spec, 'foo', iterations=1000)
+    # total number of values created should be in this range
+    assert min <= len(values) <= max
 
-def test_date_anchor_positive_delta():
-    config = {"delta_days": 1, "anchor": "02-01-2050"}
-    spec = {"foo": {"type": "date", "config": config}}
-    _test_date_spec(spec, 'foo', 100, ['01-01-2050', '02-01-2050', '03-01-2050'])
 
+def test_date_start_positive_duration():
+    config = {"duration_days": 3, "start": "02-01-2050"}
+    spec = _date_spec(**config)
+    _test_date_spec(spec, 'foo', 100, ['02-01-2050', '03-01-2050', '04-01-2050'])
 
-def test_date_anchor_negative_delta_string():
-    config = {"delta_days": "-1", "anchor": "02-01-2050"}
-    spec = {"foo": {"type": "date", "config": config}}
-    _test_date_spec(spec, 'foo', 1000, ['01-01-2050', '02-01-2050', '03-01-2050'])
 
+def test_date_start_format():
+    config = {"duration_days": 3, "start": "02-Feb-2050", "format": "%d-%b-%Y"}
+    spec = _date_spec(**config)
+    _test_date_spec(spec, 'foo', 100, ['02-Feb-2050', '03-Feb-2050', '04-Feb-2050'])
 
-def test_delta_as_array():
-    config = {"delta_days": [1, 2], "anchor": "02-01-2050"}
-    spec = {"foo": {"type": "date", "config": config}}
-    # expect one day behind and two days ahead
-    _test_date_spec(spec, 'foo', 1000, ['01-01-2050', '02-01-2050', '03-01-2050', '03-01-2050'])
 
+def test_date_start_and_end_with_format():
+    config = {"start": "02-Feb-2050", "end": "04-Feb-2050", "format": "%d-%b-%Y"}
+    spec = _date_spec(**config)
+    _test_date_spec(spec, 'foo', 100, ['02-Feb-2050', '03-Feb-2050', '04-Feb-2050'])
 
-def test_date_anchor_format():
-    config = {"delta_days": 1, "anchor": "02-Feb-2050", "format": "%d-%b-%Y"}
-    spec = {"foo": {"type": "date", "config": config}}
-    _test_date_spec(spec, 'foo', 100, ['01-Feb-2050', '02-Feb-2050', '03-Feb-2050'])
 
+def test_date_start_format_iso():
+    config = {"duration_days": 3, "start": "02-Feb-2050", "format": "%d-%b-%Y"}
+    spec = _date_iso_spec(**config)
+    _test_date_start_format_iso_type(spec)
 
-def test_date_anchor_format_iso():
-    _test_date_anchor_format_iso_type('date.iso')
 
+def test_date_start_format_iso_microseconds():
+    config = {"start": "02-Feb-2050", "end": "10-Feb-2050", "format": "%d-%b-%Y"}
+    spec = _date_iso_us_spec(**config)
+    _test_date_start_format_iso_type(spec)
 
-def test_date_anchor_format_iso_microseconds():
-    _test_date_anchor_format_iso_type('date.iso.us')
 
+def test_date_end_before_start():
+    config = {"start": "15-Feb-2050", "end": "01-Feb-2050", "format": "%d-%b-%Y"}
+    spec = _date_spec(**config)
+    with pytest.raises(dataspec.SpecException):
+        next(spec.generator(1))
 
-def _test_date_anchor_format_iso_type(type_key):
-    config = {"delta_days": 1, "anchor": "02-Feb-2050", "format": "%d-%b-%Y"}
-    spec = {"foo": {"type": type_key, "config": config}}
+
+def _test_date_start_format_iso_type(spec):
     loader = Loader(spec)
     supplier = loader.get('foo')
     # only the date portion of the iso date
     values = list(set([_date_only(supplier.next(i)) for i in range(100)]))
-    for instance in ['2050-02-01', '2050-02-02', '2050-02-03']:
+    for instance in ['2050-02-02', '2050-02-03', '2050-02-04']:
         assert instance in values
 
 
@@ -67,10 +84,22 @@ def _date_only(iso_string):
 
 
 def test_date_offset():
-    # anchored to 7 Feb - 5 day offset gives center of 2 Feb +- 1 day
-    config = {"delta_days": 1, "anchor": "07-Feb-2050", "format": "%d-%b-%Y", "offset": 5}
-    spec = {"foo": {"type": "date", "config": config}}
-    _test_date_spec(spec, 'foo', 100, ['01-Feb-2050', '02-Feb-2050', '03-Feb-2050'])
+    # started to 7 Feb - 5 day offset gives center of 2 Feb +- 1 day
+    config = {"duration_days": 1, "start": "07-Feb-2050", "format": "%d-%b-%Y", "offset": 5}
+    spec = _date_spec(**config)
+    _test_date_spec(spec, 'foo', 100, ['02-Feb-2050'])
+
+
+def test_date_center_date():
+    config = {"center_date": "15-Feb-2050", "stddev_days": 5, "format": "%d-%b-%Y"}
+    spec = _date_spec(**config)
+    _test_date_spec(spec, 'foo', 100, ['13-Feb-2050', '14-Feb-2050', '15-Feb-2050', '16-Feb-2050', '17-Feb-2050'])
+
+
+def test_date_center_date_stddev_only():
+    config = {"stddev_days": 5, "format": "%d-%b-%Y"}
+    spec = _date_spec(**config)
+    _test_date_spec(spec, 'foo', 100, [datetime.datetime.now().strftime(config['format'])])
 
 
 def _test_date_spec(spec, key, iterations, expected):
@@ -83,3 +112,15 @@ def _get_unique_values(spec, key, iterations=100):
     loader = Loader(spec)
     supplier = loader.get(key)
     return list(set([supplier.next(i) for i in range(iterations)]))
+
+
+def _date_spec(**config):
+    return builder.Builder().add_field('foo', builder.date(**config)).build()
+
+
+def _date_iso_spec(**config):
+    return builder.Builder().add_field('foo', builder.date_iso(**config)).build()
+
+
+def _date_iso_us_spec(**config):
+    return builder.Builder().add_field('foo', builder.date_iso_us(**config)).build()
diff --git a/tests/test_defaults.py b/tests/test_defaults.py
new file mode 100644
index 0000000..37b3ece
--- /dev/null
+++ b/tests/test_defaults.py
@@ -0,0 +1,22 @@
+import dataspec
+
+
+@dataspec.registry.defaults('dino')
+def _get_default_dinosaur():
+    return 'velociraptor'
+
+
+def test_default_registry():
+    assert dataspec.types.get_default('dino') == 'velociraptor'
+
+
+def test_default_registry_set():
+    dataspec.types.set_default('dino', 'triceratops')
+
+    assert dataspec.types.get_default('dino') == 'triceratops'
+
+
+def test_default_registry_get_all():
+    defaults = dataspec.types.all_defaults()
+    assert type(defaults) == dict
+    assert len(defaults) > 0
diff --git a/tests/test_distributions.py b/tests/test_distributions.py
new file mode 100644
index 0000000..0226523
--- /dev/null
+++ b/tests/test_distributions.py
@@ -0,0 +1,68 @@
+import pytest
+import dataspec
+from dataspec import distributions
+
+
+def test_uniform_distribution():
+    dist_func = dataspec.registry.distribution.get('uniform')
+    distribution = dist_func(start=5, end=10)
+
+    values = set([int(distribution.next_value()) for _ in range(100)])
+    assert len(values) == 5
+
+
+def test_normal_distribution():
+    dist_func = dataspec.registry.distribution.get('normal')
+    distribution = dist_func(mean=5, stddev=1)
+
+    values = set([int(distribution.next_value()) for _ in range(100)])
+    assert len(values) >= 5
+
+
+def test_bounded_normal_distribution():
+    dist_func = dataspec.registry.distribution.get('normal')
+    distribution = dist_func(mean=5, stddev=1, min=2, max=7)
+
+    values = set([int(distribution.next_value()) for _ in range(100)])
+    for value in values:
+        assert 2 <= value <= 7
+
+
+valid_funcs = [
+    ('uniform(start=5, end=10)', 5),
+    ('normal(mean=5, stddev=2)', 5),
+    ('gauss(mean=5, stddev=2)', 5),
+    ('gaussian(mean=5, stddev=2)', 5),
+    ('gaussian(mean=5, stddev=2, min=3, max=9)', 5),
+    ('gaussian(mean=33, stddev=5, max=50)', 10),
+]
+
+
+@pytest.mark.parametrize("string_func,min_values_generated", valid_funcs)
+def test_from_string_normal(string_func, min_values_generated):
+    distribution = dataspec.distributions.from_string(string_func)
+
+    values = set([int(distribution.next_value()) for _ in range(100)])
+    assert len(values) >= min_values_generated
+
+
+invalid_funcs = [
+    "normal(5, 2)",               # no names
+    "noormal(mean=5, stddev=2)",  # invalid spelling of distribution
+    "normal(means=5, stddev=2)",  # invalid spelling of param
+    "",                           # empty string not valid
+    " ",                          # white space not valid
+    "()",                         # open close parens not valid
+    "normal()",                   # no args
+    "uniform(start=20, end=30",   # missing closing paren
+    "uniform)start=20, end=30(",  # inverted parens
+    "gauss(mean=33, stddev=5, maximum=50)"  # maximum is not a valid extra arg
+    "gauss(mean=33, stddev=5, )"  # extra comma
+]
+
+
+@pytest.mark.parametrize("invalid_func_str", invalid_funcs)
+def test_invalid_from_string(invalid_func_str):
+    with pytest.raises(ValueError):
+        dist = dataspec.distributions.from_string(invalid_func_str)
+        dist.next_value()
diff --git a/tests/test_generate.py b/tests/test_generate.py
new file mode 100644
index 0000000..44a6ae4
--- /dev/null
+++ b/tests/test_generate.py
@@ -0,0 +1,50 @@
+import os
+from pathlib import Path
+from dataspec import builder
+
+
+def test_generator_string_template():
+    names = ['bob', 'bobby', 'robert', 'bobo']
+    spec = builder.single_field('name', builder.values(names)).build()
+
+    template = 'Name: {{ name }}'
+
+    gen = spec.generator(
+        iterations=4,
+        template=template)
+
+    for name in names:
+        assert next(gen) == f'Name: {name}'
+
+
+def test_generator_path_template():
+    spec = build_spec({'A': 1, 'B': 2, 'C': 3})
+
+    dir_path = Path(os.path.dirname(os.path.realpath(__file__)))
+    template = dir_path / 'data' / 'template.jinja'
+
+    gen = spec.generator(
+        iterations=1,
+        template=template)
+
+    rendered = next(gen)
+    assert rendered == 'A:1, B:2, C:3'
+
+
+def test_generator_no_template():
+    names = ['bob', 'bobby', 'robert', 'bobo']
+    spec = builder.single_field('name', builder.values(names)).build()
+
+    gen = spec.generator(
+        iterations=4,
+        template=None)
+
+    records = list(gen)
+    assert len(records) == 4
+
+
+def build_spec(data):
+    spec_builder = builder.Builder()
+    for key, value in data.items():
+        spec_builder.add_field(key, value)
+    return spec_builder.build()
diff --git a/tests/test_geo.py b/tests/test_geo.py
index 7c20590..a0644a2 100644
--- a/tests/test_geo.py
+++ b/tests/test_geo.py
@@ -1,39 +1,21 @@
 import decimal
-from dataspec import Loader
+from dataspec import builder, Loader
 # need this to trigger registration
-from dataspec.type_handlers import geo_handler
+from dataspec.supplier.core import geo
 
 
 def test_geo_lat_default_precision():
-    spec = {
-        "lat": {
-            "type": "geo.lat"
-        }
-    }
+    spec = _geo_lat_spec()
     _test_geo_spec_falls_in_range(spec, 'lat', -90.0, 90.0, -4)
 
 
 def test_geo_lat_precision():
-    spec = {
-        "lat": {
-            "type": "geo.lat",
-            "config": {
-                "precision": 1
-            }
-        }
-    }
+    spec = _geo_lat_spec(precision=1)
     _test_geo_spec_falls_in_range(spec, 'lat', -90.0, 90.0, -1)
 
 
 def test_geo_long_precision():
-    spec = {
-        "long": {
-            "type": "geo.long",
-            "config": {
-                "precision": 3
-            }
-        }
-    }
+    spec = _geo_long_spec(precision=3)
     _test_geo_spec_falls_in_range(spec, 'long', -180.0, 180.0, -3)
 
 
@@ -49,14 +31,7 @@ def _test_geo_spec_falls_in_range(spec, key, start, end, exponent):
 
 
 def test_geo_spec_pair_default_order():
-    spec = {
-        "pair": {
-            "type": "geo.pair",
-            "config": {
-                "precision": 1
-            }
-        }
-    }
+    spec = _geo_pair_spec(precision=1)
     supplier = Loader(spec).get('pair')
     value = supplier.next(0)
     parts = value.split(',')
@@ -65,15 +40,7 @@ def test_geo_spec_pair_default_order():
 
 
 def test_geo_spec_pair_lat_first():
-    spec = {
-        "pair": {
-            "type": "geo.pair",
-            "config": {
-                "precision": 2,
-                "lat_first": "yes"
-            }
-        }
-    }
+    spec = _geo_pair_spec(precision=2, lat_first="yes")
     supplier = Loader(spec).get('pair')
     value = supplier.next(0)
     parts = value.split(',')
@@ -86,17 +53,12 @@ def test_geo_spec_pair_reduced_ranges():
     end_lat = 75.0
     start_long = -180.0
     end_long = -90.0
-    spec = {
-        "pair": {
-            "type": "geo.pair",
-            "config": {
-                "start_lat": start_lat,
-                "end_lat": end_lat,
-                "start_long": start_long,
-                "end_long": end_long
-            }
-        }
-    }
+    spec = _geo_pair_spec(
+        start_lat=start_lat,
+        end_lat=end_lat,
+        start_long=start_long,
+        end_long=end_long)
+
     supplier = Loader(spec).get('pair')
     value = supplier.next(0)
     parts = value.split(',')
@@ -109,14 +71,9 @@ def test_geo_spec_pair_reduced_ranges_bbox():
     end_lat = -45.0
     start_long = 90.0
     end_long = 180.0
-    spec = {
-        "pair": {
-            "type": "geo.pair",
-            "config": {
-                "bbox": [start_long, start_lat, end_long, end_lat]
-            }
-        }
-    }
+
+    spec = _geo_pair_spec(bbox=[start_long, start_lat, end_long, end_lat])
+
     supplier = Loader(spec).get('pair')
     value = supplier.next(0)
     parts = value.split(',')
@@ -129,15 +86,9 @@ def test_geo_pair_as_list():
     end_lat = 45.0
     start_long = 50.0
     end_long = 60.0
-    spec = {
-        "pair": {
-            "type": "geo.pair",
-            "config": {
-                "bbox": [start_long, start_lat, end_long, end_lat],
-                "as_list": True
-            }
-        }
-    }
+
+    spec = _geo_pair_spec(bbox=[start_long, start_lat, end_long, end_lat], as_list=True)
+    
     supplier = Loader(spec).get('pair')
     value = supplier.next(0)
     assert isinstance(value, list)
@@ -158,3 +109,21 @@ def _verify_in_range_and_has_precision(value, start, end, exponent):
     assert as_decimal >= start
     assert as_decimal <= end
     assert as_decimal.as_tuple().exponent == exponent
+
+
+def _geo_lat_spec(**config):
+    return builder.Builder() \
+        .add_field('lat', builder.geo_lat(**config)) \
+        .build()
+
+
+def _geo_long_spec(**config):
+    return builder.Builder() \
+        .add_field('long', builder.geo_long(**config)) \
+        .build()
+
+
+def _geo_pair_spec(**config):
+    return builder.Builder() \
+        .add_field('pair', builder.geo_pair(**config)) \
+        .build()
diff --git a/tests/test_ip.py b/tests/test_ip.py
index 91088f9..c5044e9 100644
--- a/tests/test_ip.py
+++ b/tests/test_ip.py
@@ -1,22 +1,15 @@
 import pytest
 from dataspec.loader import Loader
-from dataspec import SpecException
+from dataspec import builder, SpecException
 # need this to trigger registration
-from dataspec.type_handlers import ip_handler
+from dataspec.supplier.core import ip_addresses
 
 
 def test_ip_v4_spec():
-    spec = {
-        "fooip": {
-            "type": "ipv4",
-            "config": {
-                "cidr": "192.168.0.0/16"
-            }
-        }
-    }
+    spec = _ipv4_spec(cidr="192.168.0.0/16")
 
     loader = Loader(spec)
-    supplier = loader.get('fooip')
+    supplier = loader.get('network')
 
     value = supplier.next(0)
     assert value.startswith('192.168.')
@@ -155,3 +148,21 @@ def _create_ip_spec_with_base(base):
             "config": {"base": base, "sample": "true"}
         }
     }
+
+
+def _ip_spec(**config):
+    return builder.Builder() \
+        .add_field('network', builder.ip(**config)) \
+        .build()
+
+
+def _ipv4_spec(**config):
+    return builder.Builder() \
+        .add_field('network', builder.ipv4(**config)) \
+        .build()
+
+
+def _ip_precise_spec(**config):
+    return builder.Builder() \
+        .add_field('network', builder.ip_precise(**config)) \
+        .build()
diff --git a/tests/test_key_providers.py b/tests/test_key_providers.py
index ea0a949..3447754 100644
--- a/tests/test_key_providers.py
+++ b/tests/test_key_providers.py
@@ -1,43 +1,24 @@
 import pytest
-from dataspec import key_providers, SpecException
-
-
-def test_invalid_fields_name():
-    spec = {
-        "one": ["uno", "ichi"],
-        "two": ["dos", "ni"],
-        "field_groups": {
-            "one_two": {
-                "weight": 1.0,
-                "name_should_be_fields": ["one", "two"],
-            }
-        }
-    }
-    with pytest.raises(SpecException):
-        key_providers.from_spec(spec).get()
+from dataspec import builder, key_providers, SpecException
 
 
 def test_no_field_groups():
-    spec = {
-        "one": ["uno", "ichi"],
-        "two": ["dos", "ni"],
-    }
+    spec = one_two_builder().build()
     provider = key_providers.from_spec(spec)
 
     assert _get_keys(provider) == ['one', 'two']
 
 
 def test_list_of_fields():
-    spec = {
-        "one": ["uno", "ichi"],
-        "two": ["dos", "ni"],
-        "three": ["tres", "son"],
-        "field_groups": [
-            ["one"],
-            ["one", "two"],
-            ["one", "two", "three"]
-        ]
-    }
+    field_groups = [
+        ["one"],
+        ["one", "two"],
+        ["one", "two", "three"]
+    ]
+
+    spec_builder = one_two_three_builder()
+    spec_builder.add_field_groups(field_groups),
+    spec = spec_builder.build()
     provider = key_providers.from_spec(spec)
 
     assert _get_keys(provider) == ['one']
@@ -51,21 +32,11 @@ def _get_keys(provider):
 
 
 def test_weighted_field_groups():
-    spec = {
-        "one": ["uno", "ichi"],
-        "two": ["dos", "ni"],
-        "three": ["tres", "son"],
-        "field_groups": {
-            "groupA": {
-                "weight": 0.7,
-                "fields": ["one", "two"],
-            },
-            "groupB": {
-                "weight": 0.3,
-                "fields": ["one", "two", "three"]
-            }
-        }
-    }
+    spec_builder = one_two_three_builder()
+    spec_builder.weighted_field_group("groupA", fields=["one", "two"], weight=0.7)
+    spec_builder.weighted_field_group("groupB", fields=["one", "two", "three"], weight=0.3),
+    spec = spec_builder.build()
+
     provider = key_providers.from_spec(spec)
     for _ in range(100):
         field_group, keys = provider.get()
@@ -74,17 +45,26 @@ def test_weighted_field_groups():
 
 
 def test_named_field_groups():
-    spec = {
-        "one": ["uno", "ichi"],
-        "two": ["dos", "ni"],
-        "three": ["tres", "son"],
-        "field_groups": {
-            "groupA": ["one", "two"],
-            "groupB": ["one", "two", "three"]
-        }
-    }
+    spec_builder = one_two_three_builder()
+    spec_builder.named_field_group("groupA", fields=["one", "two"])
+    spec_builder.named_field_group("groupB", fields=["one", "two", "three"]),
+    spec = spec_builder.build()
+
     provider = key_providers.from_spec(spec)
     for _ in range(100):
         field_group, keys = provider.get()
         assert field_group == 'groupA' or field_group == 'groupB'
-        assert keys == ['one', 'two'] or keys == ['one', 'two', 'three']
\ No newline at end of file
+        assert keys == ['one', 'two'] or keys == ['one', 'two', 'three']
+
+
+def one_two_three_builder():
+    spec_builder = one_two_builder()
+    spec_builder.values('three', ["tres", "son"])
+    return spec_builder
+
+
+def one_two_builder():
+    spec_builder = builder.Builder()
+    spec_builder.values('one', ["uno", "ichi"])
+    spec_builder.values('two', ["dos", "ni"])
+    return spec_builder
diff --git a/tests/test_loader.py b/tests/test_loader.py
index 93998ff..6447644 100644
--- a/tests/test_loader.py
+++ b/tests/test_loader.py
@@ -1,23 +1,16 @@
 from dataspec.loader import Loader
 from dataspec.exceptions import SpecException
+from dataspec import builder
 from collections import Counter
 import pytest
 # to trigger registration
-from dataspec.type_handlers import combine, weighted_ref
-
-spec = {
-    'foo': {
-        'type': 'combine',
-        'refs': ['ONE', 'TWO'],
-        'config': {
-            'join_with': ''
-        }
-    },
-    'refs': {
-        'ONE': {'type': 'values', 'data': ['do', 'ca', 'pi']},
-        'TWO': {'type': 'values', 'data': ['g', 't', 'g']}
-    }
-}
+from dataspec.supplier.core import combine, weighted_refs
+
+spec = builder.Builder() \
+    .add_field('foo', builder.combine(['ONE', 'TWO'], join_with='')) \
+    .add_ref('ONE', builder.values(['do', 'ca', 'pi'])) \
+    .add_ref('TWO', builder.values(['g', 't', 'g'])) \
+    .build()
 
 
 def test_load_spec_invalid_key():
@@ -41,12 +34,9 @@ def test_load_spec_missing_type_defaults_to_values():
 
 
 def test_load_spec_undefined_refs():
-    spec_undefined_refs = {
-        'foo': {
-            'type': 'combine',
-            'refs': ['ONE', 'TWO']
-        }
-    }
+    spec_undefined_refs = builder.Builder() \
+        .add_field('foo', builder.combine(['ONE', 'TWO'])) \
+        .build()
     loader = Loader(spec_undefined_refs)
     with pytest.raises(SpecException):
         loader.get('foo')
@@ -62,22 +52,17 @@ def test_load_spec_valid():
 
 
 def test_load_spec_weighted_ref():
-    weighted_ref_spec = {
-        'foo': {
-            'type': 'weightedref',
-            "data": {
-                "POSITIVE": 0.5,
-                "NEGATIVE": 0.4,
-                "NEUTRAL": 0.1
-            }
-
-        },
-        'refs': {
-            'POSITIVE': {'type': 'values', 'data': ['yes']},
-            'NEGATIVE': {'type': 'values', 'data': ['no']},
-            'NEUTRAL': {'type': 'values', 'data': ['meh']}
-        }
+    ref_weights = {
+        "POSITIVE": 0.5,
+        "NEGATIVE": 0.4,
+        "NEUTRAL": 0.1
     }
+    weighted_ref_spec = builder.Builder() \
+        .add_field('foo', builder.weightedref(ref_weights)) \
+        .add_ref('POSITIVE', ['yes']) \
+        .add_ref('NEGATIVE', ['no']) \
+        .add_ref('NEUTRAL', ['meh']) \
+        .build()
     loader = Loader(weighted_ref_spec)
     supplier = loader.get('foo')
 
@@ -102,9 +87,11 @@ def test_shortcut_notation_config_in_key():
 
 
 def test_load_ref_by_name():
-    refs_only_spec = {
-        'refs': {'ONE': 'uno', 'TWO': 'dos'}
-    }
+    refs_only_spec = builder.Builder() \
+        .add_ref('ONE', 'uno') \
+        .add_ref('TWO', 'dos') \
+        .build()
+
     loader = Loader(refs_only_spec)
     assert loader.get('ONE').next(0) == 'uno'
     assert loader.get('TWO').next(0) == 'dos'
diff --git a/tests/test_logging_handler.py b/tests/test_logging_handler.py
new file mode 100644
index 0000000..955c1fe
--- /dev/null
+++ b/tests/test_logging_handler.py
@@ -0,0 +1,7 @@
+from dataspec import logging_handler
+
+
+def test_logging_handler():
+    """ for coverage sake """
+    logging_handler.configure_logging('debug')
+    logging_handler.configure_logging('off')
diff --git a/tests/test_model.py b/tests/test_model.py
new file mode 100644
index 0000000..6221dbb
--- /dev/null
+++ b/tests/test_model.py
@@ -0,0 +1,22 @@
+import dataspec
+
+
+def test_spec_acts_like_dictionary():
+    raw = {
+        "field": {"type": "uuid"}
+    }
+
+    spec = dataspec.DataSpec(raw)
+
+    spec['key'] = ['a', 'b', 'c']
+    assert 'key' in spec
+    assert 'field' in spec
+
+    spec.pop('field')
+    assert 'field' not in spec
+
+    del spec['key']
+    assert 'key' not in spec
+
+    # to get coverage for __repr__
+    print(spec)
diff --git a/tests/test_nested.py b/tests/test_nested.py
index 83789bb..ca42c7a 100644
--- a/tests/test_nested.py
+++ b/tests/test_nested.py
@@ -1,24 +1,18 @@
-from dataspec import Loader
-from dataspec.type_handlers import nested_handler
+from dataspec import builder, Loader
+from dataspec.supplier.core import nested
 
 
 def test_single_nested():
-    spec = {
-        "id:uuid": {},
-        "geo": {
-            "type": "nested",
-            "fields": {
-                "place_id:uuid": {},
-                "coordinates": {
-                    "type": "geo.pair",
-                    "config": {
-                        "as_list": True
-                    }
-                }
-            }
-        }
-    }
-
+    # Geo
+    # - Place
+    # - Coord
+    geo_spec = builder.Builder() \
+        .add_field("place_id:uuid", {}) \
+        .add_field("coordinates", builder.geo_pair(as_list=True))
+    spec = builder.Builder() \
+        .add_field("id:uuid", {}) \
+        .add_field("geo", builder.nested(fields=geo_spec.build())) \
+        .build()
     supplier = Loader(spec).get('geo')
 
     first = supplier.next(0)
@@ -27,27 +21,20 @@ def test_single_nested():
 
 
 def test_multi_nested():
-    spec = {
-        "id:uuid": {},
-        "user:nested": {
-            "fields": {
-                "user_id:uuid": {},
-                "geo": {
-                    "type": "nested",
-                    "fields": {
-                        "place_id:uuid": {},
-                        "coordinates": {
-                            "type": "geo.pair",
-                            "config": {
-                                "as_list": True
-                            }
-                        }
-                    }
-                }
-            }
-        }
-    }
-
+    # User
+    # - Geo
+    # - - Place
+    # - - Coord
+    geo_spec = builder.Builder() \
+        .add_field("place_id:uuid", {}) \
+        .add_field("coordinates", builder.geo_pair(as_list=True))
+    user_spec = builder.Builder() \
+        .add_field("user_id:uuid", {}) \
+        .add_field("geo", builder.nested(fields=geo_spec.build()))
+    spec = builder.Builder() \
+        .add_field("id:uuid", {}) \
+        .add_field("user", builder.nested(fields=user_spec.build())) \
+        .build()
     supplier = Loader(spec).get('user')
 
     first = supplier.next(0)
@@ -59,26 +46,17 @@ def test_multi_nested():
     assert list(second.keys()) == ['place_id', 'coordinates']
 
 
-def test_single_nested_as_array():
-    spec = {
-        "id:uuid": {},
-        "geo": {
-            "type": "nested",
-            "config": {
-                "as_array": "true"
-            },
-            "fields": {
-                "place_id:uuid": {},
-                "coordinates": {
-                    "type": "geo.pair",
-                    "config": {
-                        "as_list": True
-                    }
-                }
-            }
-        }
-    }
-
+def test_single_nested_as_list():
+    # Geo
+    # - Place
+    # - Coord
+    geo_spec = builder.Builder() \
+        .add_field("place_id:uuid", {}) \
+        .add_field("coordinates", builder.geo_pair(as_list=True))
+    spec = builder.Builder() \
+        .add_field("id:uuid", {}) \
+        .add_field("geo", builder.nested(fields=geo_spec.build(), as_list=True)) \
+        .build()
     supplier = Loader(spec).get('geo')
 
     first = supplier.next(0)
diff --git a/tests/test_outputs.py b/tests/test_outputs.py
index 065ee60..f334b36 100644
--- a/tests/test_outputs.py
+++ b/tests/test_outputs.py
@@ -18,7 +18,7 @@ def test_outputs_single_field():
     output.handle('key', 'value')
     output.finished_record()
 
-    _verify_ouput('test_single-0.txt', 'key -> value\n')
+    _verify_output('test_single-0.txt', 'key -> value\n')
 
 
 def test_outputs_record_level():
@@ -30,7 +30,7 @@ def test_outputs_record_level():
         records_per_file=1
     )
 
-    engine = engines.load(f'{test_dir}/data/template.jinja')
+    engine = engines.for_file(f'{test_dir}/data/template.jinja')
     output = outputs.RecordLevelOutput(engine, writer)
 
     # template looks like: A:{{ A }}, B:{{ B }}, C:{{ C }}
@@ -42,10 +42,57 @@ def test_outputs_record_level():
                            group_name='TEST',
                            exclude_internal=True)
 
-    _verify_ouput('test_record-0.txt', 'A:1, B:2, C:3\n')
+    _verify_output('test_record-0.txt', 'A:1, B:2, C:3\n')
 
 
-def _verify_ouput(fild_name, expected_content):
+def test_format_json():
+    as_json = outputs.format_json({'field': 'value'})
+    assert as_json == "{\"field\": \"value\"}"
+
+
+def test_format_json_pretty():
+    as_json = outputs.format_json_pretty({'field': 'value'})
+    assert as_json == "{\n    \"field\": \"value\"\n}"
+
+
+def test_format_csv():
+    as_csv = outputs.format_csv({'field1': 'value1', 'field2': 'value2'})
+    assert as_csv == "value1,value2"
+
+
+def test_single_file_writer(tmpdir):
+    writer = outputs.SingleFileWriter(
+        outdir=tmpdir,
+        outname='foo.bar',
+        overwrite=True
+    )
+
+    writer.write("stuff")
+    with open(os.path.join(tmpdir, 'foo.bar')) as handle:
+        text = handle.read().strip()
+    assert text == 'stuff'
+
+
+def test_single_file_writer_append(tmpdir):
+    writer = outputs.SingleFileWriter(
+        outdir=tmpdir,
+        outname='single_file_append',
+        overwrite=False
+    )
+
+    writer.write("stuff1")
+    writer.write("stuff2")
+    with open(os.path.join(tmpdir, 'single_file_append')) as handle:
+        text = handle.read().strip()
+    assert text == 'stuff1\nstuff2'
+
+
+def test_std_out_writer():
+    # for coverage
+    outputs.StdOutWriter().write("blah")
+
+
+def _verify_output(fild_name, expected_content):
     with open(f'{outdir}/{fild_name}') as handle:
         content = handle.read()
 
diff --git a/tests/test_preprocessor.py b/tests/test_preprocessor.py
index f182c1a..c6f61f2 100644
--- a/tests/test_preprocessor.py
+++ b/tests/test_preprocessor.py
@@ -1,6 +1,6 @@
 from dataspec.preprocessor import preprocess_spec, preprocess_csv_select, preprocess_nested
 from dataspec.preprocessor import _parse_key, _is_spec_data, _update_no_params
-from dataspec import SpecException
+from dataspec import builder, SpecException
 import pytest
 
 parse_key_tests = [
@@ -178,13 +178,13 @@ def test_preprocess_csv_select(input_spec, expected_output_spec):
         {
             "geo": {
                 "type": "nested",
-                "config": {"as_array": "true"},
+                "config": {"as_list": "true"},
                 "fields": {"lat": 55.5, "long": 99.9}
             }
         }, {
             "geo": {
                 "type": "nested",
-                "config": {"as_array": "true"},
+                "config": {"as_list": "true"},
                 "fields": {
                     "lat": {"type": "values", "data": 55.5},
                     "long": {"type": "values", "data": 99.9}
@@ -198,7 +198,7 @@ def test_preprocess_csv_select(input_spec, expected_output_spec):
             "refs": {
                 "geo": {
                     "type": "nested",
-                    "config": {"as_array": "true"},
+                    "config": {"as_list": "true"},
                     "fields": {
                         "lat": 55.5,
                         "long": 99.9
@@ -211,7 +211,7 @@ def test_preprocess_csv_select(input_spec, expected_output_spec):
             "refs": {
                 "geo": {
                     "type": "nested",
-                    "config": {"as_array": "true"},
+                    "config": {"as_list": "true"},
                     "fields": {
                         "lat": {"type": "values", "data": 55.5},
                         "long": {"type": "values", "data": 99.9}
@@ -219,6 +219,12 @@ def test_preprocess_csv_select(input_spec, expected_output_spec):
                 }
             }
         }
+    ),
+    (
+        builder.single_field('enemies', builder.nested(
+            builder.single_field('inner', ['bat', 'slime', 'orc']).build())).build(),
+        builder.single_field('enemies', builder.nested(
+            builder.single_field('inner', builder.values(['bat', 'slime', 'orc'])).build())).build(),
     )
 ]
 
diff --git a/tests/test_random_range.py b/tests/test_random_range.py
index bca79c7..021f9af 100644
--- a/tests/test_random_range.py
+++ b/tests/test_random_range.py
@@ -8,3 +8,4 @@ def test_random_range():
     assert as_decimal >= -180.0
     assert as_decimal <= -90.0
     assert as_decimal.as_tuple().exponent == -7
+
diff --git a/tests/test_range_supplier.py b/tests/test_range_supplier.py
index c617d49..f01bb99 100644
--- a/tests/test_range_supplier.py
+++ b/tests/test_range_supplier.py
@@ -1,31 +1,31 @@
 import pytest
 
-from dataspec import Loader, SpecException
-from dataspec.type_handlers import range_handler
+from dataspec import builder, Loader, SpecException
+from dataspec.supplier.core import range_suppliers
 
 
 def test_range_lists_missing_data():
     with pytest.raises(SpecException):
-        range_handler.configure_range_supplier({'type': 'range_list'}, None)
+        range_suppliers.configure_range_supplier({'type': 'range_list'}, None)
 
 
 def test_range_lists_invalid_data_type():
     with pytest.raises(SpecException):
-        range_handler.configure_range_supplier({'type': 'range_list', 'data': 42}, None)
+        range_suppliers.configure_range_supplier({'type': 'range_list', 'data': 42}, None)
 
 
 def test_range_lists_end_before_start():
     start = 10
     end = 9
     with pytest.raises(SpecException):
-        range_handler.configure_range_supplier({'type': 'range_list', 'data': [start, end]}, None)
+        range_suppliers.configure_range_supplier({'type': 'range_list', 'data': [start, end]}, None)
 
 
 def test_range_lists_valid():
     start = 2
     end = 10
     step = 2
-    supplier = range_handler.configure_range_supplier({'type': 'range_list', 'data': [start, end, step]}, None)
+    supplier = range_suppliers.configure_range_supplier({'type': 'range_list', 'data': [start, end, step]}, None)
 
     expected = [2, 4, 6, 8, 10]
     actual = [supplier.next(i) for i in range(5)]
@@ -37,7 +37,7 @@ def test_range_lists_float_step():
     start = 0
     end = 1
     step = .1
-    supplier = range_handler.configure_range_supplier({'type': 'range_list', 'data': [start, end, step]}, None)
+    supplier = range_suppliers.configure_range_supplier({'type': 'range_list', 'data': [start, end, step]}, None)
 
     expected = [0, .1, .2, .3, .4, .5, .6, .7, .8, .9, 1]
     actual = [supplier.next(i) for i in range(11)]
@@ -48,7 +48,7 @@ def test_range_lists_float_step():
 def test_range_lists_float_start_end():
     start = 0.5
     end = 5.5
-    supplier = range_handler.configure_range_supplier({'type': 'range_list', 'data': [start, end]}, None)
+    supplier = range_suppliers.configure_range_supplier({'type': 'range_list', 'data': [start, end]}, None)
 
     expected = [0.5, 1.5, 2.5, 3.5, 4.5, 5.5]
     actual = [supplier.next(i) for i in range(6)]
@@ -57,29 +57,23 @@ def test_range_lists_float_start_end():
 
 
 def test_rand_range():
-    spec = {
-        "field": {
-            "type": "rand_range",
-            "data": [100.9, 109.9],
-            "config": {"cast": "int"}
-        }
-    }
+    spec = builder.Builder() \
+        .add_field("field", builder.rand_range([100.9, 109.9], cast="int")) \
+        .build()
     supplier = Loader(spec).get('field')
 
     first = supplier.next(0)
     assert str(first).isnumeric()
-    assert 101 <= first <= 110
+    # occasionally gets rounded down to 100
+    assert 100 <= first <= 110
 
 
 def test_nested_range_lists_simple():
-    spec = {
-        "field:range": {
-            "data": [
-                [0, 10],
-                [20, 30]
-            ]
-        }
-    }
+    data = [
+        [0, 10],
+        [20, 30]
+    ]
+    spec = builder.single_field("field:range", data).build()
     supplier = Loader(spec).get('field')
 
     first = supplier.next(0)
@@ -89,14 +83,11 @@ def test_nested_range_lists_simple():
 
 
 def test_nested_range_lists_mixed_types_and_step():
-    spec = {
-        "field:range": {
-            "data": [
-                [0, 10, 2],
-                [20.0, 30.0]
-            ]
-        }
-    }
+    data = [
+        [0, 10, 2],
+        [20.0, 30.0]
+    ]
+    spec = builder.single_field("field:range", data).build()
     supplier = Loader(spec).get('field')
 
     first = supplier.next(0)
@@ -107,14 +98,11 @@ def test_nested_range_lists_mixed_types_and_step():
 
 
 def test_nested_range_lists_mixed_types_and_step_cast():
-    spec = {
-        "field:range?cast=str&precision=2": {
-            "data": [
-                [0.5, 2.5, 0.5],
-                [20.01234, 30.56789]
-            ]
-        }
-    }
+    data = [
+        [0.5, 2.5, 0.5],
+        [20.01234, 30.56789]
+    ]
+    spec = builder.single_field("field:range?cast=str&precision=2", data).build()
     supplier = Loader(spec).get('field')
 
     assert supplier.next(0) == '0.5'
@@ -122,15 +110,15 @@ def test_nested_range_lists_mixed_types_and_step_cast():
 
 
 def test_float_range1():
-    range_list = list(range_handler.float_range(1.0, 5.0, 1.0))
+    range_list = list(range_suppliers.float_range(1.0, 5.0, 1.0))
     assert range_list == [1.0, 2.0, 3.0, 4.0]
 
 
 def test_float_range_with_precision1():
-    range_list = list(range_handler.float_range(1.25, 2.25, 0.25, 2))
+    range_list = list(range_suppliers.float_range(1.25, 2.25, 0.25, 2))
     assert range_list == [1.25, 1.5, 1.75, 2.0]
 
 
 def test_float_range_with_precision2():
-    range_list = list(range_handler.float_range(1.2499999, 2.2499999, 0.25, 2))
+    range_list = list(range_suppliers.float_range(1.2499999, 2.2499999, 0.25, 2))
     assert range_list == [1.25, 1.5, 1.75, 2.0]
diff --git a/tests/test_registry.py b/tests/test_registry.py
index 348b75d..f1066f5 100644
--- a/tests/test_registry.py
+++ b/tests/test_registry.py
@@ -1,6 +1,6 @@
 import os
+import pytest
 import dataspec
-from dataspec import suppliers
 from dataspec.loader import Loader
 import dataspec.types as types
 from dataspec.utils import load_custom_code
@@ -59,3 +59,9 @@ def test_registry_from_file():
     supplier = handler(spec.get('foo'), loader)
 
     assert supplier.next(0) == 'doe a dear'
+
+
+def test_registry_error_case():
+    with pytest.raises(FileNotFoundError):
+        # string_reverser, same as above just different key
+        load_custom_code(f'{test_dir}/custom_does_not_exist.py')
diff --git a/tests/test_schemas.py b/tests/test_schemas.py
index 51a2752..9978430 100644
--- a/tests/test_schemas.py
+++ b/tests/test_schemas.py
@@ -1,7 +1,8 @@
 import pytest
 from dataspec import ResourceError, SpecException
 from dataspec.schemas import load, validate_schema_for_spec
-from dataspec.type_handlers import range_handler
+from dataspec.supplier.core import range_suppliers
+from dataspec.supplier.core import values
 import dataspec.types as types
 
 
@@ -41,3 +42,10 @@ def test_load_and_validate_invalid_schema():
     range_schema = types.lookup_schema('range')
     with pytest.raises(SpecException):
         validate_schema_for_spec('range', {'type': 'range'}, range_schema)
+
+
+def test_invalid_count_param_values_spec():
+    values_schema = types.lookup_schema('values')
+    with pytest.raises(SpecException):
+        spec = {'type': 'values', 'data': [1, 2, 3], 'config': {'count': {}}}
+        validate_schema_for_spec('values', spec, values_schema)
diff --git a/dataspec/schema/test_schemas.py b/tests/test_schemas_data.py
similarity index 91%
rename from dataspec/schema/test_schemas.py
rename to tests/test_schemas_data.py
index 0d4fbac..75c5cfb 100644
--- a/dataspec/schema/test_schemas.py
+++ b/tests/test_schemas_data.py
@@ -14,8 +14,9 @@
 INSTANCE = "instance"
 MESSAGE = "note"
 
-schema_dir = os.path.dirname(os.path.realpath(__file__))
-tests_dir = f'{schema_dir}/tests'
+root_dir = os.path.dirname(os.path.realpath(__file__))
+schema_dir = os.path.realpath(os.sep.join([root_dir, '..', 'dataspec', 'schema']))
+tests_dir = os.sep.join([root_dir, 'data'])
 
 
 def test_values_schema():
@@ -66,6 +67,10 @@ def test_ip_schema():
     _test_run_validation("ip.tests.json")
 
 
+def test_select_list_subset():
+    _test_run_validation("select_list_subset.tests.json")
+
+
 def _test_run_validation(test_file_name):
     definitions = load_schema_file('definitions.json')
     tests = load_test_file(test_file_name)
@@ -130,5 +135,5 @@ def load_schema_file(file_name):
 
 
 def _load_file(root_dir, file_name):
-    with open(f'{root_dir}/{file_name}', 'r') as handle:
+    with open(os.sep.join([root_dir, file_name]), 'r') as handle:
         return json.load(handle)
diff --git a/tests/test_select_list.py b/tests/test_select_list.py
index b33c92a..0baa80a 100644
--- a/tests/test_select_list.py
+++ b/tests/test_select_list.py
@@ -1,8 +1,8 @@
 import pytest
 from dataspec.loader import Loader
-from dataspec import SpecException, suppliers
+from dataspec import builder, suppliers, SpecException
 # to engage registration
-from dataspec.type_handlers import select_list_subset
+from dataspec.supplier.core import select_list_subset
 
 
 def test_invalid_when_no_config():
@@ -14,21 +14,13 @@ def test_invalid_when_no_mean_specified():
 
 
 def test_invalid_when_ref_not_defined():
-    spec = {
-        "field:select_list_subset?mean=2": {
-            "ref": "REF"
-        }
-    }
+    spec = builder.single_field("field:select_list_subset?mean=2", {"ref": "REF"}).build()
     _test_invalid_select_list_spec(spec)
 
 
 def test_invalid_when_ref_and_data_specified():
-    spec = {
-        "field:select_list_subset?mean=2": {
-            "ref": "REF",
-            "data": ["one", "two", "three", "four"]
-        }
-    }
+    spec = builder.single_field("field?mean=2",
+                                builder.select_list_subset(data=["one", "two", "three", "four"], ref="REF")).build()
     _test_invalid_select_list_spec(spec)
 
 
@@ -76,3 +68,14 @@ def test_select_list_using_loader():
     supplier = loader.get('pets')
     value = supplier.next(0)
     assert len(value) == 7
+
+
+def test_select_list_ref_contains_data():
+    spec_builder = builder.spec_builder()
+    spec_builder.select_list_subset('pets', data=None, ref='pets_list', count=2)
+    spec_builder.refs().values(key='pets_list', data=['goat', 'sheep', 'bear', 'cow', 'dragon'])
+    loader = Loader(spec_builder.build())
+    supplier = loader.get('pets')
+    value = supplier.next(0)
+    assert isinstance(value, list)
+    assert len(value) == 2
diff --git a/tests/test_template_engine.py b/tests/test_template_engine.py
index 6de08b9..63a4eb9 100644
--- a/tests/test_template_engine.py
+++ b/tests/test_template_engine.py
@@ -1,10 +1,11 @@
 import os
+from pathlib import Path
 import dataspec.template_engines as engines
 
 
 def test_basic_template():
-    dir_path = os.path.dirname(os.path.realpath(__file__))
-    engine = engines.load(dir_path + '/data/template.jinja')
+    dir_path = Path(os.path.dirname(os.path.realpath(__file__)))
+    engine = engines.for_file(dir_path / 'data' / 'template.jinja')
 
     rendered = engine.process({'A': 1, 'B': 2, 'C': 3, '_internal': {'_iteration': 1}})
 
diff --git a/tests/test_unicode_range.py b/tests/test_unicode_range.py
index c0d98d1..affa0c6 100644
--- a/tests/test_unicode_range.py
+++ b/tests/test_unicode_range.py
@@ -1,31 +1,26 @@
 import string
 import pytest
-from dataspec import Loader, SpecException
-from dataspec.type_handlers import unicode_range
+from dataspec import builder, Loader, SpecException
+from dataspec.supplier.core import unicode_range
 
 
 def test_unicode_no_data_element():
-    spec = {"field": {"type": "unicode_range"}}
+    spec = builder.single_field("field", builder.unicode_range(data=None)).build()
+    spec['field'].pop('data')
+
     with pytest.raises(SpecException):
         Loader(spec).get("field")
 
 
 def test_unicode_data_is_not_list():
-    spec = {"field": {"type": "unicode_range", "data": "0x3040,0x309f"}}
+    spec = builder.single_field("field", builder.unicode_range(data="0x3040,0x309f")).build()
     with pytest.raises(SpecException):
         Loader(spec).get("field")
 
 
 def test_unicode_range_single_range_as_hex():
-    spec = {
-        "text": {
-            "type": "unicode_range",
-            "data": [0x3040, 0x309f],
-            "config": {
-                "count": 5
-            }
-        }
-    }
+    field_spec = builder.unicode_range(data=[0x3040, 0x309f], count=5)
+    spec = builder.single_field("text", field_spec).build()
     supplier = Loader(spec).get('text')
     first = supplier.next(0)
     for c in first:
@@ -33,18 +28,8 @@ def test_unicode_range_single_range_as_hex():
 
 
 def test_unicode_range_single_range_as_hex_strings():
-    spec = {
-        "text": {
-            "type": "unicode_range",
-            "data": ['0x3040', '0x309f'],
-            "config": {
-                "mean": 5,
-                "stddev": 2,
-                "min": 2,
-                "max": 7
-            }
-        }
-    }
+    field_spec = builder.unicode_range(data=[0x3040, 0x309f], mean=5, stddev=2, min=2, max=7)
+    spec = builder.single_field("text", field_spec).build()
     supplier = Loader(spec).get('text')
     first = supplier.next(0)
     assert 2 <= len(first) <= 7
@@ -53,19 +38,13 @@ def test_unicode_range_single_range_as_hex_strings():
 
 
 def test_unicode_multiple_ranges():
-    spec = {
-        "text": {
-            "type": "unicode_range",
-            "data": [
-                ['0x0590', '0x05ff'],
-                ['0x3040', '0x309f']
-            ],
-            "config": {
-                "min": 3,
-                "max": 7
-            }
-        }
-    }
+    data = [
+        ['0x0590', '0x05ff'],
+        ['0x3040', '0x309f']
+    ]
+    field_spec = builder.unicode_range(data=data, min=3, max=7)
+    spec = builder.single_field("text", field_spec).build()
+
     supplier = Loader(spec).get('text')
     first = supplier.next(0)
     assert 3 <= len(first) <= 7
diff --git a/tests/test_uuid.py b/tests/test_uuid.py
index 2387055..2a3ed88 100644
--- a/tests/test_uuid.py
+++ b/tests/test_uuid.py
@@ -1,15 +1,14 @@
 import re
-from dataspec.loader import Loader
+from dataspec import builder, Loader
 # need this to trigger registration
-from dataspec.type_handlers import uuid_handler
+from dataspec.supplier.core import uuid_handler
 
 
 UUID_REGEX = re.compile('[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}', re.I)
 
 
 def test_uuid_spec():
-    spec = {"foo": {"type": "uuid"}}
-
+    spec = builder.single_field("foo:uuid", {}).build()
     loader = Loader(spec)
     supplier = loader.get('foo')
 
diff --git a/tests/test_values.py b/tests/test_values.py
index 30b67ed..e32ac64 100644
--- a/tests/test_values.py
+++ b/tests/test_values.py
@@ -2,6 +2,7 @@
 import dataspec.suppliers as suppliers
 from dataspec.loader import preprocess_spec
 from dataspec.exceptions import SpecException
+import dataspec.builder as builder
 from collections import Counter
 import pytest
 
@@ -30,9 +31,18 @@ def test_weighted_values():
     assert 'bar' in most_common_keys
 
 
+def test_weighted_values_non_zero_count():
+    spec = builder.values({'foo': 0.5, 'bar': 0.4, 'baz': 0.1}, count=2)
+    supplier = suppliers.values(spec)
+
+    data = supplier.next(0)
+    assert isinstance(data, list)
+    assert len(data) == 2
+
+
 def test_shortcut_notation():
     # not type or data key, just what would have been the value for the data key
-    spec = {'foo': 0.5, 'bar': 0.4, 'baz': 0.1}
+    spec = builder.values({'foo': 0.5, 'bar': 0.4, 'baz': 0.1})
     most_common_keys = _get_most_common_keys(spec, 100, 2)
 
     assert 'foo' in most_common_keys
@@ -49,7 +59,7 @@ def _get_most_common_keys(spec, iterations, num_keys_to_collect):
 
 def test_sampling_mode_invalid_for_weighted_values():
     # sampling is only valid for list based suppliers
-    spec = {'foo?sample=True': {10: 0.5, 20: 0.3, 30: 0.2}}
+    spec = builder.single_field('foo?sample=True', {10: 0.5, 20: 0.3, 30: 0.2}).build()
     _test_invalid_spec(spec, 'foo')
 
 
@@ -78,24 +88,16 @@ def test_count_param_valid():
 
 def test_configref_for_values():
     """ verifies that the values ref inherits the config from the configref """
-    spec = {
-        "name?configref=quoteit": ["bob", "joe", "ann", "sue"],
-        "refs": {
-            "quoteit": {
-                "type": "configref",
-                "config": {
-                    "quote": "\""
-                }
-            }
-        }
-    }
+    spec = builder.single_field("name?configref=quoteit", ["bob", "joe", "ann", "sue"]) \
+        .add_ref("quoteit", builder.configref(quote="\"")) \
+        .build()
     supplier = Loader(spec).get('name')
     assert supplier.next(0) == '"bob"'
 
 
 def test_values_list_order():
     data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
-    spec = {'field': data}
+    spec = builder.single_field('field', data).build()
     supplier = Loader(spec).get('field')
 
     values = [supplier.next(i) for i in range(10)]
@@ -103,14 +105,8 @@ def test_values_list_order():
 
 
 def test_values_count_as_list():
-    spec = {
-        'field': {
-            'data': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
-            'config': {
-                'count': [2, 3]
-            }
-        }
-    }
+    data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
+    spec = builder.single_field('field', builder.values(data, count=[2, 3])).build()
     supplier = Loader(spec).get('field')
 
     first = supplier.next(0)
diff --git a/tests/test_weighted_ref.py b/tests/test_weighted_ref.py
index 8966f4e..ef7a7e9 100644
--- a/tests/test_weighted_ref.py
+++ b/tests/test_weighted_ref.py
@@ -1,55 +1,44 @@
-import dataspec
+from dataspec import builder, suppliers, Loader, SpecException
+from dataspec.supplier.core.weighted_refs import WeightedRefsSupplier
 import pytest
 
 
 def test_weighted_ref_missing_key():
-    spec = {
-        'field': {
-            'type': 'weightedref',
-            'data': {
-                'foo': 0.5,
-                'bar': 0.4,
-                'baz': 0.1,
-            }
-        },
-        'refs': {
-            'foo': ['foo'],
-            'bar': 'bar',
-            'baz': {'baz': 0.999}
-        }
+    ref_weights = {
+        'foo': 0.5,
+        'bar': 0.4,
+        'baz': 0.1,
     }
+    spec = builder.single_field('field', builder.weightedref(ref_weights)) \
+        .add_ref('foo', ['foo']) \
+        .add_ref('bar', 'bar') \
+        .add_ref('baz', {'baz': 0.999}) \
+        .build()
 
-    key_supplier = dataspec.suppliers.values(['foo', 'bar', 'baz', 'notvalid'])
-    values_map = {key: dataspec.suppliers.values(value) for key, value in spec['refs'].items()}
-    supplier = dataspec.suppliers.weighted_ref(key_supplier, values_map)
+    key_supplier = suppliers.values(['foo', 'bar', 'baz', 'notvalid'])
+    values_map = {key: suppliers.values(value) for key, value in spec['refs'].items()}
+    supplier = WeightedRefsSupplier(key_supplier, values_map)
 
-    with pytest.raises(dataspec.SpecException) as exception:
+    with pytest.raises(SpecException) as exception:
         [supplier.next(i) for i in range(0, 10)]
     assert "Unknown Key 'notvalid' for Weighted Reference" in str(exception.value)
 
 
 def test_weighed_ref_count_as_list():
-    spec = {
-        'field': {
-            'type': 'weightedref',
-            'config': {'count': 3},
-            'data': {
-                'one': 0.5,
-                'two': 0.4,
-                'tre': 0.1,
-            }
-        },
-        'refs': {
-            'one': 'uno',
-            'two': 'dos',
-            'tre': 'tres'
-        }
+    ref_weights = {
+        'one': 0.5,
+        'two': 0.4,
+        'tre': 0.1,
     }
+    spec = builder.single_field('field', builder.weightedref(ref_weights, count=3)) \
+        .add_ref('one', 'uno') \
+        .add_ref('two', 'dos') \
+        .add_ref('tre', 'tres') \
+        .build()
 
-    loader = dataspec.Loader(spec)
+    loader = Loader(spec)
     supplier = loader.get('field')
     first = supplier.next(0)
 
     assert isinstance(first, list)
     assert len(first) == 3
-
diff --git a/tests/test_yaml_specs.py b/tests/test_yaml_specs.py
index aadec8f..861d8d6 100644
--- a/tests/test_yaml_specs.py
+++ b/tests/test_yaml_specs.py
@@ -3,7 +3,7 @@
 
 from dataspec.loader import Loader
 # to trigger registration
-from dataspec.type_handlers import combine
+from dataspec.supplier.core import combine
 
 
 yaml_spec = '''