diff --git a/.circleci/config.yml b/.circleci/config.yml
index fa1fa12..68e7d61 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -18,6 +18,7 @@ jobs:
           shell: /bin/bash -leo pipefail
           environment:
             TOX_NUM_CORES: 2
+            TOX_PARALLEL_NO_SPINNER: 1
             TOX_SHOW_OUTPUT: "True"
           command: |
             tox -p $TOX_NUM_CORES
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b2e117f..13c48db 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
   conformers (#65)
 - Added `s.merge` to allow seamless merging of mapping Specs (#70)
 - Added `ErrorDetails.as_map` to convert `ErrorDetails` instances to simple dicts (#79)
+- Added `s.kv` to validate and conform generic key/value mapping types (#71)
 
 ### Changed
 - **Breaking** `Spec.with_conformer` will now replace the default conformer applied
@@ -29,6 +30,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 - Fixed a bug where `s(None)` is not a valid alias for `s(type(None))` (#61)
 - Fixed a bug where it was possible to define duplicate keys in mapping Specs with
   `s.opt(k)` (#74)
+- Fixed a bug where string Spec factory error message for values which do not match
+  a regex incorrectly indicates that the string _does_ match the regex (#77)
 
 ## [v0.2.5] - 2020-04-10
 ### Added
diff --git a/docs/usage.rst b/docs/usage.rst
index 18dcb2c..3859f55 100644
--- a/docs/usage.rst
+++ b/docs/usage.rst
@@ -456,6 +456,30 @@ input Spec.
    Spec types, you should wrap the mapping Specs with ``s.merge`` and pass that to
    ``s.all``.
 
+.. _key_value_specs:
+
+Key/Value Specs
+^^^^^^^^^^^^^^^
+
+Mapping Specs are useful for heterogeneous associative data structures for which the
+keys are known *a priori*. However, you may often wish to validate a homogeneous
+mapping with unknown keys. For such cases, you can turn to
+:py:meth:`dataspec.SpecAPI.kv`.
+
+.. code-block:: python
+
+   spec = s.kv(s.str(regex=r"[A-Z]{2}"), s.str(regex=r"[A-Z][\w ]+"))
+   spec.is_valid({"GA": "Georgia", "NM": "New Mexico"})  # True
+   spec.is_valid({"ga": "Georgia", "NM": "New Mexico"})  # False
+   spec.is_valid({"ga": "Georgia", "NM": "new mexico"})  # False
+
+.. note::
+
+   By default :py:meth:`s.kv <dataspec.SpecAPI.kv>` will not conform keys on input
+   values, to avoid potential creating potentially duplicate keys from the key
+   conformer. You can override this behavior with the ``conform_keys`` keyword
+   argument.
+
 .. _tuple_specs:
 
 Tuple Specs
diff --git a/src/dataspec/api.py b/src/dataspec/api.py
index 9683eed..6331126 100644
--- a/src/dataspec/api.py
+++ b/src/dataspec/api.py
@@ -9,6 +9,7 @@ from dataspec.base import (
     ValidationError,
     all_spec,
     any_spec,
+    kv_spec,
     make_spec,
     merge_spec,
 )
@@ -115,6 +116,7 @@ class SpecAPI:
     email = staticmethod(email_spec)
     every = staticmethod(every_spec)
     inst = staticmethod(datetime_spec)
+    kv = staticmethod(kv_spec)
     merge = staticmethod(merge_spec)
     nilable = staticmethod(nilable_spec)
     num = staticmethod(num_spec)
diff --git a/src/dataspec/base.py b/src/dataspec/base.py
index 9653ae0..a9e7fb4 100644
--- a/src/dataspec/base.py
+++ b/src/dataspec/base.py
@@ -65,6 +65,7 @@ SpecPredicate = Union[  # type: ignore
     List["SpecPredicate"],  # type: ignore
     FrozenSet[Any],
     Set[Any],
+    Type[Any],
     PredicateFn,
     ValidatorFn,
     "Spec",
@@ -650,7 +651,7 @@ class DictSpec(Spec):
                         via=[self.tag],
                         path=[k],
                     )
-        except TypeError:
+        except (AttributeError, TypeError):
             yield ErrorDetails(
                 message="Value is not a mapping type",
                 pred=self,
@@ -688,6 +689,83 @@ class DictSpec(Spec):
         )
 
 
+def kv_spec(
+    tag_or_pred: Union[Tag, SpecPredicate],
+    *preds: SpecPredicate,
+    conform_keys: bool = False,
+    conformer: Optional[Conformer] = None,
+) -> Spec:
+    """
+    Return a Spec that validates mapping types against a single Spec for all keys
+    and a single Spec for all values.
+
+    If ``conform_keys`` is specified as :py:obj:`True`, the default conformer will
+    conform keys and values. By default, ``conform_keys`` is :py:obj:`False` to avoid
+    duplicate names produced during the conformation.
+
+    The returned Spec's :py:meth:`dataspec.Spec.conform` method will return a
+    :py:class:`dict` with values conformed by the corresponding input Spec. If a
+    ``conformer`` is provided via keyword argument, that conformer will be provided a
+    :py:class:`dict` with the conformed :py:class:`dict` as described above. Otherwise,
+    the default conformer will simply return the conformed :py:class:`dict` . Note that
+    the default conformer does **not** modify the input mapping in place.
+
+    Exactly two Specs must be provided or a :py:class:`ValueError` will be raised
+    during construction.
+
+    :param tag_or_pred: an optional tag for the resulting spec or the key Spec
+        or value which can be converted into a Spec
+    :param preds: if a tag is given, preds should be exactly two Specs or values
+        which can be converted into Specs; the first shall be the Spec for the
+        keys and the second shall be the Spec for values
+    :param conform_keys: if :py:obj:`True`, the default conformer will also conform
+        keys according to the input key Spec; default is :py:obj:`False`
+    :param conformer: an optional conformer which will be composed with the
+        default conformer
+    :return: a Spec
+    """
+    tag, preds = tag_maybe(tag_or_pred, *preds)
+
+    if len(preds) != 2:
+        raise ValueError("Must specify a key and value Spec for k/v Specs")
+
+    tag = tag or "kv"
+    keyspec = make_spec(preds[0])
+    valspec = make_spec(preds[1])
+
+    def _kv_valid(d) -> Iterator[ErrorDetails]:
+        assert tag is not None
+
+        try:
+            for k, v in d.items():
+                yield from _enrich_errors(keyspec.validate(k), tag, d)
+                yield from _enrich_errors(valspec.validate(v), tag, k)
+        except (AttributeError, TypeError):
+            yield ErrorDetails(
+                message="Value is not a mapping type",
+                pred=_kv_valid,
+                value=d,
+                via=[tag],
+            )
+            return
+
+    if conform_keys:
+
+        def conform_mapping(d: Mapping) -> Mapping:
+            return {keyspec.conform(k): valspec.conform(v) for k, v in d.items()}
+
+    else:
+
+        def conform_mapping(d: Mapping) -> Mapping:
+            return {k: valspec.conform(v) for k, v in d.items()}
+
+    return ValidatorSpec(
+        tag,
+        _kv_valid,
+        conformer=compose_conformers(conform_mapping, *filter(None, (conformer,))),
+    )
+
+
 @attr.s(auto_attribs=True, frozen=True, slots=True)
 class ObjectSpec(Spec):
     tag: Tag
@@ -1210,7 +1288,8 @@ def make_spec(  # pylint: disable=inconsistent-return-statements  # noqa: MC0001
     Specs may be created for enumerated types using a Python ``set`` or ``frozenset``
     or using Python :py:class:`enum.Enum` types. Specs created for enumerated types
     based on :py:class:`enum.Enum` values validate the Enum name, value, or Enum
-    singleton and conform the the Enum singleton.
+    singleton and conform the input value to the corresponding :py:class:`enum.Enum`
+    value.
 
     Specs may be created for homogeneous collections using a Python ``list`` type.
     Callers can specify a few additional parameters for collection specs by providing
diff --git a/src/dataspec/factories.py b/src/dataspec/factories.py
index 0597c87..092dbb4 100644
--- a/src/dataspec/factories.py
+++ b/src/dataspec/factories.py
@@ -1298,7 +1298,9 @@ def str_spec(  # noqa: MC0001  # pylint: disable=too-many-arguments
         _pattern = regex if isinstance(regex, Pattern) else re.compile(regex)
 
         @pred_to_validator(
-            "String '{value}' does match regex '{regex}'", complement=True, regex=regex
+            "String '{value}' does not match regex '{regex}'",
+            complement=True,
+            regex=regex,
         )
         def str_matches_regex(s: str) -> bool:
             return bool(_pattern.fullmatch(s))

Add functionality for validating generic map Specs
Normal mapping Specs involve defining specific keys with Specs for each, but it may be useful occasionally to validate dictionaries with generic key/value Specs similar to collection Specs:

```python
s.map("map_spec", str, s.str(regex="\d+"))
```
diff --git a/tests/test_base.py b/tests/test_base.py
index d1d9d34..4b6510f 100644
--- a/tests/test_base.py
+++ b/tests/test_base.py
@@ -419,6 +419,117 @@ class TestDictSpecConformation:
         assert None is conformed.get("date_of_birth")
 
 
+class TestKVSpecValidation:
+    @pytest.mark.parametrize("args", [("tag",), ("tag", str), (str,),])
+    def test_kv_spec_definition(self, args):
+        with pytest.raises(ValueError):
+            s.kv(*args)
+
+    @pytest.fixture
+    def kv_spec(self) -> Spec:
+        return s.kv(s.str(regex=r"[A-Za-z\-]+"), s({"email": s.email()}),)
+
+    @pytest.mark.parametrize(
+        "d",
+        [
+            {"home-email": {"email": "chris@home.net"},},
+            {
+                "home-email": {"email": "chris@home.net"},
+                "work-email": {"email": "chris@work.net"},
+            },
+            {"other": {"email": "secret@mailinator.net", "subscribe": False}},
+        ],
+    )
+    def test_kv_spec(self, kv_spec: Spec, d):
+        assert kv_spec.is_valid(d)
+
+    @pytest.mark.parametrize(
+        "d",
+        [
+            None,
+            "a string",
+            0,
+            3.14,
+            True,
+            False,
+            {"a", "b", "c"},
+            ["a", "b", "c"],
+            ("a", "b", "c"),
+            {"home_email": {"email": "chris@home.net"}},
+            {"home-email": {"email-address": "chris@home.net"}},
+            {"home-email": {}},
+        ],
+    )
+    def test_kv_spec_failure(self, kv_spec: Spec, d):
+        assert not kv_spec.is_valid(d)
+        assert list(kv_spec.validate(d))
+
+
+class TestKVSpecConformation:
+    @pytest.fixture
+    def kv_spec(self) -> Spec:
+        return s.kv(
+            s.str(regex=r"[A-Za-z\-]+", conformer=lambda s: s.replace("-", "")),
+            s({"email": s.email()}, conformer=lambda m: m["email"]),
+        )
+
+    @pytest.fixture
+    def kv_spec_conform_keys(self) -> Spec:
+        return s.kv(
+            s.str(regex=r"[A-Za-z\-]+", conformer=lambda s: s.replace("-", "")),
+            s({"email": s.email()}, conformer=lambda m: m["email"]),
+            conform_keys=True,
+        )
+
+    @pytest.mark.parametrize(
+        "orig,conformed",
+        [
+            (
+                {"home-email": {"email": "chris@home.net"},},
+                {"home-email": "chris@home.net"},
+            ),
+            (
+                {
+                    "home-email": {"email": "chris@home.net"},
+                    "work-email": {"email": "chris@work.net"},
+                },
+                {"home-email": "chris@home.net", "work-email": "chris@work.net"},
+            ),
+            (
+                {"other": {"email": "secret@mailinator.net", "subscribe": False}},
+                {"other": "secret@mailinator.net"},
+            ),
+        ],
+    )
+    def test_kv_spec_conformation(self, kv_spec: Spec, orig, conformed):
+        assert conformed == kv_spec.conform(orig)
+
+    @pytest.mark.parametrize(
+        "orig,conformed",
+        [
+            (
+                {"home-email": {"email": "chris@home.net"},},
+                {"homeemail": "chris@home.net"},
+            ),
+            (
+                {
+                    "home-email": {"email": "chris@home.net"},
+                    "work-email": {"email": "chris@work.net"},
+                },
+                {"homeemail": "chris@home.net", "workemail": "chris@work.net"},
+            ),
+            (
+                {"other": {"email": "secret@mailinator.net", "subscribe": False}},
+                {"other": "secret@mailinator.net"},
+            ),
+        ],
+    )
+    def test_kv_spec_conform_keys_conformation(
+        self, kv_spec_conform_keys: Spec, orig, conformed
+    ):
+        assert conformed == kv_spec_conform_keys.conform(orig)
+
+
 class TestObjectSpecValidation:
     @pytest.mark.parametrize(
         "pred", [{"id": str, s.opt("id"): int}, {s.opt("id"): int, "id": str},]

pip install -e .[dev]
parse_log_pytest
requirements.txt
pytest
3.9
requirements.txt requirements.dev.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
alabaster==0.7.16
attrs==25.3.0
babel==2.17.0
backports.tarfile==1.2.0
black==25.1.0
cachetools==5.5.2
certifi==2025.1.31
cffi==1.17.1
chardet==5.2.0
charset-normalizer==3.4.1
click==8.1.8
colorama==0.4.6
cryptography==44.0.2
-e git+https://github.com/coverahealth/dataspec.git@3b4da00d792de4ce5b1ab361e31f1a8a408f3391#egg=dataspec
distlib==0.3.9
docutils==0.21.2
exceptiongroup==1.2.2
filelock==3.18.0
id==1.5.0
idna==3.10
imagesize==1.4.1
importlib_metadata==8.6.1
iniconfig==2.1.0
isort==6.0.1
jaraco.classes==3.4.0
jaraco.context==6.0.1
jaraco.functools==4.1.0
jeepney==0.9.0
Jinja2==3.1.6
keyring==25.6.0
markdown-it-py==3.0.0
MarkupSafe==3.0.2
mdurl==0.1.2
more-itertools==10.6.0
mypy-extensions==1.0.0
nh3==0.2.21
packaging==24.2
pathspec==0.12.1
phonenumbers==9.0.2
platformdirs==4.3.7
pluggy==1.5.0
pycparser==2.22
Pygments==2.19.1
pyproject-api==1.9.0
pytest==8.3.5
python-dateutil==2.9.0.post0
readme_renderer==44.0
requests==2.32.3
requests-toolbelt==1.0.0
rfc3986==2.0.0
rich==14.0.0
SecretStorage==3.3.3
six==1.17.0
snowballstemmer==2.2.0
Sphinx==7.4.7
sphinx-rtd-theme==3.0.2
sphinxcontrib-applehelp==2.0.0
sphinxcontrib-devhelp==2.0.0
sphinxcontrib-htmlhelp==2.1.0
sphinxcontrib-jquery==4.1
sphinxcontrib-jsmath==1.0.1
sphinxcontrib-qthelp==2.0.0
sphinxcontrib-serializinghtml==2.0.0
tomli==2.2.1
tox==4.25.0
twine==6.1.0
typing_extensions==4.13.0
urllib3==2.3.0
virtualenv==20.29.3
zipp==3.21.0

name: dataspec
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=3.0.16=h5eee18b_0
  - pip=25.0=py39h06a4308_0
  - python=3.9.21=he870216_1
  - readline=8.2=h5eee18b_0
  - setuptools=75.8.0=py39h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - tzdata=2025a=h04d1e81_0
  - wheel=0.45.1=py39h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - alabaster==0.7.16
      - attrs==25.3.0
      - babel==2.17.0
      - backports-tarfile==1.2.0
      - black==25.1.0
      - cachetools==5.5.2
      - certifi==2025.1.31
      - cffi==1.17.1
      - chardet==5.2.0
      - charset-normalizer==3.4.1
      - click==8.1.8
      - colorama==0.4.6
      - cryptography==44.0.2
      - dataspec==0.3.dev0
      - distlib==0.3.9
      - docutils==0.21.2
      - exceptiongroup==1.2.2
      - filelock==3.18.0
      - id==1.5.0
      - idna==3.10
      - imagesize==1.4.1
      - importlib-metadata==8.6.1
      - iniconfig==2.1.0
      - isort==6.0.1
      - jaraco-classes==3.4.0
      - jaraco-context==6.0.1
      - jaraco-functools==4.1.0
      - jeepney==0.9.0
      - jinja2==3.1.6
      - keyring==25.6.0
      - markdown-it-py==3.0.0
      - markupsafe==3.0.2
      - mdurl==0.1.2
      - more-itertools==10.6.0
      - mypy-extensions==1.0.0
      - nh3==0.2.21
      - packaging==24.2
      - pathspec==0.12.1
      - phonenumbers==9.0.2
      - platformdirs==4.3.7
      - pluggy==1.5.0
      - pycparser==2.22
      - pygments==2.19.1
      - pyproject-api==1.9.0
      - pytest==8.3.5
      - python-dateutil==2.9.0.post0
      - readme-renderer==44.0
      - requests==2.32.3
      - requests-toolbelt==1.0.0
      - rfc3986==2.0.0
      - rich==14.0.0
      - secretstorage==3.3.3
      - six==1.17.0
      - snowballstemmer==2.2.0
      - sphinx==7.4.7
      - sphinx-rtd-theme==3.0.2
      - sphinxcontrib-applehelp==2.0.0
      - sphinxcontrib-devhelp==2.0.0
      - sphinxcontrib-htmlhelp==2.1.0
      - sphinxcontrib-jquery==4.1
      - sphinxcontrib-jsmath==1.0.1
      - sphinxcontrib-qthelp==2.0.0
      - sphinxcontrib-serializinghtml==2.0.0
      - tomli==2.2.1
      - tox==4.25.0
      - twine==6.1.0
      - typing-extensions==4.13.0
      - urllib3==2.3.0
      - virtualenv==20.29.3
      - zipp==3.21.0
prefix: /opt/conda/envs/dataspec

tests/test_base.py::TestKVSpecValidation::test_kv_spec_definition[args0] tests/test_base.py::TestKVSpecValidation::test_kv_spec_definition[args1] tests/test_base.py::TestKVSpecValidation::test_kv_spec_definition[args2] tests/test_base.py::TestKVSpecValidation::test_kv_spec[d0] tests/test_base.py::TestKVSpecValidation::test_kv_spec[d1] tests/test_base.py::TestKVSpecValidation::test_kv_spec[d2] tests/test_base.py::TestKVSpecValidation::test_kv_spec_failure[None] tests/test_base.py::TestKVSpecValidation::test_kv_spec_failure[a tests/test_base.py::TestKVSpecValidation::test_kv_spec_failure[0] tests/test_base.py::TestKVSpecValidation::test_kv_spec_failure[3.14] tests/test_base.py::TestKVSpecValidation::test_kv_spec_failure[True] tests/test_base.py::TestKVSpecValidation::test_kv_spec_failure[False] tests/test_base.py::TestKVSpecValidation::test_kv_spec_failure[d6] tests/test_base.py::TestKVSpecValidation::test_kv_spec_failure[d7] tests/test_base.py::TestKVSpecValidation::test_kv_spec_failure[d8] tests/test_base.py::TestKVSpecValidation::test_kv_spec_failure[d9] tests/test_base.py::TestKVSpecValidation::test_kv_spec_failure[d10] tests/test_base.py::TestKVSpecValidation::test_kv_spec_failure[d11] tests/test_base.py::TestKVSpecConformation::test_kv_spec_conformation[orig0-conformed0] tests/test_base.py::TestKVSpecConformation::test_kv_spec_conformation[orig1-conformed1] tests/test_base.py::TestKVSpecConformation::test_kv_spec_conformation[orig2-conformed2] tests/test_base.py::TestKVSpecConformation::test_kv_spec_conform_keys_conformation[orig0-conformed0] tests/test_base.py::TestKVSpecConformation::test_kv_spec_conform_keys_conformation[orig1-conformed1] tests/test_base.py::TestKVSpecConformation::test_kv_spec_conform_keys_conformation[orig2-conformed2]
tests/test_base.py::TestCollSpecValidation::test_error_details[v0-path0] tests/test_base.py::TestCollSpecValidation::test_error_details[v1-path1] tests/test_base.py::TestCollSpecValidation::test_error_details[v2-path2] tests/test_base.py::TestCollSpecValidation::test_coll_spec_kwargs[kwargs0] tests/test_base.py::TestCollSpecValidation::test_coll_spec_kwargs[kwargs1] tests/test_base.py::TestCollSpecValidation::test_coll_spec_kwargs[kwargs2] tests/test_base.py::TestCollSpecValidation::TestAllowStr::test_allow_str_validation tests/test_base.py::TestCollSpecValidation::TestAllowStr::test_kind_validation_failure tests/test_base.py::TestCollSpecValidation::TestMinlengthValidation::test_min_minlength[-1] tests/test_base.py::TestCollSpecValidation::TestMinlengthValidation::test_min_minlength[-100] tests/test_base.py::TestCollSpecValidation::TestMinlengthValidation::test_int_minlength[-0.5] tests/test_base.py::TestCollSpecValidation::TestMinlengthValidation::test_int_minlength[0.5] tests/test_base.py::TestCollSpecValidation::TestMinlengthValidation::test_int_minlength[2.71] tests/test_base.py::TestCollSpecValidation::TestMinlengthValidation::test_minlength_spec[coll0] tests/test_base.py::TestCollSpecValidation::TestMinlengthValidation::test_minlength_spec[coll1] tests/test_base.py::TestCollSpecValidation::TestMinlengthValidation::test_minlength_spec_failure[coll0] tests/test_base.py::TestCollSpecValidation::TestMinlengthValidation::test_minlength_spec_failure[coll1] tests/test_base.py::TestCollSpecValidation::TestMinlengthValidation::test_minlength_spec_failure[coll2] tests/test_base.py::TestCollSpecValidation::TestMinlengthValidation::test_minlength_spec_failure[coll3] tests/test_base.py::TestCollSpecValidation::TestMaxlengthValidation::test_min_maxlength[-1] tests/test_base.py::TestCollSpecValidation::TestMaxlengthValidation::test_min_maxlength[-100] tests/test_base.py::TestCollSpecValidation::TestMaxlengthValidation::test_int_maxlength[-0.5] tests/test_base.py::TestCollSpecValidation::TestMaxlengthValidation::test_int_maxlength[0.5] tests/test_base.py::TestCollSpecValidation::TestMaxlengthValidation::test_int_maxlength[2.71] tests/test_base.py::TestCollSpecValidation::TestMaxlengthValidation::test_maxlength_spec[coll0] tests/test_base.py::TestCollSpecValidation::TestMaxlengthValidation::test_maxlength_spec[coll1] tests/test_base.py::TestCollSpecValidation::TestMaxlengthValidation::test_maxlength_spec[coll2] tests/test_base.py::TestCollSpecValidation::TestMaxlengthValidation::test_maxlength_spec_failure[coll0] tests/test_base.py::TestCollSpecValidation::TestMaxlengthValidation::test_maxlength_spec_failure[coll1] tests/test_base.py::TestCollSpecValidation::TestMaxlengthValidation::test_maxlength_spec_failure[coll2] tests/test_base.py::TestCollSpecValidation::TestCountValidation::test_min_count[-1] tests/test_base.py::TestCollSpecValidation::TestCountValidation::test_min_count[-100] tests/test_base.py::TestCollSpecValidation::TestCountValidation::test_int_count[-0.5] tests/test_base.py::TestCollSpecValidation::TestCountValidation::test_int_count[0.5] tests/test_base.py::TestCollSpecValidation::TestCountValidation::test_int_count[2.71] tests/test_base.py::TestCollSpecValidation::TestCountValidation::test_maxlength_spec[coll0] tests/test_base.py::TestCollSpecValidation::TestCountValidation::test_count_spec_failure[coll0] tests/test_base.py::TestCollSpecValidation::TestCountValidation::test_count_spec_failure[coll1] tests/test_base.py::TestCollSpecValidation::TestCountValidation::test_count_spec_failure[coll2] tests/test_base.py::TestCollSpecValidation::TestCountValidation::test_count_spec_failure[coll3] tests/test_base.py::TestCollSpecValidation::TestCountValidation::test_count_spec_failure[coll4] tests/test_base.py::TestCollSpecValidation::TestCountValidation::test_count_and_minlength_or_maxlength_agreement[opts0] tests/test_base.py::TestCollSpecValidation::TestCountValidation::test_count_and_minlength_or_maxlength_agreement[opts1] tests/test_base.py::TestCollSpecValidation::TestCountValidation::test_count_and_minlength_or_maxlength_agreement[opts2] tests/test_base.py::TestCollSpecValidation::test_minlength_and_maxlength_agreement tests/test_base.py::TestCollSpecValidation::TestKindValidation::test_kind_validation[frozenset] tests/test_base.py::TestCollSpecValidation::TestKindValidation::test_kind_validation[list] tests/test_base.py::TestCollSpecValidation::TestKindValidation::test_kind_validation[set] tests/test_base.py::TestCollSpecValidation::TestKindValidation::test_kind_validation[tuple] tests/test_base.py::TestCollSpecValidation::TestKindValidation::test_kind_validation_failure[frozenset] tests/test_base.py::TestCollSpecValidation::TestKindValidation::test_kind_validation_failure[list] tests/test_base.py::TestCollSpecValidation::TestKindValidation::test_kind_validation_failure[set] tests/test_base.py::TestCollSpecValidation::TestKindValidation::test_kind_validation_failure[tuple] tests/test_base.py::TestCollSpecConformation::test_coll_conformation tests/test_base.py::TestCollSpecConformation::test_set_coll_conformation tests/test_base.py::TestDictSpecValidation::test_dict_spec_definition[pred0] tests/test_base.py::TestDictSpecValidation::test_dict_spec_definition[pred1] tests/test_base.py::TestDictSpecValidation::test_dict_spec[d0] tests/test_base.py::TestDictSpecValidation::test_dict_spec[d1] tests/test_base.py::TestDictSpecValidation::test_dict_spec[d2] tests/test_base.py::TestDictSpecValidation::test_dict_spec_failure[None] tests/test_base.py::TestDictSpecValidation::test_dict_spec_failure[a tests/test_base.py::TestDictSpecValidation::test_dict_spec_failure[0] tests/test_base.py::TestDictSpecValidation::test_dict_spec_failure[3.14] tests/test_base.py::TestDictSpecValidation::test_dict_spec_failure[True] tests/test_base.py::TestDictSpecValidation::test_dict_spec_failure[False] tests/test_base.py::TestDictSpecValidation::test_dict_spec_failure[d6] tests/test_base.py::TestDictSpecValidation::test_dict_spec_failure[d7] tests/test_base.py::TestDictSpecValidation::test_dict_spec_failure[d8] tests/test_base.py::TestDictSpecValidation::test_dict_spec_failure[d9] tests/test_base.py::TestDictSpecValidation::test_dict_spec_failure[d10] tests/test_base.py::TestDictSpecValidation::test_dict_spec_failure[d11] tests/test_base.py::TestDictSpecValidation::test_dict_spec_failure[d12] tests/test_base.py::TestDictSpecValidation::test_error_details[v0-path0] tests/test_base.py::TestDictSpecValidation::test_error_details[v1-path1] tests/test_base.py::TestDictSpecValidation::test_error_details[v2-path2] tests/test_base.py::TestDictSpecConformation::test_dict_conformation tests/test_base.py::TestObjectSpecValidation::test_obj_spec_definition[pred0] tests/test_base.py::TestObjectSpecValidation::test_obj_spec_definition[pred1] tests/test_base.py::TestObjectSpecValidation::test_obj_spec[o0] tests/test_base.py::TestObjectSpecValidation::test_obj_spec[o1] tests/test_base.py::TestObjectSpecValidation::test_obj_spec[o2] tests/test_base.py::TestObjectSpecValidation::test_obj_spec_failure[o0] tests/test_base.py::TestObjectSpecValidation::test_obj_spec_failure[o1] tests/test_base.py::TestObjectSpecValidation::test_obj_spec_failure[o2] tests/test_base.py::TestObjectSpecValidation::test_obj_spec_failure[o3] tests/test_base.py::TestObjectSpecValidation::test_missing_and_optional_attrs tests/test_base.py::TestSetSpec::test_set_spec tests/test_base.py::TestSetSpec::test_set_spec_conformation tests/test_base.py::TestEnumSetSpec::test_enum_spec tests/test_base.py::TestEnumSetSpec::test_enum_spec_conformation tests/test_base.py::TestTupleSpecValidation::test_tuple_spec[row0] tests/test_base.py::TestTupleSpecValidation::test_tuple_spec[row1] tests/test_base.py::TestTupleSpecValidation::test_tuple_spec[row2] tests/test_base.py::TestTupleSpecValidation::test_tuple_spec_failure[None] tests/test_base.py::TestTupleSpecValidation::test_tuple_spec_failure[a tests/test_base.py::TestTupleSpecValidation::test_tuple_spec_failure[0] tests/test_base.py::TestTupleSpecValidation::test_tuple_spec_failure[3.14] tests/test_base.py::TestTupleSpecValidation::test_tuple_spec_failure[True] tests/test_base.py::TestTupleSpecValidation::test_tuple_spec_failure[False] tests/test_base.py::TestTupleSpecValidation::test_tuple_spec_failure[row6] tests/test_base.py::TestTupleSpecValidation::test_tuple_spec_failure[row7] tests/test_base.py::TestTupleSpecValidation::test_tuple_spec_failure[row8] tests/test_base.py::TestTupleSpecValidation::test_tuple_spec_failure[row9] tests/test_base.py::TestTupleSpecValidation::test_tuple_spec_failure[row10] tests/test_base.py::TestTupleSpecValidation::test_tuple_spec_failure[row11] tests/test_base.py::TestTupleSpecValidation::test_error_details[v0-path0] tests/test_base.py::TestTupleSpecValidation::test_error_details[v1-path1] tests/test_base.py::TestTupleSpecConformation::test_tuple_conformation tests/test_base.py::TestTupleSpecConformation::test_namedtuple_conformation tests/test_base.py::TestAllSpecConstruction::test_all_spec_must_have_pred tests/test_base.py::TestAllSpecConstruction::test_passthrough_spec tests/test_base.py::TestAllSpecValidation::test_all_validation[c5a28680-986f-4f0d-8187-80d1fbe22059] tests/test_base.py::TestAllSpecValidation::test_all_validation[3BE59FF6-9C75-4027-B132-C9792D84547D] tests/test_base.py::TestAllSpecValidation::test_all_failure[6281d852-ef4d-11e9-9002-4c327592fea9] tests/test_base.py::TestAllSpecValidation::test_all_failure[0e8d7ceb-56e8-36d2-9b54-ea48d4bdea3f] tests/test_base.py::TestAllSpecValidation::test_all_failure[10988ff4-136c-5ca7-ab35-a686a56c22c4] tests/test_base.py::TestAllSpecValidation::test_all_failure[] tests/test_base.py::TestAllSpecValidation::test_all_failure[5_0] tests/test_base.py::TestAllSpecValidation::test_all_failure[abcde] tests/test_base.py::TestAllSpecValidation::test_all_failure[ABCDe] tests/test_base.py::TestAllSpecValidation::test_all_failure[5_1] tests/test_base.py::TestAllSpecValidation::test_all_failure[3.14] tests/test_base.py::TestAllSpecValidation::test_all_failure[None] tests/test_base.py::TestAllSpecValidation::test_all_failure[v10] tests/test_base.py::TestAllSpecValidation::test_all_failure[v11] tests/test_base.py::TestAllSpecValidation::test_all_failure[v12] tests/test_base.py::TestAllSpecConformation::test_all_spec_conformation[yes-YesNo.YES] tests/test_base.py::TestAllSpecConformation::test_all_spec_conformation[Yes-YesNo.YES] tests/test_base.py::TestAllSpecConformation::test_all_spec_conformation[yES-YesNo.YES] tests/test_base.py::TestAllSpecConformation::test_all_spec_conformation[YES-YesNo.YES] tests/test_base.py::TestAllSpecConformation::test_all_spec_conformation[no-YesNo.NO] tests/test_base.py::TestAllSpecConformation::test_all_spec_conformation[No-YesNo.NO] tests/test_base.py::TestAllSpecConformation::test_all_spec_conformation[nO-YesNo.NO] tests/test_base.py::TestAllSpecConformation::test_all_spec_conformation[NO-YesNo.NO] tests/test_base.py::TestAnySpecConstruction::test_any_spec_must_have_pred tests/test_base.py::TestAnySpecConstruction::test_passthrough_spec tests/test_base.py::TestAnySpecValidation::test_any_validation[5_0] tests/test_base.py::TestAnySpecValidation::test_any_validation[5_1] tests/test_base.py::TestAnySpecValidation::test_any_validation[3.14] tests/test_base.py::TestAnySpecValidation::test_any_validation_failure[None] tests/test_base.py::TestAnySpecValidation::test_any_validation_failure[v1] tests/test_base.py::TestAnySpecValidation::test_any_validation_failure[v2] tests/test_base.py::TestAnySpecValidation::test_any_validation_failure[v3] tests/test_base.py::TestAnySpecConformation::test_conformation[5-5_0] tests/test_base.py::TestAnySpecConformation::test_conformation[5-5_1] tests/test_base.py::TestAnySpecConformation::test_conformation[3.14-3.14] tests/test_base.py::TestAnySpecConformation::test_conformation[-10--10] tests/test_base.py::TestAnySpecConformation::test_conformation_failure[None] tests/test_base.py::TestAnySpecConformation::test_conformation_failure[v1] tests/test_base.py::TestAnySpecConformation::test_conformation_failure[v2] tests/test_base.py::TestAnySpecConformation::test_conformation_failure[v3] tests/test_base.py::TestAnySpecConformation::test_conformation_failure[500x] tests/test_base.py::TestAnySpecConformation::test_conformation_failure[Just tests/test_base.py::TestAnySpecConformation::test_conformation_failure[500] tests/test_base.py::TestAnySpecConformation::test_conformation_failure[byteword] tests/test_base.py::TestAnySpecConformation::test_tagged_conformation[expected0-5] tests/test_base.py::TestAnySpecConformation::test_tagged_conformation[expected1-5] tests/test_base.py::TestAnySpecConformation::test_tagged_conformation[expected2-3.14] tests/test_base.py::TestAnySpecConformation::test_tagged_conformation[expected3--10] tests/test_base.py::TestAnySpecConformation::test_tagged_conformation_failure[None] tests/test_base.py::TestAnySpecConformation::test_tagged_conformation_failure[v1] tests/test_base.py::TestAnySpecConformation::test_tagged_conformation_failure[v2] tests/test_base.py::TestAnySpecConformation::test_tagged_conformation_failure[v3] tests/test_base.py::TestAnySpecConformation::test_tagged_conformation_failure[500x] tests/test_base.py::TestAnySpecConformation::test_tagged_conformation_failure[Just tests/test_base.py::TestAnySpecConformation::test_tagged_conformation_failure[500] tests/test_base.py::TestAnySpecConformation::test_tagged_conformation_failure[byteword] tests/test_base.py::TestAnySpecWithOuterConformation::test_conformation[10-5_0] tests/test_base.py::TestAnySpecWithOuterConformation::test_conformation[10-5_1] tests/test_base.py::TestAnySpecWithOuterConformation::test_conformation[8.14-3.14] tests/test_base.py::TestAnySpecWithOuterConformation::test_conformation[-5--10] tests/test_base.py::TestAnySpecWithOuterConformation::test_conformation_failure[None] tests/test_base.py::TestAnySpecWithOuterConformation::test_conformation_failure[v1] tests/test_base.py::TestAnySpecWithOuterConformation::test_conformation_failure[v2] tests/test_base.py::TestAnySpecWithOuterConformation::test_conformation_failure[v3] tests/test_base.py::TestAnySpecWithOuterConformation::test_conformation_failure[500x] tests/test_base.py::TestAnySpecWithOuterConformation::test_conformation_failure[Just tests/test_base.py::TestAnySpecWithOuterConformation::test_conformation_failure[500] tests/test_base.py::TestAnySpecWithOuterConformation::test_conformation_failure[byteword] tests/test_base.py::TestAnySpecWithOuterConformation::test_tagged_conformation[expected0-5] tests/test_base.py::TestAnySpecWithOuterConformation::test_tagged_conformation[expected1-5] tests/test_base.py::TestAnySpecWithOuterConformation::test_tagged_conformation[expected2-3.14] tests/test_base.py::TestAnySpecWithOuterConformation::test_tagged_conformation[expected3--10] tests/test_base.py::TestAnySpecWithOuterConformation::test_tagged_conformation_failure[None] tests/test_base.py::TestAnySpecWithOuterConformation::test_tagged_conformation_failure[v1] tests/test_base.py::TestAnySpecWithOuterConformation::test_tagged_conformation_failure[v2] tests/test_base.py::TestAnySpecWithOuterConformation::test_tagged_conformation_failure[v3] tests/test_base.py::TestAnySpecWithOuterConformation::test_tagged_conformation_failure[500x] tests/test_base.py::TestAnySpecWithOuterConformation::test_tagged_conformation_failure[Just tests/test_base.py::TestAnySpecWithOuterConformation::test_tagged_conformation_failure[500] tests/test_base.py::TestAnySpecWithOuterConformation::test_tagged_conformation_failure[byteword] tests/test_base.py::TestMergeSpecConstruction::test_merge_spec_must_have_pred tests/test_base.py::TestMergeSpecConstruction::test_passthrough_spec tests/test_base.py::TestMergeSpecConstruction::test_must_all_be_mapping_specs tests/test_base.py::TestMergeSpecConstruction::test_allow_overlapping_required_and_optional tests/test_base.py::TestMergeSpecConstruction::test_no_overlapping_required_and_optional tests/test_base.py::TestMergeSpecValidation::test_merge_validation[v0] tests/test_base.py::TestMergeSpecValidation::test_merge_validation[v1] tests/test_base.py::TestMergeSpecValidation::test_merge_failure[] tests/test_base.py::TestMergeSpecValidation::test_merge_failure[5_0] tests/test_base.py::TestMergeSpecValidation::test_merge_failure[abcde] tests/test_base.py::TestMergeSpecValidation::test_merge_failure[ABCDe] tests/test_base.py::TestMergeSpecValidation::test_merge_failure[5_1] tests/test_base.py::TestMergeSpecValidation::test_merge_failure[3.14] tests/test_base.py::TestMergeSpecValidation::test_merge_failure[None] tests/test_base.py::TestMergeSpecValidation::test_merge_failure[v7] tests/test_base.py::TestMergeSpecValidation::test_merge_failure[v8] tests/test_base.py::TestMergeSpecValidation::test_merge_failure[v9] tests/test_base.py::TestMergeSpecValidation::test_merge_failure[v10] tests/test_base.py::TestMergeSpecValidation::test_merge_failure[v11] tests/test_base.py::TestMergeSpecValidation::test_merge_failure[v12] tests/test_base.py::TestTypeSpec::test_typecheck[None-vals0] tests/test_base.py::TestTypeSpec::test_typecheck[bool-vals1] tests/test_base.py::TestTypeSpec::test_typecheck[bytes-vals2] tests/test_base.py::TestTypeSpec::test_typecheck[dict-vals3] tests/test_base.py::TestTypeSpec::test_typecheck[float-vals4] tests/test_base.py::TestTypeSpec::test_typecheck[int-vals5] tests/test_base.py::TestTypeSpec::test_typecheck[list-vals6] tests/test_base.py::TestTypeSpec::test_typecheck[set-vals7] tests/test_base.py::TestTypeSpec::test_typecheck[str-vals8] tests/test_base.py::TestTypeSpec::test_typecheck[tuple-vals9] tests/test_base.py::TestTypeSpec::test_typecheck_failure[bool] tests/test_base.py::TestTypeSpec::test_typecheck_failure[bytes] tests/test_base.py::TestTypeSpec::test_typecheck_failure[dict] tests/test_base.py::TestTypeSpec::test_typecheck_failure[float] tests/test_base.py::TestTypeSpec::test_typecheck_failure[int] tests/test_base.py::TestTypeSpec::test_typecheck_failure[list] tests/test_base.py::TestTypeSpec::test_typecheck_failure[set] tests/test_base.py::TestTypeSpec::test_typecheck_failure[str] tests/test_base.py::TestTypeSpec::test_typecheck_failure[tuple]
src/dataspec/factories.py .circleci/config.yml CHANGELOG.md src/dataspec/api.py src/dataspec/base.py docs/usage.rst
src/dataspec/factories.py .circleci/config.yml CHANGELOG.md src/dataspec/api.py src/dataspec/base.py docs/usage.rst