[
  {
    "path": ".gitignore",
    "content": "__pycache__/\n*.py[cod]\n\n.DS_Store\n\n.env\n.venv\nenv/\nvenv/\n\nbuild/\ndist/\n*.egg-info/\n\n.tox/\n.mypy_cache/\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Changelog\n\n## November 2025\n\n- fix most tests on Python 3.14\n- support cast to `datetime.datetime`\n- improve `is_subtype` for `TypedDict`s\n- add `Computed` reference type, thanks sfitzgerald!\n- support int dict keys in blueprint, thanks hessam!\n- fix subparam mutation in the \"template thing\", thanks tz!\n- improve docs, thanks awei!\n- require newer `typing-extensions`\n\n## September 2025\n\n- add `dispatch_entrypoint`\n- several changes to optimise argmap lookups by collapsing and consolidating layers\n  - this is >10x speedup for some use cases\n- always print additional diagnostics for extraneous args, thanks camillo!\n- add `skip_default` arg to `beta_to_blueprint_values`, thanks charlieb!\n- add special casing for tuples in `beta_argv_arg_to_string`, thanks tz!\n- testing improvements\n\n## August 2025\n\n- mention the value of the closest ancestor for extraneous args to help with polymorphism confusion\n- improve extraneous arg error message\n- add `exclude` param to `asdict`, thanks andrey!\n- fix subtype check in the \"template thing\", thanks tz!\n- some cleanup of the \"template thing\"\n\n## July 2025\n\n- changes to add the \"template thing\" to blueprint, thanks xintao!\n  - this feature is not available in the open source version and I plan to attempt to remove it from the internal version\n- differentiate between untyped and zero length tuple in sequence param collection, thanks elwong!\n- fix `beta_argv_arg_to_string` behaviour for list elements that are strings containing commas\n\n## June 2025\n\n- better error if annotation eval fails, thanks jelle!\n- add `ge` and `le` validators, thanks cassirer!\n- special casing to make `beta_argv_arg_to_string` handle dicts, thanks yjiao!\n\n## May 2025\n\n- error for duplicate class when name is ambiguous\n- fix defaulting special case for nested args\n- add `chz.traverse`, thanks hessam!\n- better handling of type variables and meta factory casting\n- special casing to make `beta_argv_arg_to_string` involving lists more compact\n- improve `freeze_dict` munger static typing for optionals, thanks camillo!\n- add `include_type` param to `asdict`, thanks wenda and andrei!\n- internal refactoring\n\n## March 2025\n\nImprovements:\n- add \"universal CLI\" via `python -m chz.universal`\n- add `shallow` param to `asdict` to prevent deep copying, thanks wenda!\n- look at `builtins` and `__main__` to find object factories\n- support `*args` and `**kwargs` collection in blueprint\n- support type variables in `is_subtype`\n- fix variadics that match wildcards in more than one literal location\n- fix blueprint apply to subpath with empty key, thanks hessam!\n- suppert converter argument in field, thanks camillo!\n- refactor param collection in blueprint\n- various docs improvements, thanks andrey, csh, mtli!\n\nError messages:\n- better error for a value with subparams specified\n- improve error for blueprint type mismatch\n- fix bug in `simplistic_type_of_value`\n- include Python's native suggestions for `AttributeError` in blueprint attribute access, thanks yifan!\n\n## February 2025\n\nImprovements:\n- revamp the docs\n- improve casting for callables\n- blindly trust explicit inheritance from protocol\n- record `meta_factory_value` for non castable factory\n- add `__eq__` to `castable`\n- fix quoting in the `beta_blueprint_to_argv` thing\n- expose the `beta_argv_arg_to_string` thing\n\nPerformance:\n- add an optimisation when constructing large variadics for 6x speedup on some workloads\n- rewrite the `beta_blueprint_to_argv` thing so it's now 40x faster\n- make it easier to reuse `MakeResult` to save repeated blueprint make\n- lru cache `inspect.getmembers_static` to speed up repeated construction\n- refactoring to make optimisation easier\n\nError messages:\n- show the full path more often when errors occur during blueprint construction, thanks mlim and gross!\n- add error for case where you have duplicate classes due to `__main__` confusion\n- improve error message when constructing ambiguous or mistyped callables\n- minor improvements to error messages\n\n## January 2025\n\nImprovements:\n- add basic support for functools.partial in blueprints\n- allow parametrising the entrypoint in chz blueprints. this allows for a \"universal\" cli\n- rewrie `beta_to_blueprint_values` to better support nesting and polymorphism\n- improve interaction between type checking and munger\n- ignore self references more consistently when there is a default value available\n- better error when self references are missing a default value\n- add `freeze_dict` munger, thanks camillo!\n- use post init field value in hash, thanks camillo!\n- prevent parameterisation of enum classes\n- colourise and improve alignment of `--help` output\n- various refactoring\n\nTyping improvements:\n- implement callable subtyping (especially useful for substructural typing)\n- improve `is_subtype_instance` of protocols\n- improve `is_subtype_instance` of None, thanks tongzhou!\n- improve `is_subtype` handling of unions, thanks tongzhou!\n- improve `is_subtype` handling of literals and `types.NoneType`\n- better signature subtyping\n- better casting for dict\n\n## December 2024\n\nImprovements:\n- expand the error for wildcard matching variadic defaults, preventing a footgun\n- optimise blueprint construction with large variadics, making a use case 2.7x faster\n- add support for protocol subtyping for vitchyr use case\n- pass field metadata through blueprint, for use in custom tools\n- allow custom root for consistency in tree, thanks ignasi!\n- fix `beta_blueprint_to_argv` with None args, thanks tongzhou!\n- add test for unspecified `type(None)` trick to avoid instantiating defaulted class\n- simplify some `meta_factory` logic\n- fix standard `meta_factory` for `type[specialform]`\n\nError messages:\n- mention layer name for extraneous arguments, so you know where the arg comes from\n- reorder logic in cast for better errors\n- more helpful error message when disallowing `__init__`, `__post_init__`, etc. thanks ebrevdo!\n- other misc error message improvements\n- misc internal docs\n\n## November 2024\n\nTwo headline features for this month: references and `meta_factory` unification:\n- references allow for deduplication of parameters and allow introducing indirection where some config is controlled by other teams\n- `meta_factory` unification makes chz’s polymorphism more consistent and more powerful\n\nFeatures:\n- core of `meta_factory` unification, change default `meta_factory`\n- infra for references, expose references\n- use `X_values` from pre-init in `beta_to_blueprint_values`, thanks guillaume!\n- give users access to methods_entrypoint blueprint\n- add strict option to `Blueprint.apply`, thanks menick!\n- add subpath to apply\n- add override validators, thanks vineet!\n- allow default values in nested_entrypoint\n- make (wildcard) references not self-reference when defaulted\n- recurse into dict in pretty_format\n- make `meta_factory` lambda logic more robust\n- support for python3.12 and 3.13\n\nTyping features:\n- basic pep 692 support\n- add typeddict total=False and pep 655 support, thanks alec!\n- add subtype support for pep 655 / required\n- parse objects as literals\n- allow casting to iterable\n- allow ast eval of tuple for sequence\n- better casting rules for list\n- support casting pathlib\n- add typeddict and callable tests\n\nError messages:\n- improve two issues with `--help` in polymorphic command lines\n- better error when we choose not to cast due to subparams\n- batch errors for invalid ref targets\n- improve error with reference cycles\n- improve error message during blueprint evaluation\n- include previous valid parent for non wildcard extraneous\n- improve error mentioning closest matching parent for extraneous argument\n- improve error messages on failure to interpret argument\n- special case representation of objects from typing module\n\nInternal:\n- many refactoring changes and clean up, including large refactor of blueprint and changes for open source\n\n## October 2024\n\n- finally land support for variadic typeddicts\n- add ability to attach user metadata to fields\n- add better support for NewType, LiteralString, NamedTuple and other niche typing features\n- add some support for PEP 646 unpacking of tuples\n- add native support for casting fractions\n- steps towards `meta_factory` unification. these changes make chz's polymorphism more powerful and more consistent\n- allow disallowing `meta_factory`, useful in niche cases\n- fix static typing of runtime typing to allow better downstream type checking\n\n## September 2024\n\n- add `blueprint_unspecified` to field, as generalisation of `chz.field(meta_factory=chz.factories.subclass(annot, default_cls=...))`. thanks to vitchyr for helping with this\n- use `__orig_class__` to type check user defined generics, if possible\n- add `chz.chz_fields` helper to access `__chz_fields__` attribute\n- better error if there are no params and extraneous args\n\n## August 2024\n\n- add `check_field_consistency_in_tree` validator, as a way to help ensure your wildcards are doing what you want them to do\n- use stdout for `--help`\n- allow parsing empty tuple\n- add a `const_default` validator for constant fields\n\n## July 2024\n\n- improvements to static types, thanks lmetz and wenda\n- quick follow ups to `beta_blueprint_to_argv`, thanks hunter and noah\n- improve `type_repr`, thanks davis\n- minor error improvements\n\n## June 2024\n\n- support for polymorphic variadic generics\n- fix some issues with pydantic support\n- add `x_type` to improve static type checking of mungers\n- add `beta_blueprint_to_argv`, thanks hunter\n- fix callable subtyping with future annotations\n- various improvements to `pretty_repr`, make dunder pure\n- allow use of chz with abc\n- add some special casing to avoid false positives with the conservative check against wildcard default factory interaction\n- improve error message when validating types against a `Literal`\n- improve error message when hashing chz class with unhashable fields, thanks alexk\n- improve error message for unparseable type, thanks andmis\n- fix typo in error message, thanks sean\n- make various error messages more concise\n\n## May 2024\n\n- show default values in `--help`, includes some fancy logic around lambdas\n- show values from unspecified_factory in `--help`, to make polymorphic construction easier to understand\n- add `chz.methods_entrypoint` for easily make cli's from classes\n- support mapping and sequence variadics\n- basic support for pydantic validation during runtime type checking, thanks camillo\n- better handling of runtime contexts for future annotations support\n- support for nested classes when `meta_factory` turns strings into classes\n- better support for polymorphism in `beta_blueprint_to_values`, thanks wenda\n- only error for variadic failure if variadic param specified\n- more docs, more tests, cleaner help output, cleaner tracebacks\n\n## ???\n\nEstablished in 2022\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2024 OpenAI\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# 🪤 chz\n\n*(pronounced \"चीज़\")*\n\n`chz` helps you manage configuration, particularly from the command line.\n\n`chz` is available on [PyPI](https://pypi.org/project/chz/).\n\nTo click the links below, please visit [Github](https://github.com/openai/chz).\n\nOverview:\n- [Quickstart](docs/01_quickstart.md)\n- [Declarative object model](docs/02_object_model.md)\n- [Immutability](docs/02_object_model.md#immutability)\n- [Validation](docs/03_validation.md)\n- [Type checking](docs/03_validation.md#type-checking)\n- [Command line parsing](docs/04_command_line.md)\n- [Discoverability](docs/04_command_line.md#discoverability---help-and-errors)\n- [Partial application](docs/05_blueprint.md)\n- [Presets or shared configuration](docs/05_blueprint.md#presets-or-shared-configuration)\n- [Serialisation and deserialisation](docs/06_serialisation.md)\n\nMore details:\n- [Post init](docs/21_post_init.md)\n- [Field API](docs/22_field_api.md)\n- [Philosophy](docs/91_philosophy.md)\n- [Alternatives](docs/92_alternatives.md)\n- [Testimonials](docs/93_testimonials.md)\n\nPlease let @shantanu know if you have feedback!\n"
  },
  {
    "path": "chz/__init__.py",
    "content": "from typing import TYPE_CHECKING, Callable, TypeVar, overload\n\nfrom . import blueprint, factories, mungers, tiepin, validators\nfrom .blueprint import (\n    Blueprint,\n    Castable,\n    dispatch_entrypoint,\n    entrypoint,\n    get_nested_target,\n    methods_entrypoint,\n    nested_entrypoint,\n)\nfrom .data_model import (\n    asdict,\n    beta_to_blueprint_values,\n    chz_fields,\n    chz_make_class,\n    init_property,\n    is_chz,\n    replace,\n    traverse,\n)\nfrom .field import field\nfrom .validators import validate\n\n__all__ = [\n    \"Blueprint\",\n    \"asdict\",\n    \"chz\",\n    \"is_chz\",\n    \"chz_fields\",\n    \"entrypoint\",\n    \"field\",\n    \"get_nested_target\",\n    \"init_property\",\n    \"methods_entrypoint\",\n    \"nested_entrypoint\",\n    \"replace\",\n    \"beta_to_blueprint_values\",\n    \"traverse\",\n    \"validate\",\n    \"validators\",\n    \"mungers\",\n    \"Castable\",\n    # are the following public?\n    \"blueprint\",\n    \"factories\",\n    \"tiepin\",\n]\n\n\ndef _chz(cls=None, *, version: str | None = None, typecheck: bool | None = None):\n    if cls is None:\n        return lambda cls: chz_make_class(cls, version=version, typecheck=typecheck)\n    return chz_make_class(cls, version=version, typecheck=typecheck)\n\n\nif TYPE_CHECKING:\n    _TypeT = TypeVar(\"_TypeT\", bound=type)\n\n    from typing_extensions import dataclass_transform\n\n    @dataclass_transform(kw_only_default=True, frozen_default=True, field_specifiers=(field,))\n    @overload\n    def chz(version: str = ..., typecheck: bool = ...) -> Callable[[type], type]: ...\n\n    @overload\n    def chz(cls: _TypeT, /) -> _TypeT: ...\n\n    def chz(*a, **k):\n        raise NotImplementedError\n\nelse:\n    chz = _chz\n"
  },
  {
    "path": "chz/blueprint/__init__.py",
    "content": "from chz.blueprint._argv import argv_to_blueprint_args as argv_to_blueprint_args\nfrom chz.blueprint._argv import beta_argv_arg_to_string as beta_argv_arg_to_string\nfrom chz.blueprint._argv import beta_blueprint_to_argv as beta_blueprint_to_argv\nfrom chz.blueprint._blueprint import Blueprint as Blueprint\nfrom chz.blueprint._blueprint import Castable as Castable\nfrom chz.blueprint._blueprint import Reference as Reference\nfrom chz.blueprint._entrypoint import ConstructionException as ConstructionException\nfrom chz.blueprint._entrypoint import EntrypointHelpException as EntrypointHelpException\nfrom chz.blueprint._entrypoint import ExtraneousBlueprintArg as ExtraneousBlueprintArg\nfrom chz.blueprint._entrypoint import InvalidBlueprintArg as InvalidBlueprintArg\nfrom chz.blueprint._entrypoint import MissingBlueprintArg as MissingBlueprintArg\nfrom chz.blueprint._entrypoint import dispatch_entrypoint as dispatch_entrypoint\nfrom chz.blueprint._entrypoint import entrypoint as entrypoint\nfrom chz.blueprint._entrypoint import exit_on_entrypoint_error as exit_on_entrypoint_error\nfrom chz.blueprint._entrypoint import get_nested_target as get_nested_target\nfrom chz.blueprint._entrypoint import methods_entrypoint as methods_entrypoint\nfrom chz.blueprint._entrypoint import nested_entrypoint as nested_entrypoint\n"
  },
  {
    "path": "chz/blueprint/_argmap.py",
    "content": "from __future__ import annotations\n\nimport bisect\nimport re\nfrom dataclasses import dataclass\nfrom typing import TYPE_CHECKING, AbstractSet, Any, Iterator, Mapping\n\nfrom chz.blueprint._entrypoint import ExtraneousBlueprintArg\nfrom chz.blueprint._wildcard import wildcard_key_approx, wildcard_key_to_regex\n\nif TYPE_CHECKING:\n    from chz.blueprint._blueprint import _MakeResult\n\n\nclass Layer:\n    def __init__(self, args: Mapping[str, Any], layer_name: str | None):\n        self._args = args\n        self.layer_name = layer_name\n\n        # Computed from the above\n        self.qualified = {}\n        self.wildcard = {}\n        self._to_regex = {}\n        # Match more specific wildcards first\n        for k, v in sorted(args.items(), key=lambda kv: -len(kv[0])):\n            if \"...\" in k:\n                self.wildcard[k] = v\n                self._to_regex[k] = wildcard_key_to_regex(k)\n            else:\n                self.qualified[k] = v\n\n    def get_kv(self, exact_key: str) -> tuple[str, Any, str | None] | None:\n        # TODO: remove this method\n        if exact_key in self.qualified:\n            return exact_key, self.qualified[exact_key], self.layer_name\n        for wildcard_key, value in self.wildcard.items():\n            if self._to_regex[wildcard_key].fullmatch(exact_key):\n                return wildcard_key, value, self.layer_name\n        return None\n\n    def iter_keys(self) -> Iterator[tuple[str, bool]]:\n        yield from ((k, False) for k in self.qualified)\n        yield from ((k, True) for k in self.wildcard)\n\n    def nest_subpath(self, subpath: str | None) -> Layer:\n        if subpath is None:\n            return self\n        return Layer(\n            {join_arg_path(subpath, k): v for k, v in self._args.items()},\n            self.layer_name,\n        )\n\n    def __repr__(self) -> str:\n        return f\"<Layer {self.layer_name} {self.qualified | self.wildcard}>\"\n\n\n@dataclass(frozen=True)\nclass _FoundArgument:\n    key: str\n    value: Any\n    layer_index: int\n    layer_name: str | None\n\n\ndef _valid_parent(parts: list[str], param_paths: AbstractSet[str]) -> str | None:\n    for i in reversed(range(1, len(parts))):\n        parent = \".\".join(parts[:i])\n        if parent in param_paths:\n            return parent\n    return None\n\n\nclass ArgumentMap:\n    def __init__(self, layers: list[Layer]) -> None:\n        self._layers = layers\n\n        self.consolidated = False\n        self.consolidated_qualified: dict[str, tuple[Any, int]] = {}\n        self.consolidated_qualified_sorted: list[str] = []\n        self.consolidated_wildcard: list[tuple[str, re.Pattern[str], Any, int]] = []\n\n    def add_layer(self, layer: Layer) -> None:\n        self._layers.append(layer)\n        self.consolidated = False\n\n    def consolidate(self) -> None:\n        self.consolidated_qualified = {}\n        for i, layer in enumerate(self._layers):\n            for key, value in layer.qualified.items():\n                self.consolidated_qualified[key] = (value, i)\n        self.consolidated_qualified_sorted = sorted(self.consolidated_qualified.keys())\n\n        self.consolidated_wildcard = []\n        for i, layer in reversed(list(enumerate(self._layers))):\n            for wildcard_key, value in layer.wildcard.items():\n                self.consolidated_wildcard.append(\n                    (wildcard_key, layer._to_regex[wildcard_key], value, i)\n                )\n\n        self.consolidated = True\n\n    def subpaths(self, path: str, strict: bool = False) -> list[str]:\n        \"\"\"Returns the suffix of arguments this contains that would match a subpath of path.\n\n        The invariant is that for each element `suffix` in the returned list, `path + suffix`\n        would match an argument in this map.\n\n        Args:\n            strict: Whether to avoid returning arguments that match path exactly.\n        \"\"\"\n        assert self.consolidated, \"ArgumentMap must be consolidated before calling subpaths\"\n        assert not path.endswith(\".\")\n\n        wildcard_literal = path.split(\".\")[-1]\n        # note path may be the empty string\n        assert path.endswith(wildcard_literal)\n\n        path_plus_dot = path + \".\"\n\n        ret = []\n        if not strict and path in self.consolidated_qualified:\n            ret.append(\"\")\n\n        if not path:\n            ret.extend([k for k in self.consolidated_qualified_sorted if k])\n\n        index = bisect.bisect_left(self.consolidated_qualified_sorted, path_plus_dot)\n        for i in range(index, len(self.consolidated_qualified_sorted)):\n            key = self.consolidated_qualified_sorted[i]\n            if not key.startswith(path_plus_dot):\n                break\n            ret.append(key.removeprefix(path_plus_dot))\n            assert key == join_arg_path(path, ret[-1])\n\n        for key, pattern, _value, _index in self.consolidated_wildcard:\n            if not path:\n                ret.append(key)\n                continue\n\n            # If it's not a wildcard, the logic is straightforward. But doing the equivalent\n            # for wildcards is tricky!\n            i = key.rfind(wildcard_literal)\n            if i == -1:\n                continue\n            # The not strict case is not complicated, we just regex match\n            if pattern.fullmatch(path):\n                if not strict:\n                    ret.append(\"\")\n                    assert pattern.fullmatch(path + ret[-1])\n                continue\n            # This needs a little thinking about.\n            # Say path is \"foo.bar\" and key is \"...bar...baz\"\n            # Then wildcard_literal is \"bar\" and we check if \"...bar\" matches \"foo.bar\"\n            # Since it does, we append \"...baz\"\n            while i != -1:\n                if (\n                    i + len(wildcard_literal) < len(key)\n                    and key[i + len(wildcard_literal)] == \".\"\n                    and wildcard_key_to_regex(key[: i + len(wildcard_literal)]).fullmatch(path)\n                ):\n                    assert i == 0 or key[i - 1] == \".\"\n\n                    suffix = key[i + len(wildcard_literal) :]\n                    if not suffix.startswith(\"...\"):\n                        suffix = suffix.removeprefix(\".\")\n                    ret.append(suffix)\n                    assert pattern.fullmatch(join_arg_path(path, ret[-1]))\n                    break\n                i_next = key.rfind(wildcard_literal, 0, i)\n                assert i_next < i, \"Infinite loop\"\n                i = i_next\n        return ret\n\n    def get_kv(self, exact_key: str, *, ignore_wildcards: bool = False) -> _FoundArgument | None:\n        assert self.consolidated, \"ArgumentMap must be consolidated before calling get_kv\"\n        lookup = self.consolidated_qualified.get(exact_key)\n\n        if not ignore_wildcards:\n            lookup_index = lookup[1] if lookup is not None else -1\n\n            for wildcard_key, pattern, value, index in self.consolidated_wildcard:\n                if index <= lookup_index:\n                    break\n                if pattern.fullmatch(exact_key):\n                    layer_name = self._layers[index].layer_name\n                    return _FoundArgument(wildcard_key, value, index, layer_name=layer_name)\n\n        if lookup is not None:\n            value, lookup_index = lookup\n            layer_name = self._layers[lookup_index].layer_name\n            return _FoundArgument(exact_key, value, lookup_index, layer_name=layer_name)\n\n        return None\n\n    def check_extraneous(\n        self,\n        used_args: set[tuple[str, int]],\n        param_paths: AbstractSet[str],\n        make_result: _MakeResult,\n        *,\n        entrypoint_repr: str,\n    ) -> None:\n        for index in range(len(self._layers)):\n            layer = self._layers[index]\n            for key, is_wildcard in layer.iter_keys():\n                # If something is not in used_args, it means it was either extraneous or it got\n                # clobbered because something in a higher layer matched it\n                if (key, index) in used_args:\n                    continue\n\n                if (\n                    # It's easy to check if a non-wildcard arg was clobbered. We just check if\n                    # there was a param with that name (that we should have matched if not for\n                    # presumed clobbering)\n                    (not is_wildcard and key not in param_paths)\n                    # For wildcards, we need to match against all param paths\n                    or (\n                        is_wildcard\n                        and not any(layer._to_regex[key].fullmatch(p) for p in param_paths)\n                    )\n                ):\n                    # Okay, we have an extraneous argument. We're going to error, but we should\n                    # helpfully try to figure out what the user wanted\n                    extra = \"\"\n                    if layer.layer_name:\n                        extra += f\" (from {layer.layer_name})\"\n\n                    ratios = {p: wildcard_key_approx(key, p) for p in param_paths}\n                    if ratios:\n                        max_option = max(ratios, key=lambda v: ratios[v][0])\n                        if ratios[max_option][0] > 0.1:\n                            extra = f\"\\nDid you mean {ratios[max_option][1]!r}?\"\n                    if not is_wildcard:\n                        nested_pattern = wildcard_key_to_regex(\"...\" + key)\n                        found_key = next(\n                            (p for p in param_paths if nested_pattern.fullmatch(p)), None\n                        )\n                        if found_key is not None:\n                            extra += (\n                                f\"\\nDid you get the nesting wrong, maybe you meant {found_key!r}?\"\n                            )\n                    if key.startswith(\"--\"):\n                        extra += \"\\nDid you mean to use allow_hyphens=True in your entrypoint?\"\n\n                    if not is_wildcard:\n                        parts = key.split(\".\")\n                        if len(parts) >= 2:\n                            valid_parent = _valid_parent(parts, param_paths)\n                            if valid_parent is None:\n                                extra += f\"\\nNo param found matching {parts[0]!r}\"\n                            else:\n                                from chz.blueprint._blueprint import _found_arg_desc\n\n                                extra += f\"\\n\\nParam {valid_parent!r} is closest valid ancestor\"\n                                parent_found_arg = self.get_kv(valid_parent)\n                                param = make_result.all_params[valid_parent]\n                                desc = _found_arg_desc(\n                                    make_result,\n                                    parent_found_arg,\n                                    param_path=valid_parent,\n                                    param=param,\n                                    omit_redundant=False,\n                                )\n                                invalid_part = (\n                                    \".\".join(parts).removeprefix(valid_parent + \".\").split(\".\")[0]\n                                )\n                                extra += f\"\\nParam {valid_parent!r} is set to {desc}\"\n                                extra += f\"\\nSubparam {invalid_part!r} does not exist on it\"\n\n                    raise ExtraneousBlueprintArg(\n                        f\"Extraneous argument {key!r} to Blueprint for {entrypoint_repr}\"\n                        + extra\n                        + \"\\nAppend --help to your command to see valid arguments\"\n                    )\n\n    def __repr__(self) -> str:\n        return \"ArgumentMap(\\n\" + \"\\n\".join(\"    \" + repr(layer) for layer in self._layers) + \"\\n)\"\n\n\ndef join_arg_path(parent: str, child: str) -> str:\n    if not parent:\n        return child\n    if child.startswith(\".\") or child == \"\":\n        return parent + child\n    return parent + \".\" + child\n"
  },
  {
    "path": "chz/blueprint/_argv.py",
    "content": "from __future__ import annotations\n\nimport itertools\nimport types\nfrom typing import Any, TypeVar\n\nimport chz.blueprint\nfrom chz.blueprint._argmap import Layer\nfrom chz.blueprint._wildcard import wildcard_key_to_regex\nfrom chz.tiepin import type_repr\n\n_T = TypeVar(\"_T\")\n\n\ndef argv_to_blueprint_args(\n    argv: list[str], *, allow_hyphens: bool = False\n) -> dict[str, chz.blueprint.Castable | chz.blueprint.Reference]:\n    # TODO: allow stuff like model[family=linear n_layers=1]\n    ret: dict[str, chz.blueprint.Castable | chz.blueprint.Reference] = {}\n    for arg in argv:\n        try:\n            key, value = arg.split(\"=\", 1)\n        except ValueError:\n            raise ValueError(\n                f\"Invalid argument {arg!r}. Specify arguments in the form key=value\"\n            ) from None\n        if allow_hyphens:\n            key = key.lstrip(\"-\")\n\n        # parse key@=reference syntax (note =@ would be ambiguous)\n        if key.endswith(\"@\"):\n            ret[key.removesuffix(\"@\")] = chz.blueprint.Reference(value)\n        else:\n            ret[key] = chz.blueprint.Castable(value)\n    return ret\n\n\ndef beta_argv_arg_to_string(key: str, value: Any) -> list[str]:\n    if isinstance(value, chz.blueprint.Castable):\n        return [f\"{key}={value.value}\"]\n    if isinstance(value, chz.blueprint.Reference):\n        return [f\"{key}@={value.ref}\"]\n    if isinstance(value, (types.FunctionType, type)):\n        return [f\"{key}={type_repr(value)}\"]\n    if isinstance(value, str):\n        return [f\"{key}={value}\"]\n    if isinstance(value, (int, float, bool)) or value is None:\n        return [f\"{key}={repr(value)}\"]\n    if isinstance(value, (list, tuple)):\n        if all(isinstance(e, str) for e in value):\n            if not any(\",\" in e for e in value):\n                return [f\"{key}={','.join(value)}\"]\n            args_list = []\n            for i, e in enumerate(value):\n                args_list.extend(beta_argv_arg_to_string(f\"{key}.{i}\", e))\n            return args_list\n        elif all(isinstance(e, (int, float, bool)) or e is None for e in value):\n            return [f\"{key}={','.join(map(str, value))}\"]\n    if isinstance(value, dict):\n        args_list = []\n        for k, v in value.items():\n            args_list.extend(beta_argv_arg_to_string(f\"{key}.{k}\", v))\n        return args_list\n    # Probably safe to use repr here, but I'm curious to see how people end up using this\n    raise NotImplementedError(\n        f\"TODO: beta_blueprint_to_argv does not currently convert {value!r} of \"\n        f\"type {type(value)} to string\"\n    )\n\n\ndef beta_blueprint_to_argv(blueprint: chz.Blueprint[_T]) -> list[str]:\n    \"\"\"Returns a list of arguments that would recreate the given blueprint.\n\n    Please do not use this function without asking @shantanu, it is slow and not fully robust,\n    and more importantly, there may well be a better way to accomplish your goal.\n    \"\"\"\n    ret = [\n        arg\n        for key, value in _collapse_layers(blueprint)\n        for arg in beta_argv_arg_to_string(key, value)\n    ]\n    return ret\n\n\ndef _collapse_layer(\n    ordered_args: list[tuple[str, Any]], ordered_arg_keys: set[str], layer: Layer\n) -> None:\n    \"\"\"Collapses `layer` into `ordered_args`, overriding any old keys as necessary.\"\"\"\n\n    layer_args: list[tuple[str, Any]] = []\n    keys_to_remove: set[str] = set()\n\n    for key, value in itertools.chain(layer.qualified.items(), layer.wildcard.items()):\n        # Remove any previous args that would be overwritten by this one.\n        wildcard = wildcard_key_to_regex(key) if \"...\" in key else None\n\n        if wildcard:\n            for prev_key in ordered_arg_keys:\n                # TODO(shantanu): usually this regex is only matched against concrete keys\n                # However, here we're matching against other wildcards\n                if wildcard.fullmatch(prev_key):\n                    keys_to_remove.add(prev_key)\n        else:\n            if key in ordered_arg_keys:\n                keys_to_remove.add(key)\n        layer_args.append((key, value))\n\n    # Commit the new layer\n    ordered_args[:] = [arg for arg in ordered_args if arg[0] not in keys_to_remove] + layer_args\n    ordered_arg_keys.difference_update(keys_to_remove)\n    ordered_arg_keys.update(key for key, _ in layer_args)\n\n\ndef _collapse_layers(blueprint: chz.Blueprint[_T]) -> list[tuple[str, Any]]:\n    \"\"\"Collapses the layers of a blueprint into a list of key-value pairs.\n\n    These could be applied as a single layer to a new blueprint to recreate the original.\n    \"\"\"\n    ordered_args: list[tuple[str, Any]] = []\n    ordered_arg_keys: set[str] = set()\n    for layer in blueprint._arg_map._layers:\n        _collapse_layer(ordered_args, ordered_arg_keys, layer)\n    return ordered_args\n"
  },
  {
    "path": "chz/blueprint/_blueprint.py",
    "content": "from __future__ import annotations\n\nimport ast\nimport collections.abc\nimport dataclasses\nimport functools\nimport inspect\nimport io\nimport sys\nimport textwrap\nimport typing\nfrom dataclasses import dataclass\nfrom typing import Any, Callable, Final, Generic, Mapping, Protocol\n\nfrom typing_extensions import TypeVar\n\nimport chz\nfrom chz.blueprint._argmap import ArgumentMap, Layer, _FoundArgument, join_arg_path\nfrom chz.blueprint._argv import argv_to_blueprint_args\nfrom chz.blueprint._entrypoint import (\n    ConstructionException,\n    EntrypointHelpException,\n    ExtraneousBlueprintArg,\n    InvalidBlueprintArg,\n    MissingBlueprintArg,\n)\nfrom chz.blueprint._lazy import (\n    Evaluatable,\n    ParamRef,\n    Thunk,\n    Value,\n    check_reference_targets,\n    evaluate,\n)\nfrom chz.field import Field\nfrom chz.tiepin import (\n    CastError,\n    _simplistic_try_cast,\n    _simplistic_type_of_value,\n    eval_in_context,\n    is_kwargs_unpack,\n    is_subtype_instance,\n    is_typed_dict,\n    type_repr,\n)\nfrom chz.util import MISSING, MISSING_TYPE\n\n_T = TypeVar(\"_T\")\n_T_cov_def = TypeVar(\"_T_cov_def\", covariant=True, default=Any)\n\n\nclass SpecialArg: ...\n\n\nclass Castable(SpecialArg):\n    \"\"\"A wrapper class for str if you want a Blueprint value to be magically type aware casted.\"\"\"\n\n    def __init__(self, value: str) -> None:\n        self.value = value\n\n    def __repr__(self) -> str:\n        return f\"Castable({self.value!r})\"\n\n    def __hash__(self) -> int:\n        return hash(self.value)\n\n    def __eq__(self, other: object) -> bool:\n        if not isinstance(other, Castable):\n            try:\n                return _simplistic_try_cast(self.value, type(other)) == other\n            except CastError:\n                return False\n        return self.value == other.value\n\n\nclass Reference(SpecialArg):\n    \"\"\"A reference to another parameter in a Blueprint.\"\"\"\n\n    def __init__(self, ref: str) -> None:\n        if \"...\" in ref:\n            raise ValueError(\"Cannot use wildcard as a reference target\")\n        self.ref = ref\n\n    def __repr__(self) -> str:\n        return f\"Reference({self.ref!r})\"\n\n\n@dataclass(frozen=True, kw_only=True)\nclass Computed(SpecialArg):\n    \"\"\"A parameter computed from other parameters in a Blueprint.\"\"\"\n\n    src: dict[str, Reference]\n    compute: Callable[..., Any]\n\n    def __repr__(self) -> str:\n        arg_str = \", \".join(f\"{k}@={v.ref}\" for k, v in self.src.items())\n        return f\"Computed({arg_str})\"\n\n\n@dataclass(frozen=True)\nclass _MakeResult:\n    # `value_mapping` is a dictionary mapping from parameter paths to Evaluatable values.\n    # This ultimately contains all the kinds of values we will use in instantiation.\n    # See chz.blueprint._lazy.evaluate for an example of using Evaluatable.\n    value_mapping: dict[str, Evaluatable]\n\n    # `all_params` is a dictionary containing all parameters we discover, mapping from that param\n    # path to the parameter. Note what parameters we discover will depend on polymorphic\n    # construction via meta_factories. We use all_params to provide a useful --help (and various\n    # other things, e.g. detect clobbering when checking for extraneous arguments)\n    all_params: dict[str, _Param]\n\n    # `used_args` is a set of (key, layer_index) tuples that we use to track which arguments from\n    # arg_map we've used. We use this to check for extraneous arguments.\n    used_args: set[tuple[str, int]]\n\n    # `meta_factory_value` records what meta_factory we're using. This makes --help more\n    # understandable in the presence of polymorphism, especially when factories come from\n    # blueprint_unspecified. It's conceptually the same information as in Thunk.fn in value_mapping,\n    # but preserves user input for variadics or generics (instead of being a constructor function)\n    meta_factory_value: dict[str, Any]\n\n    # `missing_params` is a list of parameters we know need are required but haven't been\n    # specified. In theory, this is unnecessary because `__init__` will raise an error if\n    # a required param is missing, but this improves diagnostics.\n    missing_params: list[str]\n\n\ndef _entrypoint_caster(o: str) -> object:\n    raise chz.tiepin.CastError(\"Will not interpret entrypoint as a value\")\n\n\ndef _found_arg_desc(\n    r: _MakeResult,\n    found_arg: _FoundArgument | None,\n    *,\n    param_path: str,\n    param: _Param,\n    omit_redundant: bool = True,\n    color: bool = False,\n) -> str:\n    if found_arg is None:\n        if param_path in r.meta_factory_value:\n            found_arg_str = type_repr(r.meta_factory_value[param_path])\n            if color:\n                found_arg_str += \" \\033[90m(meta_factory)\\033[0m\"\n            else:\n                found_arg_str += \" (meta_factory)\"\n        elif param.default is not None:\n            found_arg_str = param.default.to_help_str()\n            if color:\n                found_arg_str += \" \\033[90m(default)\\033[0m\"\n            else:\n                found_arg_str += \" (default)\"\n        elif (\n            param.meta_factory is not None\n            and (factory := param.meta_factory.unspecified_factory()) is not None\n            and (factory is not param.type or not omit_redundant)\n        ):\n            if getattr(factory, \"__name__\", None) == \"<lambda>\":\n                found_arg_str = _lambda_repr(factory) or type_repr(factory)\n            else:\n                found_arg_str = type_repr(factory)\n            if color:\n                found_arg_str += \" \\033[90m(blueprint_unspecified)\\033[0m\"\n            else:\n                found_arg_str += \" (blueprint_unspecified)\"\n        else:\n            found_arg_str = \"-\"\n    else:\n        if isinstance(found_arg.value, Castable):\n            found_arg_str = repr(found_arg.value.value)[1:-1]\n        elif isinstance(found_arg.value, Reference):\n            found_arg_str = f\"@={found_arg.value.ref}\"\n        elif isinstance(found_arg.value, Computed):\n            arg_str = \", \".join(f\"{k}@={v.ref}\" for k, v in found_arg.value.src.items())\n            found_arg_str = f\"f({arg_str})\"\n        else:\n            found_arg_str = type_repr(found_arg.value)\n        if found_arg.layer_name:\n            if color:\n                found_arg_str += f\" \\033[90m(from \\033[94m{found_arg.layer_name}\\033[90m)\\033[0m\"\n            else:\n                found_arg_str += f\" (from {found_arg.layer_name})\"\n\n    return found_arg_str\n\n\nclass Blueprint(Generic[_T_cov_def]):\n    def __init__(\n        self, target: chz.factories.MetaFactory | type[_T_cov_def] | Callable[..., _T_cov_def]\n    ) -> None:\n        \"\"\"Instantiate a Blueprint.\n\n        Args:\n            target: The target object or callable we will instantiate or call.\n        \"\"\"\n\n        self.target = target\n\n        if isinstance(target, chz.factories.MetaFactory):\n            self.meta_factory = target\n            if isinstance(target, chz.factories.standard):\n                entrypoint_type = target.annotation\n                entrypoint_doc = getattr(entrypoint_type, \"__doc__\", \"\")\n            else:\n                entrypoint_type = object\n                entrypoint_doc = \"\"\n\n            self.entrypoint_repr = type_repr(entrypoint_type)\n        else:\n            self.meta_factory = chz.factories.standard(annotation=target)\n            entrypoint_type = target\n            if self.meta_factory.unspecified_factory() is None:\n                if not callable(target):\n                    raise ValueError(f\"{target} is not callable\")\n                self.meta_factory = chz.factories.standard(annotation=object, unspecified=target)\n                entrypoint_type = object\n\n            self.entrypoint_repr = type_repr(target)\n            entrypoint_doc = getattr(target, \"__doc__\", \"\")\n\n        self.param = _Param(\n            name=\"\",\n            type=entrypoint_type,\n            meta_factory=self.meta_factory,\n            default=None,\n            doc=entrypoint_doc.strip() if entrypoint_doc else \"\",\n            blueprint_cast=_entrypoint_caster,\n            metadata={},\n        )\n\n        self._arg_map = ArgumentMap([])\n\n    def clone(self) -> Blueprint[_T_cov_def]:\n        \"\"\"Make a copy of this Blueprint.\"\"\"\n        return Blueprint(self.target).apply(self)\n\n    def apply(\n        self,\n        values: Blueprint[_T_cov_def] | Mapping[str, Any],\n        layer_name: str | None = None,\n        *,\n        subpath: str | None = None,\n        strict: bool = False,\n    ) -> Blueprint[_T_cov_def]:\n        \"\"\"Modify this Blueprint by partially applying some arguments.\n\n        Args:\n            values: The values to partially apply to this Blueprint\n            layer_name: The name of the layer to add (allows identification of the source of values)\n            subpath: A subpath to nest the argument names under\n            strict: Whether to eagerly check for extraneous arguments. This may not work well in\n                cases where a polymorphic field is applied later.\n        \"\"\"\n        if isinstance(values, Mapping):\n            self._arg_map.add_layer(Layer(values, layer_name).nest_subpath(subpath))\n\n        elif isinstance(values, Blueprint):\n            if subpath is None:\n                if values.target is not self.target:\n                    raise ValueError(\n                        f\"Cannot apply Blueprint for {type_repr(values.target)} to Blueprint for \"\n                        f\"{type_repr(self.target)}\"\n                    )\n            for layer in values._arg_map._layers:\n                self._arg_map.add_layer(layer.nest_subpath(subpath))\n        else:\n            raise TypeError(f\"Expected dict or Blueprint, got {type(values)}\")\n\n        if strict:\n            r = self._make_lazy()\n            self._arg_map.check_extraneous(\n                r.used_args,\n                r.all_params.keys(),\n                make_result=r,\n                entrypoint_repr=self.entrypoint_repr,\n            )\n\n        return self\n\n    def apply_from_argv(\n        self, argv: list[str], allow_hyphens: bool = False, layer_name: str = \"command line\"\n    ) -> Blueprint[_T_cov_def]:\n        \"\"\"Apply arguments from argv to this Blueprint.\"\"\"\n        values = argv_to_blueprint_args(\n            [a for a in argv if a != \"--help\"], allow_hyphens=allow_hyphens\n        )\n\n        self.apply(values, layer_name=layer_name)\n\n        if \"--help\" in argv:\n            argv.remove(\"--help\")\n            raise EntrypointHelpException(self.get_help(color=sys.stdout.isatty()))\n        return self\n\n    def _make_lazy(self) -> _MakeResult:\n        all_params: dict[str, _Param] = {}\n        used_args: set[tuple[str, int]] = set()\n        meta_factory_value: dict[str, Any] = {}\n        missing_params: list[str] = []\n\n        self._arg_map.consolidate()\n\n        value_mapping: dict[str, Evaluatable] | ConstructionIssue | None\n        value_mapping = _construct_param(\n            self.param,\n            \"\",\n            self._arg_map,\n            all_params=all_params,\n            used_args=used_args,\n            meta_factory_value=meta_factory_value,\n            missing_params=missing_params,\n        )\n        if isinstance(value_mapping, ConstructionIssue):\n            raise ConstructionException(value_mapping.issue)\n\n        if value_mapping is None:\n            # value_mapping is None if _construct_param / _construct_unspecified_param\n            # wants us to fallback to the default or thinks we're missing required arguments\n            # This is sort of equivalent to what happens in _construct_factory\n            unspecified_factory = self.meta_factory.unspecified_factory()\n            if unspecified_factory is None:\n                raise ConstructionException(\n                    f\"Cannot construct {self.target} because it has no unspecified factory\"\n                )\n            value_mapping = {\"\": Thunk(unspecified_factory, {})}\n            if \"\" in missing_params:\n                missing_params.remove(\"\")\n\n        return _MakeResult(\n            value_mapping=value_mapping,\n            all_params=all_params,\n            used_args=used_args,\n            meta_factory_value=meta_factory_value,\n            missing_params=missing_params,\n        )\n\n    def _make_from_make_result(self, r: _MakeResult) -> _T_cov_def:\n        self._arg_map.check_extraneous(\n            r.used_args,\n            r.all_params.keys(),\n            make_result=r,\n            entrypoint_repr=self.entrypoint_repr,\n        )\n        check_reference_targets(r.value_mapping, r.all_params.keys())\n        # Note we check for extraneous args first, so we get better errors for typos\n        if r.missing_params:\n            raise MissingBlueprintArg(\n                f\"Missing required arguments for parameter(s): {', '.join(r.missing_params)}\"\n            )\n        # __chz_blueprint__\n        return evaluate(r.value_mapping)\n\n    def make(self) -> _T_cov_def:\n        \"\"\"Instantiate or call the target object or callable.\"\"\"\n        r = self._make_lazy()\n        return self._make_from_make_result(r)\n\n    def make_from_argv(\n        self, argv: list[str] | None = None, allow_hyphens: bool = False\n    ) -> _T_cov_def:\n        \"\"\"Like make, but suitable for command line entrypoints.\n\n        This will apply arguments from argv to this Blueprint before attempting to make the target.\n        If \"--help\" is in argv, this will print help text and exit.\n        \"\"\"\n        if argv is None:\n            argv = sys.argv[1:]\n\n        self.apply_from_argv(argv, allow_hyphens=allow_hyphens)\n\n        return self.make()\n\n    def get_help(self, *, color: bool = False) -> str:\n        \"\"\"Get help text for this Blueprint.\n\n        Note that applied arguments may affect output here, e.g. in case of polymorphically\n        constructed fields.\n        \"\"\"\n        r = self._make_lazy()\n\n        f = io.StringIO()\n        output = functools.partial(print, file=f)\n\n        try:\n            self._arg_map.check_extraneous(\n                r.used_args,\n                r.all_params.keys(),\n                make_result=r,\n                entrypoint_repr=self.entrypoint_repr,\n            )\n        except ExtraneousBlueprintArg as e:\n            output(f\"WARNING: {e}\\n\")\n        try:\n            check_reference_targets(r.value_mapping, r.all_params.keys())\n        except InvalidBlueprintArg as e:\n            output(f\"WARNING: {e}\\n\")\n\n        if r.missing_params:\n            output(\n                f\"WARNING: Missing required arguments for parameter(s): {', '.join(r.missing_params)}\\n\"\n            )\n\n        output(f\"Entry point: {self.entrypoint_repr}\")\n        output()\n        if self.param.doc:\n            output(textwrap.indent(self.param.doc, \"  \"))\n            output()\n\n        param_output = []\n        for param_path, param in r.all_params.items():\n            # TODO: cast or meta_factory info, not just type\n            found_arg = self._arg_map.get_kv(param_path)\n\n            if (\n                not isinstance(self.target, chz.factories.MetaFactory)\n                and param_path == \"\"\n                and found_arg is None\n            ):\n                # If we're not using root polymorphism, skip this param\n                continue\n\n            found_arg_str = _found_arg_desc(\n                r, found_arg, param_path=param_path, param=param, color=color\n            )\n\n            param_output.append(\n                (param_path or \"<entrypoint>\", type_repr(param.type), found_arg_str, param.doc)\n            )\n\n        clip = 40\n        lens = tuple(min(clip, max(map(len, column))) for column in zip(*param_output))\n\n        output(\"Arguments:\")\n        for p, typ, arg, doc in param_output:\n            col = 0\n            target_col = 0\n\n            line = io.StringIO()\n            add = functools.partial(print, file=line, end=\"\")\n\n            raw_string = p\n            add(\"  \")\n            if color:\n                add(f\"\\033[1m{raw_string}\\033[0m\")\n            else:\n                add(raw_string)\n\n            col += 2 + len(raw_string)\n            target_col += 2 + lens[0]\n            pad = (target_col - col) if col <= target_col else (-len(raw_string)) % 20\n            add(\" \" * pad)\n            col += pad\n\n            raw_string = typ\n            add(\"  \")\n            add(raw_string)\n            col += 2 + len(raw_string)\n            target_col += 2 + lens[1]\n            pad = (target_col - col) if col <= target_col else (-len(raw_string)) % 20\n            add(\" \" * pad)\n            col += pad\n\n            raw_string = arg\n            add(\"  \")\n            add(raw_string)\n            col += 2 + len(raw_string)\n            target_col += 2 + lens[2]\n            pad = (target_col - col) if col <= target_col else (-len(raw_string)) % 20\n            add(\" \" * pad)\n            col += pad\n\n            raw_string = doc\n            add(\"  \")\n            if color:\n                add(f\"\\033[90m{raw_string}\\033[0m\")\n            else:\n                add(raw_string)\n\n            output(line.getvalue().rstrip())\n\n        return f.getvalue()\n\n\ndef _lambda_repr(fn) -> str | None:\n    try:\n        src = inspect.getsource(fn).strip()\n        tree = ast.parse(src)\n        nodes = [node for node in ast.walk(tree) if isinstance(node, ast.Lambda)]\n        if len(nodes) != 1:\n            return None\n        return ast.unparse(nodes[0])\n    except Exception:\n        return None\n\n\n@dataclass(frozen=True, kw_only=True)\nclass _Default:\n    value: Any | MISSING_TYPE\n    factory: Callable[..., Any] | MISSING_TYPE\n\n    def to_help_str(self) -> str:\n        if self.factory is not MISSING:\n            if getattr(self.factory, \"__name__\", None) == \"<lambda>\":\n                return f\"({_lambda_repr(self.factory)})()\" or \"<default>\"\n            # type_repr works reasonably well for functions too\n            return f\"{type_repr(self.factory)}()\"\n\n        ret = repr(self.value)\n        if len(ret) > 40:\n            return \"<default>\"\n        return ret\n\n    def instantiate(self) -> Any:\n        if not isinstance(self.factory, MISSING_TYPE):\n            return self.factory()\n        return self.value\n\n    @classmethod\n    def from_field(cls, field: Field) -> _Default | None:\n        if field._default is MISSING and field._default_factory is MISSING:\n            return None\n        return _Default(value=field._default, factory=field._default_factory)\n\n    @classmethod\n    def from_inspect_param(cls, sigparam: inspect.Parameter) -> _Default | None:\n        if sigparam.default is sigparam.empty:\n            return None\n        return _Default(value=sigparam.default, factory=MISSING)\n\n\n@dataclass(frozen=True, kw_only=True)\nclass _Param:\n    name: str\n    type: Any\n    meta_factory: chz.factories.MetaFactory | None\n    default: _Default | None\n    doc: str\n    blueprint_cast: Callable[[str], object] | None\n    metadata: dict[str, Any]\n\n    def cast(self, value: str) -> object:\n        # If we have a field-level cast, always use that!\n        if self.blueprint_cast is not None:\n            return self.blueprint_cast(value)\n\n        # If we have a meta_factory, route casting through it. This allows user expectations\n        # of types that result from casting to better match their expectations from polymorphic\n        # construction (e.g. using default_cls from chz.factories.subclass)\n        if self.meta_factory is not None:\n            return self.meta_factory.perform_cast(value)\n\n        # TODO: maybe MetaFactory should have default impl and this should be:\n        # return chz.factories.MetaFactory().perform_cast(value, self.type)\n        return _simplistic_try_cast(value, self.type)\n\n\ndef _get_variadic_elements(obj_path: str, arg_map: ArgumentMap) -> set[str]:\n    elements = set()\n    for subpath in arg_map.subpaths(obj_path, strict=True):\n        assert subpath\n        if subpath[0] != \".\":\n            element = subpath.split(\".\")[0]\n            assert element\n            elements.add(element)\n    return elements\n\n\ndef _collect_params_from_chz(\n    obj: Any, obj_path: str, arg_map: ArgumentMap\n) -> tuple[list[_Param], Callable[..., Any], list[Any]]:\n    obj_origin = getattr(obj, \"__origin__\", obj)\n    params = []\n    for field in chz.chz_fields(obj_origin).values():\n        params.append(\n            _Param(\n                name=field.logical_name,\n                type=field.x_type,\n                meta_factory=field.meta_factory,\n                default=_Default.from_field(field),\n                doc=field._doc,\n                blueprint_cast=field._blueprint_cast,\n                metadata=(field.metadata or {}),\n            )\n        )\n    return params, obj, []\n\n\ndef _collect_params_from_sequence(\n    obj: Any, obj_path: str, arg_map: ArgumentMap\n) -> tuple[list[_Param], Callable[..., Any], list[Any]]:\n    elements = _get_variadic_elements(obj_path, arg_map)\n    max_element = max((int(e) for e in elements), default=-1)\n\n    obj_origin = getattr(obj, \"__origin__\", obj)\n    obj_type_construct = obj_origin\n\n    type_for_index: Callable[[int], type]\n    if obj_origin is list:\n        element_type = getattr(obj, \"__args__\", [object])[0]\n        type_for_index = lambda i: element_type\n        variadic_types = [element_type]\n\n    elif obj_origin is collections.abc.Sequence:\n        element_type = getattr(obj, \"__args__\", [object])[0]\n        type_for_index = lambda i: element_type\n        variadic_types = [element_type]\n        obj_type_construct = tuple\n\n    elif obj_origin is tuple:\n        args: tuple[Any, ...] | None = getattr(obj, \"__args__\", None)\n        if args is None:\n            args = (Any, ...)\n\n        if len(args) == 2 and args[-1] is ...:\n            # homogeneous tuple\n            type_for_index = lambda i: args[0]\n            variadic_types = [args[0]]\n        else:\n            # heterogeneous tuple\n            if max_element >= len(args):\n                raise TypeError(\n                    f\"Tuple type {obj} for {obj_path!r} must take {len(args)} items; \"\n                    f\"arguments for index {max_element} were specified\"\n                    + (\n                        f\". Homogeneous tuples should be typed as tuple[{type_repr(args[0])}, ...] not tuple[{type_repr(args[0])}]\"\n                        if len(args) == 1\n                        else \"\"\n                    )\n                )\n            type_for_index = lambda i: args[i]\n            variadic_types = list(args)\n    else:\n        raise AssertionError\n\n    params = []\n    for i in range(max_element + 1):\n        element_type = type_for_index(i)\n        params.append(\n            _Param(\n                name=str(i),\n                type=element_type,\n                meta_factory=chz.factories.standard(annotation=element_type),\n                default=None,\n                doc=\"\",\n                blueprint_cast=None,\n                metadata={},\n            )\n        )\n\n    def sequence_constructor(**kwargs):\n        return obj_type_construct(kwargs[str(i)] for i in range(max_element + 1))\n\n    obj_constructor = sequence_constructor\n    return params, obj_constructor, variadic_types\n\n\ndef _collect_params_from_mapping(\n    obj: Any, obj_path: str, arg_map: ArgumentMap\n) -> tuple[list[_Param], Callable[..., Any], list[Any]] | ConstructionIssue:\n    elements = _get_variadic_elements(obj_path, arg_map)\n    args: tuple[Any, ...] = getattr(obj, \"__args__\", ())\n    if len(args) == 0:\n        element_type = object\n        key_type = str\n    elif len(args) == 2:\n        if args[0] not in (str, int):\n            if elements:\n                raise TypeError(\n                    f\"Variadic dict type must take str or int keys, not {type_repr(args[0])}\"\n                )\n            return ConstructionIssue(\n                f\"Variadic dict type must take str or int keys, not {type_repr(args[0])}\"\n            )\n        key_type = args[0]\n        element_type = args[1]\n    else:\n        raise TypeError(f\"Dict type {obj} must take 0 or 2 items\")\n\n    params = []\n    for element in elements:\n        params.append(\n            _Param(\n                name=element,\n                type=element_type,\n                meta_factory=chz.factories.standard(annotation=element_type),\n                default=None,\n                doc=\"\",\n                blueprint_cast=None,\n                metadata={},\n            )\n        )\n\n    def _dict(**kwargs) -> dict[int | str, Any]:\n        return {key_type(k): v for k, v in kwargs.items()}\n\n    return params, _dict, [element_type]\n\n\ndef _collect_params_from_typed_dict(\n    obj: Any, obj_path: str, arg_map: ArgumentMap\n) -> tuple[list[_Param], Callable[..., Any], list[Any]]:\n    obj_origin = getattr(obj, \"__origin__\", obj)\n    params = []\n    variadic_types = []\n    for key, annotation in typing.get_type_hints(obj_origin).items():\n        required = key in obj_origin.__required_keys__\n\n        params.append(\n            _Param(\n                name=key,\n                type=annotation,\n                meta_factory=chz.factories.standard(annotation=annotation),\n                # Mark the default as NotRequired to improve --help output\n                # We don't actually use the default values in Blueprint since we let\n                # instantiation handle insertion of default values\n                default=(None if required else _Default(value=typing.NotRequired, factory=MISSING)),\n                doc=\"\",\n                blueprint_cast=None,\n                metadata={},\n            )\n        )\n        variadic_types.append(annotation)\n\n    return params, obj_origin, variadic_types\n\n\ndef _collect_params_from_callable(\n    obj: Any, obj_path: str, arg_map: ArgumentMap\n) -> tuple[list[_Param], Callable[..., Any], list[Any]] | ConstructionIssue:\n    # Note you probably don't want to call this if obj is a primitive\n    try:\n        signature = inspect.signature(obj)\n    except ValueError:\n        return ConstructionIssue(f\"Failed to get signature for {obj.__name__}\")\n\n    obj_constructor = obj\n    has_pos_only = False\n    has_pos_or_kwarg = False\n    elements: set[str] | None = None\n    params = []\n    for i, (name, sigparam) in enumerate(signature.parameters.items()):\n        param_annot: Any\n        if sigparam.annotation is sigparam.empty:\n            if i == 0 and \".\" in obj.__qualname__:\n                # potentially first parameter of a method, default the annotation to the class\n                try:\n                    cls = getattr(inspect.getmodule(obj), obj.__qualname__.rsplit(\".\", 1)[0])\n                    param_annot = cls\n                except Exception:\n                    param_annot = object\n            else:\n                param_annot = object\n        else:\n            param_annot = sigparam.annotation\n        if isinstance(param_annot, str):\n            try:\n                param_annot = eval_in_context(param_annot, obj)\n            except Exception as e:\n                raise ValueError(\n                    f\"Failed to evaluate parameter {name}: {param_annot} in signature {signature} of object {obj}\"\n                ) from e\n\n        if sigparam.kind == sigparam.POSITIONAL_ONLY:\n            has_pos_only = True\n            name = str(i)\n\n        if sigparam.kind == sigparam.POSITIONAL_OR_KEYWORD:\n            has_pos_or_kwarg = True\n\n        if sigparam.kind == sigparam.VAR_POSITIONAL:\n            if elements is None:\n                elements = _get_variadic_elements(obj_path, arg_map)\n\n            max_element = max((int(e) for e in elements if e.isdigit()), default=-1)\n            if has_pos_or_kwarg and max_element >= 0:\n                return ConstructionIssue(\n                    \"Cannot collect parameters with both positional-or-keyword and variadic positional parameters\"\n                )\n\n            has_pos_only = True\n            for j in range(i, max_element + 1):\n                params.append(\n                    _Param(\n                        name=str(j),\n                        type=param_annot,\n                        meta_factory=chz.factories.standard(annotation=param_annot),\n                        default=None,\n                        doc=\"\",\n                        blueprint_cast=None,\n                        metadata={},\n                    )\n                )\n            continue\n\n        if sigparam.kind == sigparam.VAR_KEYWORD:\n            if is_kwargs_unpack(param_annot):\n                if len(param_annot.__args__) != 1 or not is_typed_dict(param_annot.__args__[0]):\n                    return ConstructionIssue(\n                        f\"Cannot collect parameters from {obj.__name__}, expected Unpack[TypedDict], not {param_annot}\"\n                    )\n                for key, annotation in typing.get_type_hints(param_annot.__args__[0]).items():\n                    # TODO: handle total=False and PEP 655\n                    params.append(\n                        _Param(\n                            name=key,\n                            type=annotation,\n                            meta_factory=chz.factories.standard(annotation=annotation),\n                            default=None,\n                            doc=\"\",\n                            blueprint_cast=None,\n                            metadata={},\n                        )\n                    )\n            else:\n                if elements is None:\n                    elements = _get_variadic_elements(obj_path, arg_map)\n\n                for element in elements - {p.name for p in params}:\n                    params.append(\n                        _Param(\n                            name=element,\n                            type=param_annot,\n                            meta_factory=chz.factories.standard(annotation=param_annot),\n                            default=None,\n                            doc=\"\",\n                            blueprint_cast=None,\n                            metadata={},\n                        )\n                    )\n            continue\n\n        # It could be interesting to let function defaults be chz.Field :-)\n        # TODO: could be fun to parse function docstring\n        params.append(\n            _Param(\n                name=name,\n                type=param_annot,\n                meta_factory=chz.factories.standard(annotation=param_annot),\n                default=_Default.from_inspect_param(sigparam),\n                doc=\"\",\n                blueprint_cast=None,\n                metadata={},\n            )\n        )\n\n    if has_pos_only:\n\n        def positional_constructor(**kwargs):\n            a = []\n            kw = {}\n            for k, v in kwargs.items():\n                if k.isdigit():\n                    a.append((int(k), v))\n                else:\n                    kw[k] = v\n            a = [v for _, v in sorted(a)]\n            return obj(*a, **kw)\n\n        obj_constructor = positional_constructor\n\n    # Note params may be empty here if obj doesn't take any parameters.\n    # This is usually okay, but has some interaction with fully defaulted subcomponents.\n    # See test_nested_all_defaults and variants\n    return params, obj_constructor, []\n\n\ndef _collect_params(\n    obj: Any, obj_path: str, arg_map: ArgumentMap\n) -> (\n    ConstructionIssue\n    | tuple[\n        list[_Param],  # params discovered\n        Callable[..., Any],  # constructor to call\n        list[Any],  # vaguely like [p.type for p in params], used only for sanity checking\n    ]\n):\n    obj_origin = getattr(obj, \"__origin__\", obj)\n\n    if chz.is_chz(obj_origin):\n        return _collect_params_from_chz(obj, obj_path, arg_map)\n\n    if isinstance(obj, functools.partial) and chz.is_chz(obj.func):\n        if obj.args:\n            return ConstructionIssue(\n                f\"Cannot collect parameters from partial function of chz class \"\n                f\"{type_repr(obj.func)} with positional arguments\"\n            )\n        result = _collect_params(obj.func, obj_path, arg_map)\n        if isinstance(result, ConstructionIssue):\n            return result\n\n        params, _constructor, variadic_types = result\n        new_params = []\n        for param in params:\n            if param.name in obj.keywords:\n                # The actual value of the default should only matter for --help output\n                param = dataclasses.replace(\n                    param, default=_Default(value=obj.keywords[param.name], factory=MISSING)\n                )\n            new_params.append(param)\n        return new_params, obj, variadic_types\n\n    if obj_origin in {list, tuple, collections.abc.Sequence}:\n        return _collect_params_from_sequence(obj, obj_path, arg_map)\n\n    if obj_origin in {dict, collections.abc.Mapping}:\n        return _collect_params_from_mapping(obj, obj_path, arg_map)\n\n    if is_typed_dict(obj_origin):\n        return _collect_params_from_typed_dict(obj, obj_path, arg_map)\n\n    if obj_origin in {bool, int, float, str, bytes, None, type(None)}:\n        return ConstructionIssue(\"Cannot collect parameters from primitive\")\n\n    if \"enum\" in sys.modules:\n        import enum\n\n        if type(obj) is enum.EnumMeta:\n            return ConstructionIssue(\"Cannot collect parameters from Enum class\")\n\n    if callable(obj):\n        return _collect_params_from_callable(obj, obj_path, arg_map)\n\n    return ConstructionIssue(\n        f\"Could not collect parameters to construct {obj} of type {type_repr(obj)}\"\n    )\n\n\n_K = TypeVar(\"_K\")\n_V = TypeVar(\"_V\", contravariant=True)\n\n\nclass _WriteOnlyMapping(Generic[_K, _V], Protocol):\n    def __setitem__(self, __key: _K, __value: _V, /) -> None: ...\n    def update(self, __m: Mapping[_K, _V], /) -> None: ...\n\n\nclass ConstructionIssue:\n    def __init__(self, issue: str) -> None:\n        self.issue = issue\n\n\ndef _construct_factory(\n    obj: Callable[..., _T],\n    obj_path: str,\n    arg_map: ArgumentMap,\n    *,\n    # Output parameters, do not use within this function\n    # Typing these as write-only should help prevent accidental unsound use within this function\n    # See _MakeResult for docs about these parameters\n    all_params: _WriteOnlyMapping[str, _Param],\n    used_args: set[tuple[str, int]],\n    meta_factory_value: _WriteOnlyMapping[str, Any],\n    missing_params: list[str],\n) -> dict[str, Evaluatable] | ConstructionIssue:\n    result = _collect_params(obj, obj_path, arg_map)\n    del obj\n\n    if isinstance(result, ConstructionIssue):\n        return result\n\n    params, obj_constructor, _ = result\n\n    # Ideas:\n\n    # TODO: Allow automatically accessing any attribute on parent for factories?\n    # This eases the responsibility of getting the right structure for the config and could mean\n    # we don't need to support wildcards? Reduces problems of something not getting specified\n    # correctly.\n    # \"If you need a value, move it one level up.\"\n\n    # TODO: Allow factories to return blueprints? This would allow for better presets, e.g. you\n    # could do model=d4-dev model.n_layers=5\n\n    kwargs: dict[str, ParamRef] = {}\n    value_mapping: dict[str, Evaluatable] = {}\n\n    for param in params:\n        sub_value_mapping = _construct_param(\n            param,\n            obj_path,\n            arg_map,\n            all_params=all_params,\n            used_args=used_args,\n            meta_factory_value=meta_factory_value,\n            missing_params=missing_params,\n        )\n        if isinstance(sub_value_mapping, ConstructionIssue):\n            return sub_value_mapping\n        if sub_value_mapping is None:\n            continue\n        param_path = (obj_path + \".\" if obj_path else \"\") + param.name\n        value_mapping.update(sub_value_mapping)\n        kwargs[param.name] = ParamRef(param_path)\n\n    value_mapping[obj_path] = Thunk(obj_constructor, kwargs)\n    return value_mapping\n\n\ndef _construct_unspecified_param(\n    param: _Param,\n    *,\n    param_path: str,\n    arg_map: ArgumentMap,\n    # Output parameters, do not use within this function\n    # See _MakeResult for docs about these parameters\n    all_params: _WriteOnlyMapping[str, _Param],\n    used_args: set[tuple[str, int]],\n    meta_factory_value: _WriteOnlyMapping[str, Any],\n    missing_params: list[str],\n) -> dict[str, Evaluatable] | ConstructionIssue | None:\n    if (\n        param.meta_factory is not None\n        and (factory := param.meta_factory.unspecified_factory()) is not None\n    ):\n        assert callable(factory)\n        sub_all_params: dict[str, _Param] = {}\n        sub_missing_params: list[str] = []\n        sub_used_args: set[tuple[str, int]] = set()\n        sub_meta_factory_value: dict[str, Any] = {}\n\n        value_mapping = _construct_factory(\n            factory,\n            param_path,\n            arg_map,\n            all_params=sub_all_params,\n            used_args=sub_used_args,\n            meta_factory_value=sub_meta_factory_value,\n            missing_params=sub_missing_params,\n        )\n        all_params.update(sub_all_params)\n        used_args.update(sub_used_args)  # TODO: should this be gated by use?\n        meta_factory_value.update(sub_meta_factory_value)\n\n        if isinstance(value_mapping, ConstructionIssue):\n            if param_path == \"\" and param.default is None:\n                # This is a little bit special case-y. But if we have a construction issue with\n                # the root param, it's much better to forward it than for the user to get an error\n                # about a missing required root argument.\n                return value_mapping\n        else:\n            thunk = value_mapping[param_path]\n            assert isinstance(thunk, Thunk)\n            # Only do this if we have subcomponents specified (including wildcards)\n            if thunk.kwargs:\n                meta_factory_value[param_path] = factory\n                missing_params.extend(sub_missing_params)\n                return value_mapping\n\n            # Alternatively, if a) we do not have a default, b) we're making a chz object\n            # c) we know instantiation would always work, that's fine too.\n            # A little special-case-y, but somewhat sane. It turns something that would\n            # error due to lack of default into something reasonable.\n            # See test_nested_all_defaults and variants\n            if (\n                param.default is None\n                and (\n                    chz.is_chz(factory)\n                    or (isinstance(factory, functools.partial) and chz.is_chz(factory.func))\n                )\n                and (\n                    all(\n                        p.default is not None\n                        for path, p in sub_all_params.items()\n                        if \".\" not in path.removeprefix(param_path + \".\")\n                    )\n                )\n            ):\n                assert not sub_missing_params\n                meta_factory_value[param_path] = factory\n                return value_mapping\n\n            # If we have a default, make sure we don't extend missing_params\n            if param.default is None:\n                if sub_missing_params:\n                    missing_params.extend(sub_missing_params)\n                else:\n                    # Happens if we collect no params, like non-chz field or variadics\n                    missing_params.append(param_path)\n            else:\n                # If we have a default, do some validation about wildcards + variadics\n                _check_for_wildcard_matching_variadic_top_level(factory, param, param_path, arg_map)\n\n            return None\n\n        assert not sub_missing_params\n\n    # If we have no subcomponents specified or we have no factory, we don't add any kwargs\n    # When the object is created, this will be equivalent to:\n    # `attr = default` or `attr = default_factory()`\n\n    # If there is no default, we will raise MissingBlueprintArg, instead of relying on the\n    # normal Python error during instantiation. We also rely on raising ExtraneousBlueprintArg\n    # if there are arguments that go unused.\n    # (In the case of Blueprint implementation bugs, if we're missing a param, __init__ will\n    # have our back, but the extraneous logic has no backup)\n    if param.default is None:\n        missing_params.append(param_path)\n\n    return None\n\n\ndef _construct_param(\n    param: _Param,\n    obj_path: str,\n    arg_map: ArgumentMap,\n    *,\n    # Output parameters, do not use within this function\n    # See _MakeResult for docs about these parameters\n    all_params: _WriteOnlyMapping[str, _Param],\n    used_args: set[tuple[str, int]],\n    meta_factory_value: _WriteOnlyMapping[str, Any],\n    missing_params: list[str],\n) -> dict[str, Evaluatable] | ConstructionIssue | None:\n    # Returns None if we don't need to pass any value. This doesn't mean there's an error,\n    # we might simply want the default or default_factory value.\n\n    param_path: Final = (obj_path + \".\" if obj_path else \"\") + param.name\n    all_params[param_path] = param\n\n    found_arg = arg_map.get_kv(param_path)\n\n    # If nothing is specified, check if we have a factory we can feed subcomponents to and if there\n    # are specified subcomponents we could feed to it. Otherwise, if a default or default_factory\n    # exists, we'll use that.\n    if found_arg is None:\n        return _construct_unspecified_param(\n            param,\n            param_path=param_path,\n            arg_map=arg_map,\n            all_params=all_params,\n            used_args=used_args,\n            meta_factory_value=meta_factory_value,\n            missing_params=missing_params,\n        )\n\n    used_args.add((found_arg.key, found_arg.layer_index))\n    spec: object = found_arg.value\n\n    # Something is specified, so we must either add something to kwargs or error out\n\n    # If something is specified, and is of the expected type, we just assign it:\n    # `attr = spec`\n    if not isinstance(spec, SpecialArg) and is_subtype_instance(spec, param.type):\n        # (ignore SpecialArg's here, in case param.type is object)\n        if not (param.meta_factory is not None and arg_map.subpaths(param_path, strict=True)):\n            # TODO: deep copy?\n            return {param_path: Value(spec)}\n\n    # ..or if it's a Reference to some other parameter\n    if isinstance(spec, Reference):\n        if spec.ref == param_path:\n            # If it's a self reference, treat it as if it were unspecified\n            value_mapping = _construct_unspecified_param(\n                param,\n                param_path=param_path,\n                arg_map=arg_map,\n                all_params=all_params,\n                used_args=used_args,\n                meta_factory_value=meta_factory_value,\n                missing_params=missing_params,\n            )\n            if isinstance(value_mapping, ConstructionIssue):\n                return value_mapping\n            if value_mapping is None and param.default is not None:\n                # See test_blueprint_reference_wildcard_default\n                # TODO: this is the only place we instantiate a default\n                default = param.default.instantiate()\n                return {param_path: Value(default)}\n            return value_mapping\n\n        return {param_path: ParamRef(spec.ref)}\n\n    elif isinstance(spec, Computed):\n        # If it inherits from a set of other parameters\n        if param_path in {spec.ref for spec in spec.src.values()}:\n            # Same as the unspecified param case\n            return _construct_unspecified_param(\n                param,\n                param_path=param_path,\n                arg_map=arg_map,\n                all_params=all_params,\n                used_args=used_args,\n                meta_factory_value=meta_factory_value,\n                missing_params=missing_params,\n            )\n        else:\n            kwargs = {k: ParamRef(v.ref) for k, v in spec.src.items()}\n            return {param_path: Thunk(kwargs=kwargs, fn=spec.compute)}\n\n    # Otherwise, we see if we can cast it to the expected type:\n    # `attr = trycast(spec.value, param.type)`\n    if isinstance(spec, Castable):\n        # If we have a meta_factory and we have args that are prefixed with the param path, we\n        # will always want to construct that (if we successfully casted here when subcomponents\n        # are specified, we'd just fail later because those subcomponents would be extraneous)\n        if not (param.meta_factory is not None and arg_map.subpaths(param_path, strict=True)):\n            try:\n                casted_spec = param.cast(spec.value)\n                return {param_path: Value(casted_spec)}\n            except CastError:\n                pass\n\n    # ..or if it's a Reference to some other parameter\n    if isinstance(spec, Reference):\n        if spec.ref == param_path:\n            # If it's a self reference, treat it as if it were unspecified\n            value_mapping = _construct_unspecified_param(\n                param,\n                param_path=param_path,\n                arg_map=arg_map,\n                all_params=all_params,\n                used_args=used_args,\n                meta_factory_value=meta_factory_value,\n                missing_params=missing_params,\n            )\n            if isinstance(value_mapping, ConstructionIssue):\n                return value_mapping\n            if value_mapping is None and param.default is not None:\n                # See test_blueprint_reference_wildcard_default\n                # TODO: this is the only place we instantiate a default\n                default = param.default.instantiate()\n                return {param_path: Value(default)}\n            return value_mapping\n\n        return {param_path: ParamRef(spec.ref)}\n\n    # Otherwise, see if it's something that can construct the expected type. For instance,\n    # maybe it's a subclass of param.type, or more generally any `Callable[..., param.type]`,\n    # in which case we do:\n    # `attr = spec(...)`\n    factory: Callable[..., Any]\n    if is_subtype_instance(spec, Callable[..., param.type]):\n        assert callable(spec)\n        factory = spec\n        value_mapping = _construct_factory(\n            factory,\n            param_path,\n            arg_map,\n            all_params=all_params,\n            used_args=used_args,\n            meta_factory_value=meta_factory_value,\n            missing_params=missing_params,\n        )\n        if isinstance(value_mapping, ConstructionIssue):\n            return value_mapping\n        meta_factory_value[param_path] = factory\n        return value_mapping\n\n    # Otherwise, see if it's something that can be casted into something that can construct\n    # the expected type. For instance, maybe it's a string that's the name of a subclass of\n    # param.type or \"module:func\" where module.func is a `func: Callable[..., param.type]`.\n    # `attr = trycast(spec, constructor_type)(...)`\n\n    if isinstance(spec, Castable):\n        if param.meta_factory is not None:\n            try:\n                factory = param.meta_factory.from_string(spec.value)\n            except chz.factories.MetaFromString as e:\n                cast_error = None\n                try:\n                    param.cast(spec.value)\n                except CastError as e2:\n                    cast_error = str(e2)\n                if cast_error is None:\n                    subpaths = arg_map.subpaths(param_path, strict=True)\n                    assert subpaths\n                    cast_error = f\"Not a value, since subparameters were provided (e.g. {join_arg_path(param_path, subpaths[0])!r})\"\n                raise InvalidBlueprintArg(\n                    f\"Could not interpret argument {spec.value!r} provided for param {param_path!r}...\\n\\n\"\n                    f\"- Failed to interpret it as a value:\\n{cast_error}\\n\\n\"\n                    f\"- Failed to interpret it as a factory for polymorphic construction:\\n{e}\"\n                ) from None\n            assert callable(factory)\n            value_mapping = _construct_factory(\n                factory,\n                param_path,\n                arg_map,\n                all_params=all_params,\n                used_args=used_args,\n                meta_factory_value=meta_factory_value,\n                missing_params=missing_params,\n            )\n            if isinstance(value_mapping, ConstructionIssue):\n                return value_mapping\n            meta_factory_value[param_path] = factory\n            return value_mapping\n\n        # This cast is just to raise the error we caught previously\n        try:\n            param.cast(spec.value)\n        except CastError as e:\n            raise InvalidBlueprintArg(\n                f\"Could not cast {spec.value!r} to {type_repr(param.type)}:\\n{e}\"\n            ) from e\n        # This next line should be unreachable...\n        raise TypeError(\n            f\"Expected {param_path!r} to be castable to {type_repr(param.type)}, got {spec.value!r}\"\n        )\n\n    if not isinstance(spec, SpecialArg) and is_subtype_instance(spec, param.type):\n        if param.meta_factory is not None:\n            subpaths = arg_map.subpaths(param_path, strict=True)\n            if subpaths:\n                raise InvalidBlueprintArg(\n                    f\"Could not interpret {spec!r} provided for param {param_path!r} \"\n                    f\"as a value, since subparameters were provided \"\n                    f\"(e.g. {join_arg_path(param_path, subpaths[0])!r})\"\n                )\n\n    raise TypeError(\n        f\"Expected {param_path!r} to be {type_repr(param.type)}, got {type_repr(_simplistic_type_of_value(spec))}\"\n    )\n\n\ndef _check_for_wildcard_matching_variadic_top_level(\n    obj: object, param: _Param, obj_path: str, arg_map: ArgumentMap\n):\n    assert param.default is not None\n    if (\n        type(param.default.value) is tuple and param.default.value == ()\n    ) or param.default.factory in {tuple, list, dict}:\n        return\n\n    result = _collect_params(obj, obj_path, arg_map)\n    if isinstance(result, ConstructionIssue):\n        return\n\n    variadic_params, _, variadic_types = result\n    if variadic_params:\n        return\n    if isinstance(param.default.value, (tuple, list)):\n        variadic_types = list(\n            set(variadic_types) | {type(element) for element in param.default.value}\n        )\n    elif isinstance(param.default.value, dict):\n        variadic_types = list(\n            set(variadic_types) | {type(element) for element in param.default.value.values()}\n        )\n\n    if not variadic_types:\n        return\n\n    # The case we're checking here is if we:\n    # 1) have a param with a default\n    # 1.5) the default is not an empty tuple or list or dict\n    # 2) have a variadic factory for that param\n    # 3) we do not find any variadic params\n    # Then we check if any wildcards would have matched a param if we had one, since it can be\n    # unintuitive that the default will not be affected by the wildcard (default / default_factory\n    # are opaque and have no interaction with wildcards beyond their presence or absence).\n    # See test_variadic_default_wildcard_error\n    for element_type in variadic_types:\n        result = _collect_params(element_type, obj_path + \".__chz_empty_variadic\", arg_map)\n        if isinstance(result, ConstructionIssue):\n            continue\n        subparams, _, _ = result\n        for subparam in subparams:\n            param_path = obj_path + \".__chz_empty_variadic.\" + subparam.name\n            found_arg = arg_map.get_kv(param_path)\n            param_path = obj_path + \".(variadic).\" + subparam.name\n            if found_arg is not None:\n                raise ConstructionException(\n                    f\"\\n\\nYou've hit an interesting case.\\n\\n\"\n                    f'The parameter \"{obj_path}\" is variadic ({type_repr(obj)}), but no '\n                    \"parametrisation was found (either variadic subparameters or a polymorphic \"\n                    \"parametrisation).\\n\"\n                    f'This is fine in theory, because \"{obj_path}\" has a '\n                    f\"default value.\\n\\n\"\n                    f'However, you also specified the wildcard \"{found_arg.key}\" and you may '\n                    f'have expected it to modify the value of \"{param_path}\".\\n'\n                    \"This is not possible -- default values / default_factory results are \"\n                    \"opaque to chz. \"\n                    \"The only way in which default / default_factory interact with Blueprint \"\n                    \"is presence / absence. So out of caution, here's an error!\\n\\n\"\n                    \"If this error is a false positive, consider scoping the wildcard more \"\n                    \"narrowly or using exact keys. As always, appending --help to a chz command \"\n                    \"will show you what gets mapped to which param.\"\n                )\n"
  },
  {
    "path": "chz/blueprint/_entrypoint.py",
    "content": "from __future__ import annotations\n\nimport functools\nimport inspect\nimport io\nimport os\nimport sys\nfrom typing import Any, Callable, TypeVar\n\nimport chz\nfrom chz.tiepin import eval_in_context, type_repr\n\n_T = TypeVar(\"_T\")\n_F = TypeVar(\"_F\", bound=Callable[..., Any])\n\n\nclass EntrypointException(Exception): ...\n\n\nclass EntrypointHelpException(EntrypointException): ...\n\n\nclass ExtraneousBlueprintArg(EntrypointException): ...\n\n\nclass InvalidBlueprintArg(EntrypointException): ...\n\n\nclass MissingBlueprintArg(EntrypointException): ...\n\n\nclass ConstructionException(EntrypointException): ...\n\n\ndef exit_on_entrypoint_error(fn: _F) -> _F:\n    @functools.wraps(fn)\n    def inner(*args, **kwargs):\n        try:\n            return fn(*args, **kwargs)\n        except EntrypointException as e:\n            if isinstance(e, EntrypointHelpException):\n                print(e, end=\"\" if e.args[0][-1] == \"\\n\" else \"\\n\")\n            else:\n                print(\"Error:\", file=sys.stderr)\n                print(e, end=\"\" if e.args[0][-1] == \"\\n\" else \"\\n\", file=sys.stderr)\n            if \"PYTEST_VERSION\" in os.environ:\n                raise\n            sys.exit(1)\n\n    return inner  # type: ignore[return-value]\n\n\n@exit_on_entrypoint_error\ndef entrypoint(\n    target: Callable[..., _T], *, argv: list[str] | None = None, allow_hyphens: bool = False\n) -> _T:\n    \"\"\"Easy way to create a script entrypoint using chz.\n\n    For example, if you wish to run a function:\n    ```\n    def do_something(alpha: int, beta: str, gamma: bytes) -> None:\n        ...\n\n    if __name__ == \"__main__\":\n        chz.entrypoint(do_something)\n    ```\n\n    It also works for instantiating objects:\n    ```\n    @chz.chz\n    class Run:\n        name: str\n        def launch(self) -> None: ...\n\n    if __name__ == \"__main__\":\n        run = chz.entrypoint(Run)\n        run.launch()\n    ```\n    \"\"\"\n    # This function should be easily forkable, so do not make it more complicated\n    return chz.Blueprint(target).make_from_argv(argv, allow_hyphens=allow_hyphens)\n\n\n@exit_on_entrypoint_error\ndef nested_entrypoint(\n    main: Callable[[Any], _T], *, argv: list[str] | None = None, allow_hyphens: bool = False\n) -> _T:\n    \"\"\"Easy way to create a script entrypoint using chz for functions that take a chz object.\n\n    For example:\n    ```\n    @chz.chz\n    class Run:\n        name: str\n\n    def main(run: Run) -> None:\n        ...\n\n    if __name__ == \"__main__\":\n        chz.nested_entrypoint(main)\n    ```\n\n    Tip: If your `main` function is `async`, you can just do\n    `asyncio.run(chz.nested_entrypoint(main))`.\n    \"\"\"\n    # This function should be easily forkable, so do not make it more complicated\n    target = get_nested_target(main)\n    value = chz.Blueprint(target).make_from_argv(argv, allow_hyphens=allow_hyphens)\n    return main(value)\n\n\n@exit_on_entrypoint_error\ndef methods_entrypoint(\n    target: type[_T],\n    *,\n    argv: list[str] | None = None,\n    transform: Callable[[chz.Blueprint[Any], Any, str], chz.Blueprint[Any]] | None = None,\n) -> _T:\n    \"\"\"Easy way to create a script entrypoint using chz for methods on a class.\n\n    For example, given main.py:\n    ```\n    @chz.chz\n    class Run:\n        name: str\n\n        def launch(self, cluster: str):\n            \"Launch a job on a cluster\"\n            return (\"launch\", self, cluster)\n\n    if __name__ == \"__main__\":\n        print(chz.methods_entrypoint(Run))\n    ```\n\n    Try out the following command line invocations:\n    ```\n    python main.py launch self.name=job cluster=owl\n    python main.py launch --help\n    python main.py --help\n    ```\n\n    Note that you can rename the `self` argument in your method to something else.\n    \"\"\"\n    if argv is None:\n        argv = sys.argv[1:]\n\n    is_help = not argv or argv[0] == \"--help\"\n    is_valid = not argv or (argv[0].isidentifier() and hasattr(target, argv[0]))\n\n    if is_help or not is_valid:\n        f = io.StringIO()\n        output = functools.partial(print, file=f)\n        if not is_valid:\n            output(f\"WARNING: {argv[0]} is not a valid method\")\n        output(f\"Entry point: methods of {type_repr(target)}\")\n        output()\n        output(\"Available methods:\")\n        for name in dir(target):\n            meth = getattr(target, name)\n            if not name.startswith(\"_\") and callable(meth):\n                meth_doc = getattr(meth, \"__doc__\", \"\") or \"\"\n                meth_doc = meth_doc.strip().split(\"\\n\", 1)[0]\n                output(f\"  {name}  {meth_doc}\".rstrip())\n        raise EntrypointHelpException(f.getvalue())\n\n    blueprint = chz.Blueprint(getattr(target, argv[0]))\n    if transform is not None:\n        blueprint = transform(blueprint, target, argv[0])\n    return blueprint.make_from_argv(argv[1:])\n\n\n@exit_on_entrypoint_error\ndef dispatch_entrypoint(\n    targets: dict[str, Callable[..., _T]], *, argv: list[str] | None = None\n) -> _T:\n    \"\"\"Easy way to create a script entrypoint using chz for dispatching to different functions.\n\n    Conceptually, this is strictly a subset of the universal `python -m chz.universal` entrypoint.\n    Compared to that, or methods_entrypoint, this basically just lets you flatten args one level.\n\n    ```\n    def say_hello(name: str) -> None:\n        print(f\"Hello, {name}!\")\n\n    def say_goodbye(name: str) -> None:\n        print(f\"Goodbye, {name}!\")\n\n    chz.dispatch_entrypoint({\n        \"hello\": say_hello,\n        \"goodbye\": say_goodbye,\n    })\n    ```\n    \"\"\"\n    if argv is None:\n        argv = sys.argv[1:]\n\n    is_help = not argv or argv[0] == \"--help\"\n    is_valid = not argv or (argv[0].isidentifier() and argv[0] in targets)\n\n    if is_help or not is_valid:\n        f = io.StringIO()\n        output = functools.partial(print, file=f)\n        if not is_valid:\n            output(f\"WARNING: {argv[0]} is not a valid entrypoint\")\n        output(\"Available entrypoints:\")\n        for name in targets:\n            meth = targets[name]\n            meth_doc = getattr(meth, \"__doc__\", \"\") or \"\"\n            meth_doc = meth_doc.strip().split(\"\\n\", 1)[0]\n            output(f\"  {name}  {meth_doc}\".rstrip())\n        raise EntrypointHelpException(f.getvalue())\n\n    return chz.Blueprint(targets[argv[0]]).make_from_argv(argv[1:])\n\n\ndef _resolve_annotation(annotation: Any, func: Any) -> Any:\n    \"\"\"Resolves a type annotation against the globals of the target function.\"\"\"\n    if annotation is inspect.Parameter.empty:\n        return None\n    if isinstance(annotation, str):\n        return eval_in_context(annotation, func)\n    return annotation\n\n\ndef get_nested_target(main: Callable[[_T], object]) -> type[_T]:\n    \"\"\"Returns the type of the first argument of a function.\n\n    For example:\n    ```\n    def main(run: Run) -> None: ...\n\n    assert chz.get_nested_target(main) is Run\n    ```\n    \"\"\"\n    params = list(inspect.signature(main).parameters.values())\n    if not params or params[0].annotation == inspect.Parameter.empty:\n        raise ValueError(\"Nested entrypoints must take a type annotated argument\")\n    if any(p.default is p.empty for p in params[1:]):\n        raise ValueError(\"Nested entrypoints must take at most one argument without a default\")\n    return _resolve_annotation(params[0].annotation, main)\n"
  },
  {
    "path": "chz/blueprint/_lazy.py",
    "content": "import collections\nfrom typing import AbstractSet, Any, Callable, TypeVar\n\nfrom chz.blueprint._entrypoint import InvalidBlueprintArg\nfrom chz.blueprint._wildcard import wildcard_key_approx, wildcard_key_to_regex\nfrom chz.tiepin import type_repr\n\nT = TypeVar(\"T\")\n\n\nclass Evaluatable: ...\n\n\nclass Value(Evaluatable):\n    def __init__(self, value: Any) -> None:\n        self.value = value\n\n    def __repr__(self) -> str:\n        return f\"Value({self.value})\"\n\n\nclass ParamRef(Evaluatable):\n    def __init__(self, ref: str) -> None:\n        self.ref = ref\n\n    def __repr__(self) -> str:\n        return f\"ParamRef({self.ref})\"\n\n\nclass Thunk(Evaluatable):\n    def __init__(self, fn: Callable[..., Any], kwargs: dict[str, ParamRef]) -> None:\n        self.fn = fn\n        self.kwargs = kwargs\n\n    def __repr__(self) -> str:\n        return f\"Thunk({type_repr(self.fn)}, {self.kwargs})\"\n\n\ndef evaluate(value_mapping: dict[str, Evaluatable]) -> Any:\n    assert \"\" in value_mapping\n\n    refs_in_progress = collections.OrderedDict[str, None]()\n\n    def inner(ref: str) -> Any:\n        if ref in refs_in_progress:\n            cycle = \" -> \".join(list(refs_in_progress.keys())[1:] + [ref])\n            raise RecursionError(f\"Detected cyclic reference: {cycle}\")\n\n        refs_in_progress[ref] = None\n        try:\n            value = value_mapping[ref]\n            assert isinstance(value, Evaluatable)\n\n            if isinstance(value, Value):\n                return value.value\n\n            if isinstance(value, ParamRef):\n                try:\n                    ret = inner(value.ref)\n                except Exception as e:\n                    e.add_note(f\" (when dereferencing {ref!r})\")\n                    raise\n                assert not isinstance(ret, Evaluatable)\n                value_mapping[ref] = Value(ret)\n                return ret\n\n            if isinstance(value, Thunk):\n                kwargs = {}\n                for k, v in value.kwargs.items():\n                    assert isinstance(v, ParamRef)\n                    try:\n                        kwargs[k] = inner(v.ref)\n                    except Exception as e:\n                        e.add_note(f\" (when evaluating argument {k!r} for {type_repr(value.fn)})\")\n                        raise\n                ret = value.fn(**kwargs)\n                return ret\n        finally:\n            item = refs_in_progress.popitem()\n            assert item[0] == ref\n\n        raise AssertionError\n\n    return inner(\"\")\n\n\ndef check_reference_targets(\n    value_mapping: dict[str, Evaluatable], param_paths: AbstractSet[str]\n) -> None:\n    invalid_references: dict[str, list[str]] = {}\n\n    def record_invalid(ref: str, referrer: str) -> None:\n        if not referrer:\n            return\n        if ref not in param_paths:\n            referrers = invalid_references.setdefault(ref, [])\n            if referrer not in referrers:\n                referrers.append(referrer)\n\n    def walk(value: Evaluatable, referrer: str) -> None:\n        if isinstance(value, ParamRef):\n            record_invalid(value.ref, referrer)\n        elif isinstance(value, Thunk):\n            for param_ref in value.kwargs.values():\n                walk(param_ref, referrer)\n\n    for param_path, value in value_mapping.items():\n        walk(value, param_path)\n\n    if invalid_references:\n        errors = []\n        for reference, referrers in invalid_references.items():\n            ratios = {p: wildcard_key_approx(reference, p) for p in param_paths}\n            extra = \"\"\n            if ratios:\n                max_option = max(ratios, key=lambda v: ratios[v][0])\n                if ratios[max_option][0] > 0.1:\n                    extra = f\"\\nDid you mean {ratios[max_option][1]!r}?\"\n\n            nested_pattern = wildcard_key_to_regex(\"...\" + reference)\n            found_key = next((p for p in param_paths if nested_pattern.fullmatch(p)), None)\n            if found_key is not None:\n                extra += f\"\\nDid you get the nesting wrong, maybe you meant {found_key!r}?\"\n\n            if len(referrers) > 1:\n                referrers_str = \"params \" + \", \".join(referrers)\n            else:\n                referrers_str = f\"param {referrers[0]}\"\n\n            errors.append(f\"Invalid reference target {reference!r} for {referrers_str}\" + extra)\n\n        raise InvalidBlueprintArg(\"\\n\\n\".join(errors))\n"
  },
  {
    "path": "chz/blueprint/_wildcard.py",
    "content": "import re\n\n_FUZZY_SIMILARITY = 0.6\n\n\ndef wildcard_key_to_regex_str(key: str) -> str:\n    if key.endswith(\"...\"):\n        raise ValueError(\"Wildcard not allowed at end of key\")\n    pattern = r\"(.*\\.)?\" if key.startswith(\"...\") else \"\"\n    pattern += r\"\\.(.*\\.)?\".join(map(re.escape, key.removeprefix(\"...\").split(\"...\")))\n    return pattern\n\n\ndef wildcard_key_to_regex(key: str) -> re.Pattern[str]:\n    return re.compile(wildcard_key_to_regex_str(key))\n\n\ndef _wildcard_key_match(key: str, target_str: str) -> bool:\n    # This is what the regex does; currently unused (but is tested)\n    if key.endswith(\"...\"):\n        raise ValueError(\"Wildcard not allowed at end of key\")\n    pattern = [\"...\"] if key.startswith(\"...\") else []\n    pattern += [x for x in re.split(r\"(\\.\\.\\.)|\\.\", key.removeprefix(\"...\")) if x is not None]\n    target = target_str.split(\".\")\n\n    _grid: dict[tuple[int, int], bool] = {}\n\n    def _match(i: int, j: int) -> bool:\n        if i == len(pattern):\n            return j == len(target)\n        if j == len(target):\n            return False\n        if (i, j) in _grid:\n            return _grid[i, j]\n        if pattern[i] == \"...\":\n            ret = _match(i, j + 1) or _match(i + 1, j)\n            _grid[i, j] = ret\n            return ret\n        ret = pattern[i] == target[j] and _match(i + 1, j + 1)\n        _grid[i, j] = ret\n        return ret\n\n    return _match(0, 0)\n\n\ndef wildcard_key_approx(key: str, target_str: str) -> tuple[float, str]:\n    \"\"\"\n    Returns a score and a string representing what the key should have been to match the target.\n\n    Currently only used in error messages.\n    \"\"\"\n    if key.endswith(\"...\"):\n        raise ValueError(\"Wildcard not allowed at end of key\")\n    pattern = [\"...\"] if key.startswith(\"...\") else []\n    pattern += [x for x in re.split(r\"(\\.\\.\\.)|\\.\", key.removeprefix(\"...\")) if x is not None]\n    target = target_str.split(\".\")\n\n    import difflib\n\n    _grid: dict[tuple[int, int], tuple[float, tuple[str, ...]]] = {}\n\n    def _match(i, j) -> tuple[float, tuple[str, ...]]:\n        if i == len(pattern):\n            return (1, ()) if j == len(target) else (0, ())\n        if j == len(target):\n            return (0, ())\n        if (i, j) in _grid:\n            return _grid[i, j]\n        if pattern[i] == \"...\":\n            with_wildcard = _match(i, j + 1)\n            without_wildcard = _match(i + 1, j)\n            if with_wildcard[0] * _FUZZY_SIMILARITY > without_wildcard[0]:\n                score, value = with_wildcard\n                score *= _FUZZY_SIMILARITY\n            else:\n                score, value = without_wildcard\n            if value and value[0] != \"...\":\n                value = (\"...\",) + value\n            ret = (score, value)\n            _grid[i, j] = ret\n            return ret\n\n        ratio = difflib.SequenceMatcher(a=pattern[i], b=target[j]).ratio()\n        if ratio >= _FUZZY_SIMILARITY:\n            score, value = _match(i + 1, j + 1)\n            score *= ratio\n            if value and value[0] != \"...\":\n                value = (target[j] + \".\",) + value\n            else:\n                value = (target[j],) + value\n            ret = (score, value)\n            _grid[i, j] = ret\n            return ret\n        return 0, ()\n\n    score, value = _match(0, 0)\n    return score, \"\".join(value)\n"
  },
  {
    "path": "chz/data_model.py",
    "content": "\"\"\"\n\nThis is the core implementation of the chz class. It's based off of the implementation of\ndataclasses, but is somewhat simpler. I also fixed a couple minor issues in dataclasses when\nwriting this :-)\n\nSome non-exhaustive reasons why chz's feature set isn't built on top of dataclasses / attrs:\n- dataclasses is a general purpose class replacement, chz isn't. This lets us establish intention,\n  have better defaults, make different tradeoffs, better errors in various places\n- Ability to have custom logic in chz.field\n- Clearer handling of type annotation evaluation and scopes\n- chz needs keyword-only arguments for various reasons (dataclasses acquired this only later)\n- Cool data model tricks like munging and init_property\n- Many small things\n\n\"\"\"\n\nimport builtins\nimport copy\nimport dataclasses\nimport functools\nimport hashlib\nimport inspect\nimport sys\nimport types\nimport typing\nfrom collections.abc import Collection, Mapping\nfrom typing import TYPE_CHECKING, Any, Callable, Iterable, TypeVar\n\nimport typing_extensions\n\nfrom chz.field import Field\nfrom chz.tiepin import type_repr\nfrom chz.util import MISSING\n\nFrozenInstanceError = dataclasses.FrozenInstanceError\n\n_T = TypeVar(\"_T\")\n\n\n_INIT_ALTERNATIVES: str = (\n    \"For validation, see @chz.validate decorators. \"\n    \"For per-field defaults, see `default` and `default_factory` options in chz.field. \"\n    \"To perform post-initialization rewrites of field values, use `munger` option in chz.field \"\n    \"or add an `init_property` to the class.\\n\"\n    \"See the docs for more details.\"\n)\n\n\ndef _create_fn(\n    name: str, args: list[str], body: list[str], *, locals: dict[str, Any], globals: dict[str, Any]\n):\n    args_str = \",\".join(args)\n    body_str = \"\\n\".join(f\"  {b}\" for b in body)\n\n    # Compute the text of the entire function.\n    txt = f\" def {name}({args_str}):\\n{body_str}\"\n\n    # Free variables in exec are resolved in the global namespace.\n    # The global namespace we have is user-provided, so we can't modify it for\n    # our purposes. So we put the things we need into locals and introduce a\n    # scope to allow the function we're creating to close over them.\n    local_vars = \", \".join(locals.keys())\n    txt = f\"def __create_fn__({local_vars}):\\n{txt}\\n return {name}\"\n\n    ns: Any = {}\n    exec(txt, globals, ns)\n    return ns[\"__create_fn__\"](**locals)\n\n\n# ==============================\n# Method synthesis\n# ==============================\n\n\ndef _synthesise_field_init(f: Field, out_vars: dict[str, Any]) -> tuple[str, str]:\n    # This function modifies out_vars\n    var_type = f\"__chz_{f.logical_name}\"\n    out_vars[var_type] = f._raw_type\n\n    var_default = f\"__chz_dflt_{f.logical_name}\"\n    if f._default_factory is not MISSING:\n        out_vars[var_default] = f._default_factory\n        value = f\"{var_default}() if {f.logical_name} is __chz_MISSING else {f.logical_name}\"\n        dflt_expr = \" = __chz_MISSING\"\n    elif f._default is not MISSING:\n        out_vars[var_default] = f._default\n        # Is it ever useful to explicitly pass MISSING?\n        # value = f\"{var_default} if {f.logical_name} is __chz_MISSING else {f.logical_name}\"\n        value = f.logical_name\n        dflt_expr = f\" = {var_default}\"\n    else:\n        value = f.logical_name\n        dflt_expr = \"\"\n\n    arg = f\"{f.logical_name}: {var_type}{dflt_expr}\"\n    body = f\"__chz_builtins.object.__setattr__(self, {f.x_name!r}, {value})\"\n\n    return arg, body\n\n\ndef _synthesise_init(fields: Collection[Field], user_globals: dict[str, Any]) -> Callable[..., Any]:\n    varlocals = {\"__chz_MISSING\": MISSING, \"__chz_builtins\": builtins}\n\n    # __chz_args is not strictly necessary, but makes for better errors\n    args = [\"self\", \"*__chz_args\"]\n    body = [\n        \"if __chz_args:\",\n        \"    raise __chz_builtins.TypeError(f'{self.__class__.__name__}.__init__ only takes keyword arguments')\",\n        \"if '__chz_fields__' not in __chz_builtins.type(self).__dict__:\",\n        \"    raise __chz_builtins.TypeError(f'{self.__class__.__name__} is not decorated with @chz.chz')\",\n    ]\n    for field in fields:\n        if field.logical_name.startswith(\"__chz\") or field.logical_name == \"self\":\n            raise ValueError(f\"Field name {field.logical_name!r} is reserved\")\n        _arg, _body = _synthesise_field_init(field, varlocals)\n\n        args.append(_arg)\n        body.append(_body)\n\n    # Note it's important we validate before we check all init_property\n    body.append(\"self.__chz_validate__()\")\n    body.append(\"self.__chz_init_property__()\")\n\n    return _create_fn(\"__init__\", args, body, locals=varlocals, globals=user_globals)\n\n\ndef __setattr__(self, name, value):\n    raise FrozenInstanceError(f\"Cannot modify field {name!r}\")\n\n\ndef __delattr__(self, name):\n    raise FrozenInstanceError(f\"Cannot delete field {name!r}\")\n\n\ndef _recursive_repr(user_function):\n    import threading\n\n    repr_running = set()\n\n    @functools.wraps(user_function)\n    def wrapper(self):\n        key = id(self), threading.get_ident()\n        if key in repr_running:\n            return \"...\"\n        repr_running.add(key)\n        try:\n            result = user_function(self)\n        finally:\n            repr_running.discard(key)\n        return result\n\n    return wrapper\n\n\ndef __repr__(self) -> str:\n    def field_repr(field: Field) -> str:\n        # use x_name so that repr can be copy-pasted to create the same object\n        if callable(field._repr):\n            return field._repr(getattr(self, field.x_name))\n        assert isinstance(field._repr, bool)\n        if field._repr:\n            return repr(getattr(self, field.x_name))\n        return \"...\"\n\n    contents = \", \".join(\n        f\"{field.logical_name}={field_repr(field)}\" for field in self.__chz_fields__.values()\n    )\n    return self.__class__.__qualname__ + f\"({contents})\"\n\n\ndef __eq__(self, other):\n    if self.__class__ is not other.__class__:\n        return NotImplemented\n    return all(getattr(self, name) == getattr(other, name) for name in self.__chz_fields__)\n\n\ndef __hash__(self) -> int:\n    try:\n        return hash(tuple((name, getattr(self, name)) for name in self.__chz_fields__))\n    except TypeError as e:\n        for name in self.__chz_fields__:\n            value = getattr(self, name)\n            try:\n                hash(value)\n            except TypeError:\n                raise TypeError(\n                    f\"Cannot hash chz field: {type(self).__name__}.{name}={value}\"\n                ) from e\n        raise e\n\n\ndef __chz_validate__(self) -> None:\n    for field in self.__chz_fields__.values():\n        if field._munger is None:\n            for validator in field._validator:\n                # So without mungers, we always run validators against the raw value\n                # There is currently code that relies on not running validator against a potential\n                # user-specified init_property\n                # TODO: is it unfortunate that x_name appears in error messages?\n                validator(self, field.x_name)\n        else:\n            # With mungers, we run validators against both the munged and unmunged value\n            # I'm willing to reconsider this, but want to be conservative for now\n            for validator in field._validator:\n                validator(self, field.logical_name)\n                validator(self, field.x_name)\n    for validator in getattr(self, \"__chz_validators__\", []):\n        validator(self)\n\n\n@functools.lru_cache()\ndef _get_init_properties(cls: type) -> list[str]:\n    return [\n        name\n        for name, _obj in inspect.getmembers_static(cls, lambda o: isinstance(o, init_property))\n    ]\n\n\ndef __chz_init_property__(self) -> None:\n    for name in _get_init_properties(self.__class__):\n        getattr(self, name)\n\n\ndef pretty_format(obj: Any, colored: bool = True) -> str:\n    \"\"\"Format a chz object for human readability.\"\"\"\n    bold = \"\\033[1m\" if colored else \"\"\n    blue = \"\\033[34m\" if colored else \"\"\n    grey = \"\\033[90m\" if colored else \"\"\n    reset = \"\\033[0m\" if colored else \"\"\n    space = \" \" * 4\n\n    if isinstance(obj, (list, tuple)):\n        if not obj or all(not is_chz(x) for x in obj):\n            return repr(obj)\n\n        a, b = (\"[\", \"]\") if isinstance(obj, list) else (\"(\", \")\")\n        items = [pretty_format(x, colored).replace(\"\\n\", \"\\n\" + space) for x in obj]\n        items_str = f\",\\n{space}\".join(items)\n        return f\"{a}\\n{space}{items_str},\\n{b}\"\n\n    if isinstance(obj, dict):\n        if not obj or all(not is_chz(x) for x in obj.values()):\n            return repr(obj)\n\n        items = []\n        for k, v in obj.items():\n            k_str = pretty_format(k, colored).replace(\"\\n\", \"\\n\" + space)\n            v_str = pretty_format(v, colored).replace(\"\\n\", \"\\n\" + space)\n            items.append(f\"{k_str}: {v_str}\")\n        items_str = f\",\\n{space}\".join(items)\n        return f\"{{\\n{space}{items_str},\\n}}\"\n\n    if not is_chz(obj):\n        return repr(obj)\n\n    cls_name = obj.__class__.__qualname__\n    out = f\"{bold}{cls_name}({reset}\\n\"\n\n    def field_repr(field: Field) -> str:\n        # use x_name so that repr can be copy-pasted to create the same object\n        if field._repr is False:\n            return \"...\"\n        if callable(field._repr):\n            r = field._repr\n        else:\n            assert field._repr is True\n            r = lambda o: pretty_format(o, colored=colored)\n\n        x_val = getattr(obj, field.x_name)\n        val = getattr(obj, field.logical_name)\n        if x_val is val:\n            return r(val)\n        return f\"{grey}{r(x_val)}  # {reset}{r(val)}{grey} (after init){reset}\"\n\n    field_reprs: dict[bool, list[str]] = {True: [], False: []}\n    for field in sorted(obj.__chz_fields__.values(), key=lambda f: f.logical_name):\n        if field._default is not MISSING:\n            matches_default = field._default is getattr(obj, field.x_name)\n        elif field._default_factory is not MISSING:\n            matches_default = field._default_factory() == getattr(obj, field.x_name)\n        else:\n            matches_default = False\n\n        val_str = field_repr(field).replace(\"\\n\", \"\\n\" + space)\n        field_str = f\"{space}{blue}{field.logical_name}={reset}{val_str},\\n\"\n        field_reprs[matches_default].append(field_str)\n\n    out += \"\".join(field_reprs[False])\n    if field_reprs[True]:\n        out += f\"{space}{bold}# Fields where pre-init value matches default:{reset}\\n\"\n        out += \"\".join(field_reprs[True])\n    out += f\"{bold}){reset}\"\n    return out\n\n\ndef _repr_pretty_(self, p, cycle: bool) -> None:\n    # for nice ipython printing\n    p.text(pretty_format(self))\n\n\ndef __chz_pretty__(self, colored: bool = True) -> str:\n    \"\"\"Print a chz object for human readability.\"\"\"\n    return pretty_format(self, colored=colored)\n\n\n# ==============================\n# Construction\n# ==============================\n\n\ndef _is_classvar_annotation(annot: str | Any) -> bool:\n    if isinstance(annot, str):\n        # TODO: use better dataclass logic?\n        return annot.startswith((\"typing.ClassVar[\", \"ClassVar[\"))\n    return annot is typing.ClassVar or (\n        type(annot) is typing._GenericAlias  # type: ignore[attr-defined]\n        and annot.__origin__ is typing.ClassVar\n    )\n\n\ndef _is_property_like(obj: Any) -> bool:\n    # TODO: the semantics implied here could be more crisply defined and maybe generalised to\n    # more descriptors\n    return isinstance(obj, (property, init_property, functools.cached_property))\n\n\ndef chz_make_class(cls, version: str | None, typecheck: bool | None) -> type:\n    if cls.__class__ is not type:\n        if cls.__class__ is typing._ProtocolMeta:\n            if typing_extensions.is_protocol(cls):\n                raise TypeError(\"chz class cannot itself be a Protocol)\")\n        else:\n            import abc\n\n            if cls.__class__ is not abc.ABCMeta:\n                raise TypeError(\"Cannot use custom metaclass\")\n\n    user_module = cls.__module__\n    cls_annotations = typing_extensions.get_annotations(cls)\n\n    fields: dict[str, Field] = {}\n\n    # Collect fields from parent classes\n    for b in reversed(cls.__mro__):\n        if hasattr(b, \"__dataclass_fields__\"):\n            raise ValueError(\"Cannot mix chz with dataclasses\")\n\n        # Only process classes that have been processed by our decorator\n        base_fields: dict[str, Field] | None = getattr(b, \"__chz_fields__\", None)\n        if base_fields is None:\n            continue\n        for f in base_fields.values():\n            if (\n                f.logical_name in cls.__dict__\n                and f.logical_name not in cls_annotations\n                and not _is_property_like(getattr(cls, f.logical_name))\n            ):\n                # Do an LSP check against parent fields (for non-property-like members)\n                raise ValueError(\n                    f\"Cannot override field {f.logical_name!r} with a non-field member; \"\n                    f\"maybe you're missing a type annotation?\"\n                )\n            else:\n                fields[f.logical_name] = f\n\n    # Collect fields from the current class\n    for name, annotation in cls_annotations.items():\n        if _is_classvar_annotation(annotation):\n            continue\n\n        # Find the field specification from the class __dict__\n        value = cls.__dict__.get(name, MISSING)\n        if value is MISSING:\n            field = Field(name=name, raw_type=annotation)\n        elif isinstance(value, Field):\n            field = value\n            field._name = name\n            field._raw_type = annotation\n            delattr(cls, name)\n        else:\n            if _is_property_like(value) or (\n                isinstance(value, types.FunctionType)\n                and value.__name__ != \"<lambda>\"\n                and value.__qualname__.startswith(cls.__qualname__)\n            ):\n                # It's problematic to redefine the field in the same class, because it means we\n                # lose any field specification or default value\n                raise ValueError(f\"Field {name!r} is clobbered by {type_repr(type(value))}\")\n            field = Field(name=name, raw_type=annotation, default=value)\n            delattr(cls, name)\n        field._user_module = user_module\n\n        # Do a basic LSP check for new fields\n        parent_value = getattr(cls, name, MISSING)  # note the delattr above\n        if parent_value is not MISSING and not (\n            field.logical_name in fields and isinstance(parent_value, init_property)\n        ):\n            raise ValueError(\n                f\"Cannot define field {name!r} because it conflicts with something defined on a \"\n                f\"superclass: {parent_value!r}\"\n            )\n        other_name = field.logical_name if name != field.logical_name else field.x_name\n        parent_value = getattr(cls, other_name, MISSING)\n        if (\n            parent_value is not MISSING\n            and not (field.logical_name in fields and isinstance(parent_value, init_property))\n            and other_name not in cls.__dict__\n        ):\n            raise ValueError(\n                f\"Cannot define field {name!r} because it conflicts with something defined on a \"\n                f\"superclass: {parent_value!r}\"\n            )\n\n        if (\n            name == field.logical_name\n            and name not in cls.__dict__\n            and name in fields\n            and fields[name]._name != name\n        ):\n            raise ValueError(\n                \"I'm a little unsure of what the semantics should be here. \"\n                \"See test_conflicting_superclass_x_field_in_base. \"\n                \"Please let @shantanu know if you hit this. \"\n                f\"You can also just rename the field in the subclass to X_{name}.\"\n            )\n\n        # Create a default init_property for the field that accesses the raw X_ field\n        munger: Any = field.get_munger()\n        if munger is not None:\n            if field.logical_name in cls.__dict__:\n                raise ValueError(\n                    f\"Cannot define {field.logical_name!r} in class when the associated field \"\n                    f\"has a munger\"\n                )\n            munger.__name__ = field.logical_name\n            munger = init_property(munger)\n            munger.__set_name__(cls, field.logical_name)\n            setattr(cls, field.logical_name, munger)\n        if (\n            # but don't clobber existing definitions...\n            field.logical_name not in cls.__dict__  # ...if something is already there in class\n            and field.logical_name not in fields  # ...if a parent has defined the field\n        ):\n            fn: Any = lambda self, x_name=field.x_name: getattr(self, x_name)\n            fn.__name__ = field.logical_name\n            fn = init_property(fn)\n            fn.__set_name__(cls, field.logical_name)\n            setattr(cls, field.logical_name, fn)\n\n        fields[field.logical_name] = field\n\n    for name, value in cls.__dict__.items():\n        if isinstance(value, Field) and name not in cls_annotations:\n            raise TypeError(f\"{name!r} has no type annotation\")\n\n    # Mark the class as having been processed by our decorator\n    cls.__chz_fields__ = fields\n\n    if \"__init__\" in cls.__dict__:\n        raise ValueError(\"Cannot define __init__ on a chz class. \" + _INIT_ALTERNATIVES)\n    if \"__post_init__\" in cls.__dict__:\n        raise ValueError(\"Cannot define __post_init__ on a chz class. \" + _INIT_ALTERNATIVES)\n    cls.__init__ = _synthesise_init(fields.values(), sys.modules[user_module].__dict__)\n    cls.__init__.__qualname__ = f\"{cls.__qualname__}.__init__\"\n\n    cls.__chz_validate__ = __chz_validate__\n    cls.__chz_init_property__ = __chz_init_property__\n\n    if \"__setattr__\" in cls.__dict__:\n        raise ValueError(\"Cannot define __setattr__ on a chz class\")\n    cls.__setattr__ = __setattr__\n    if \"__delattr__\" in cls.__dict__:\n        raise ValueError(\"Cannot define __delattr__ on a chz class\")\n    cls.__delattr__ = __delattr__\n\n    if \"__repr__\" not in cls.__dict__:\n        cls.__repr__ = __repr__\n    if \"__eq__\" not in cls.__dict__:\n        cls.__eq__ = __eq__\n\n    if \"__hash__\" not in cls.__dict__:\n        cls.__hash__ = __hash__\n\n    if \"_repr_pretty_\" not in cls.__dict__:\n        # Special-cased by IPython\n        cls._repr_pretty_ = _repr_pretty_\n    if \"__chz_pretty__\" not in cls.__dict__:\n        cls.__chz_pretty__ = __chz_pretty__\n\n    if version is not None:\n        import json\n\n        # Hash all the fields and check the version matches\n        expected_version = version.split(\"-\")[0]\n        key = [f.versioning_key() for f in sorted(fields.values(), key=lambda f: f.x_name)]\n        key_bytes = json.dumps(key, separators=(\",\", \":\")).encode()\n        actual_version = hashlib.sha1(key_bytes).hexdigest()[:8]\n        if actual_version != expected_version:\n            raise ValueError(f\"Version {version!r} does not match {actual_version!r}\")\n\n    if typecheck is not None:\n        import chz.validators as chzval\n\n        if typecheck:\n            chzval._ensure_chz_validators(cls)\n            if chzval._decorator_typecheck not in cls.__chz_validators__:\n                cls.__chz_validators__.append(chzval._decorator_typecheck)\n        else:\n            if chzval._decorator_typecheck in getattr(cls, \"__chz_validators__\", []):\n                raise ValueError(\"Cannot disable typecheck; all validators are inherited\")\n\n    return cls\n\n\n# ==============================\n# is_chz\n# ==============================\n\n\ndef is_chz(c: object) -> bool:\n    \"\"\"Check if an object is a chz object.\"\"\"\n    return hasattr(c, \"__chz_fields__\")\n\n\n# ==============================\n# __chz_fields__\n# ==============================\n\n\ndef chz_fields(c: object) -> dict[str, Field]:\n    return c.__chz_fields__  # type: ignore[attr-defined]\n\n\n# ==============================\n# replace\n# ==============================\n\n\ndef replace(obj: _T, /, **changes) -> _T:\n    \"\"\"Return a new object replacing specified fields with new values.\n\n    Example:\n    ```\n    @chz.chz\n    class Foo:\n        a: int\n        b: str\n\n    foo = Foo(a=1, b=\"hello\")\n    assert chz.replace(foo, a=101) == Foo(a=101, b=\"hello\")\n    ```\n\n    This just constructs a new object, so for example, the generated `__init__` gets run and\n    validation will work exactly as if you manually constructed the new object.\n    \"\"\"\n    if not hasattr(obj, \"__chz_fields__\"):\n        raise ValueError(f\"{obj} is not a chz object\")\n\n    for field in obj.__chz_fields__.values():\n        if field.logical_name not in changes:\n            changes[field.logical_name] = getattr(obj, field.x_name)\n    return obj.__class__(**changes)\n\n\n# ==============================\n# asdict\n# ==============================\n\n\ndef asdict(\n    obj: object,\n    shallow: bool = False,\n    include_type: bool = False,\n    exclude: Collection[str] | None = None,\n) -> dict[str, Any]:\n    \"\"\"Recursively convert a chz object to a dict.\n\n    This works similarly to dataclasses.asdict. Note no computed properties will be included\n    in the output.\n\n    See also: beta_to_blueprint_values\n\n    Args:\n    - shallow: if True, only take shallow copies of inner values. Otherwise,\n        deep copies are made.\n    - include_type: If True, include the type of the object in the output dict for each\n        chz object. Useful for debugging and identity.\n    - exclude: Iterable of field names to omit from the resulting dict at the top level.\n    \"\"\"\n\n    exclude_set = set(exclude) if exclude is not None else None\n\n    def inner(x: Any, current_exclude: Collection[str] | None = None):\n        if hasattr(x, \"__chz_fields__\"):\n            result = {\n                k: inner(getattr(x, k))\n                for k in x.__chz_fields__\n                if not current_exclude or k not in current_exclude\n            }\n            if include_type:\n                result[\"__chz_type__\"] = type_repr(type(x))\n            return result\n        if isinstance(x, dict):\n            return {k: inner(v) for k, v in x.items()}\n        if isinstance(x, list):\n            return [inner(x) for x in x]\n        if isinstance(x, tuple):\n            return tuple(inner(x) for x in x)\n        if shallow:\n            return x\n        else:\n            return copy.deepcopy(x)\n\n    if not hasattr(obj, \"__chz_fields__\"):\n        raise RuntimeError(f\"{obj} is not a chz object\")\n\n    result = inner(obj, exclude_set)\n    assert type(result) is dict\n    return result\n\n\ndef traverse(obj: Any, obj_path: str = \"\") -> Iterable[tuple[str, Any]]:\n    \"\"\"Traverses the chz object and yields (path, value) pairs for all sub attributes recursively.\"\"\"\n    assert is_chz(obj)\n\n    yield obj_path, obj\n\n    for f in obj.__chz_fields__.values():\n        value = getattr(obj, f.logical_name)\n        field_path = f\"{obj_path}.{f.logical_name}\" if obj_path else f.logical_name\n\n        yield field_path, value\n\n        if is_chz(value):\n            yield from traverse(value, field_path)\n        if isinstance(value, Mapping):\n            for k, v in value.items():\n                if is_chz(v):\n                    yield from traverse(v, f\"{field_path}.{k}\")\n                else:\n                    yield f\"{field_path}.{k}\", v\n        elif isinstance(value, (list, tuple)):\n            for i, v in enumerate(value):\n                if is_chz(v):\n                    yield from traverse(v, f\"{field_path}.{i}\")\n                else:\n                    yield f\"{field_path}.{i}\", v\n\n\n# ==============================\n# beta_to_blueprint_values\n# ==============================\n\n\ndef beta_to_blueprint_values(obj, skip_defaults: bool = False) -> Any:\n    \"\"\"Return a dict which can be used to recreate the same object via blueprint.\n\n    Args:\n    - obj: The object to convert to blueprint values.\n    - skip_defaults: If True, fields whose values are equal to their default values will be\n        omitted from the output dict. If False (default), all fields are included.\n\n    Example:\n    ```\n    @chz.chz\n    class Foo:\n        a: int\n        b: str\n\n    foo = Foo(a=1, b=\"hello\")\n    assert chz.Blueprint(Foo).apply(chz.beta_to_blueprint_values(foo)).make() == foo\n    ```\n\n    See also: asdict\n    \"\"\"\n    blueprint_values = {}\n\n    def join_arg_path(parent: str, child: str) -> str:\n        if not parent:\n            return child\n        if child.startswith(\".\"):\n            return parent + child\n        return parent + \".\" + child\n\n    def inner(obj: Any, path: str):\n        if hasattr(obj, \"__chz_fields__\"):\n            for field_name, field_info in obj.__chz_fields__.items():\n                value = getattr(obj, field_info.x_name)\n                if skip_defaults and field_info._default == value:\n                    continue\n                param_path = join_arg_path(path, field_name)\n                if field_info.meta_factory is not None and (\n                    type(value)\n                    is not typing.get_origin(field_info.meta_factory.unspecified_factory())\n                ):\n                    # Try to detect when we have used polymorphic construction\n                    blueprint_values[param_path] = type(value)\n\n                inner(value, param_path)\n\n        elif (\n            isinstance(obj, dict)\n            and all(isinstance(k, str) for k in obj.keys())\n            and any(is_chz(v) for v in obj.values())\n        ):\n            for k, v in obj.items():\n                param_path = join_arg_path(path, k)\n                blueprint_values[param_path] = type(v)  # may be overridden if not needed\n                inner(v, param_path)\n\n        elif isinstance(obj, (list, tuple)) and any(is_chz(v) for v in obj):\n            for i, v in enumerate(obj):\n                param_path = join_arg_path(path, str(i))\n                blueprint_values[param_path] = type(v)  # may be overridden if not needed\n                inner(v, param_path)\n\n        else:\n            blueprint_values[path] = obj\n\n    inner(obj, \"\")\n\n    return blueprint_values\n\n\n# ==============================\n# init_property\n# ==============================\n\nif TYPE_CHECKING:\n    init_property = functools.cached_property\nelse:\n\n    class init_property:\n        # Simplified and pickleable version of Python 3.8's cached_property\n        # It's important that this remains a non-data descriptor\n\n        def __init__(self, func: Callable[..., Any]) -> None:\n            self.func = func\n            self.name = None\n\n        def __set_name__(self, owner, name):\n            self.name = name\n            # Basically just validation\n            func_name = self.func.__name__\n            if (\n                name != func_name\n                and func_name != \"<lambda>\"\n                # TODO: remove figure out why mini needs name mangling\n                and not func_name.endswith(\"__register_chz_has_state\")\n            ):\n                raise ValueError(\"Are you doing something weird with init_property?\")\n\n        def __get__(self, obj: Any, cls: Any) -> Any:\n            if obj is None:\n                return self\n            ret = self.func(obj)\n            if self.name is not None:\n                obj.__dict__[self.name] = ret\n\n            return ret\n"
  },
  {
    "path": "chz/factories.py",
    "content": "import ast\nimport collections\nimport functools\nimport importlib\nimport re\nimport sys\nimport types\nimport typing\nfrom typing import Any, Callable\n\nimport typing_extensions\n\nfrom chz.tiepin import (\n    CastError,\n    InstantiableType,\n    TypeForm,\n    _simplistic_try_cast,\n    eval_in_context,\n    is_instantiable_type,\n    is_subtype,\n    is_subtype_instance,\n    is_union_type,\n    type_repr,\n)\nfrom chz.util import MISSING, MISSING_TYPE\n\n\nclass MetaFromString(Exception): ...\n\n\nclass MetaFactory:\n    \"\"\"\n    A metafactory represents a set of possible factories, where a factory is a callable that can\n    give us a value of a given type.\n\n    This is the heart of polymorphic construction in chz. The idea is that when instantiating\n    Blueprints, you should be able to not only specify the arguments to whatever is being\n    constructed, but also specify what the thing to be constructed is!\n\n    In other words, when constructing a value, chz lets you specify the factory to produce it,\n    in addition to the arguments to pass to that factory.\n\n    In other other words, many tools will let you construct an X by specifying `...` to feed to\n    `X(...)`. But chz lets you construct an X by specifying both callee and arguments in `...(...)`\n\n    This concept is a little tricky, but it's fairly intuitive when you actually use it.\n    Consider looking at the docstring for `subclass` for a concrete example.\n    \"\"\"\n\n    def __init__(self) -> None:\n        # Set by chz.Field\n        self.field_annotation: TypeForm | MISSING_TYPE = MISSING\n        self.field_module: types.ModuleType | str | MISSING_TYPE = MISSING\n\n    def unspecified_factory(self) -> Callable[..., Any] | None:\n        \"\"\"The default callable to use to get a value of the expected type.\n\n        If this returns None, there is no default. In order to construct a value of the expected\n        type, the user must explicitly specify a factory.\n        \"\"\"\n        raise NotImplementedError\n\n    def from_string(self, factory: str) -> Callable[..., Any]:\n        \"\"\"The callable that best corresponds to `factory`.\"\"\"\n        raise NotImplementedError\n\n    def perform_cast(self, value: str):\n        # TODO: maybe make this default to:\n        # return _simplistic_try_cast(value, default_target)\n        raise NotImplementedError\n\n\nclass subclass(MetaFactory):\n    \"\"\"\n    ATTN: this is soft deprecated, since `chz.factories.standard` is powerful enough to effectively\n    do a superset of this.\n\n    Read the docstring for MetaFactory first.\n\n    ```\n    @chz.chz\n    class Experiment:\n        model: Model\n    ```\n\n    In the above example, we want to construct a value for the model for our experiment.\n    How should we go about making a model?\n\n    The meta_factory we provide is what is meant to answer this question. And in this case, the\n    answer we want is: we should make a model by attempting to instantiate `Model` or some subclass\n    of `Model`.\n\n    This is a common enough answer that chz in fact defaults to it. That is, here chz will\n    set the meta_factory to be `subclass(base_cls=Model, default_cls=Model)` for our model field.\n    See the logic in chz.Field.\n\n    Given `model=Transformer model.n_layers=16 model.d_model=1024`\n    chz will construct `Transformer(n_layers=16, d_model=1024`\n\n    That is, if the user specifies a factory for the model field, e.g. model=\"Transformer\", then\n    the logic in `subclass.from_string` will attempt to find a subclass of `Model` (the `base_cls`)\n    named `Transformer` and instantiate it.\n\n    Given `model.n_layers=16 model.d_model=1024`\n    chz will construct `Model(n_layers=Y, d_model=Z)`\n\n    That is, if the user doesn't specify a factory (maybe they only specify subcomponents, like\n    `model.n_layers=16`), then we will default to trying to instantiate `Model` (the `default_cls`).\n    \"\"\"\n\n    def __init__(\n        self,\n        base_cls: InstantiableType | MISSING_TYPE = MISSING,\n        default_cls: InstantiableType | MISSING_TYPE = MISSING,\n    ) -> None:\n        super().__init__()\n        self._base_cls = base_cls\n        self._default_cls = default_cls\n\n    def __repr__(self) -> str:\n        return f\"subclass(base_cls={self.base_cls!r}, default_cls={self.default_cls!r})\"\n\n    @property\n    def base_cls(self) -> InstantiableType:\n        if isinstance(self._base_cls, MISSING_TYPE):\n            assert not isinstance(self.field_annotation, MISSING_TYPE)\n            if not isinstance(self.field_annotation, InstantiableType):\n                raise RuntimeError(\n                    f\"Must explicitly specify base_cls since {self.field_annotation!r} \"\n                    \"is not an instantiable type\"\n                )\n            return self.field_annotation\n        return self._base_cls\n\n    @property\n    def default_cls(self) -> InstantiableType:\n        if isinstance(self._default_cls, MISSING_TYPE):\n            return self.base_cls\n        return self._default_cls\n\n    def unspecified_factory(self) -> Callable[..., Any]:\n        return self.default_cls  # type: ignore[return-value]\n\n    def from_string(self, factory: str) -> Callable[..., Any]:\n        \"\"\"\n        If factory=module:cls, import module and return cls.\n        If factory=cls, do our best to find a subclass of base_cls named cls.\n        \"\"\"\n        return _find_subclass(factory, self.base_cls)\n\n    def perform_cast(self, value: str):\n        try:\n            return _simplistic_try_cast(value, self.default_cls)\n        except CastError:\n            pass\n        return _simplistic_try_cast(value, self.base_cls)\n\n\nclass function(MetaFactory):\n    def __init__(\n        self,\n        unspecified: Callable[..., Any] | None = None,\n        *,\n        default_module: str | types.ModuleType | None | MISSING_TYPE = MISSING,\n    ) -> None:\n        \"\"\"\n        ATTN: this is soft deprecated, since `chz.factories.standard` is powerful enough to effectively\n        do a superset of this.\n\n        Read the docstring for `MetaFactory` and `subclass` first.\n\n        If you specify `function` as your meta_factory, any function can serve as a factory to\n        construct a value of the expected type.\n\n        ```\n        def wikipedia_text(seed: int) -> Dataset: ...\n\n        @chz.chz\n        class Experiment:\n            dataset: Dataset = field(meta_factory=function())\n        ```\n\n        In the above example, we want to construct a value of type `Dataset` for our experiment.\n        The way we want to do this is by calling some function that returns a `Dataset`.\n\n        Given `dataset=wikipedia_text dataset.seed=217`\n        chz will construct `wikipedia_text(seed=217)`.\n\n        If you use a fully qualified name like `function=module:fn` it's obvious where to find the\n        function. Otherwise, chz looks for an appropriately named function in the module\n        `default_module` (which defaults to the module in which the chz class was defined).\n\n        If you love `wikipedia_text` and you don't wish to explicitly specify\n        `dataset=wikipedia_text` every time, set the `unspecified` argument to be `wikipedia_text`.\n        This way, chz will default to trying to call `wikipedia_text` to instantiate a value of type\n        `Dataset`, instead of erroring because it doesn't know what factory to use to produce a\n        Dataset.\n        \"\"\"\n\n        super().__init__()\n        self.unspecified = unspecified\n        self._default_module = default_module\n\n    def __repr__(self) -> str:\n        return f\"function(unspecified={self.unspecified!r}, default_module={self.default_module!r})\"\n\n    @property\n    def default_module(self) -> types.ModuleType | str | None:\n        if isinstance(self._default_module, MISSING_TYPE):\n            assert not isinstance(self.field_module, MISSING_TYPE)\n            return self.field_module\n        return self._default_module\n\n    def unspecified_factory(self) -> Callable[..., Any] | None:\n        return self.unspecified\n\n    def from_string(self, factory: str) -> Callable[..., Any]:\n        \"\"\"\n        If factory=module:fn, import module and return fn.\n        If factory=fn, look in the default module for a function named fn.\n        \"\"\"\n        if \":\" not in factory:\n            if self.default_module is None:\n                raise MetaFromString(\n                    f\"No module specified in {factory!r} and no default module specified\"\n                )\n            if isinstance(self.default_module, str):\n                module = importlib.import_module(self.default_module)\n            else:\n                module = self.default_module\n            var = factory\n        else:\n            module_name, var = factory.split(\":\", 1)\n            if module_name != \"lambda\" and not module_name.startswith(\"lambda \"):\n                module = _module_from_name(module_name)\n            else:\n                import ast\n\n                if isinstance(self.default_module, str):\n                    eval_ctx = importlib.import_module(self.default_module)\n                elif self.default_module is not None:\n                    eval_ctx = self.default_module\n                else:\n                    eval_ctx = None\n\n                try:\n                    # TODO: add docs for this branch\n                    if isinstance(ast.parse(factory).body[0].value, ast.Lambda):  # type: ignore[attr-defined]\n                        return eval_in_context(factory, eval_ctx)\n                except Exception as e:\n                    raise MetaFromString(\n                        f\"Could not interpret {factory!r} as a function: {e}\"\n                    ) from None\n                raise AssertionError\n\n        return _module_getattr(module, var)\n\n    def perform_cast(self, value: str):\n        assert not isinstance(self.field_annotation, MISSING_TYPE)\n        return _simplistic_try_cast(value, self.field_annotation)\n\n\ndef _module_from_name(name: str) -> types.ModuleType:\n    try:\n        return importlib.import_module(name)\n    except ImportError as e:\n        raise MetaFromString(\n            f\"Could not import module {name!r} ({type(e).__name__}: {e})\"\n        ) from None\n\n\ndef _module_getattr(mod: types.ModuleType, attr: str) -> Any:\n    try:\n        for a in attr.split(\".\"):\n            mod = getattr(mod, a)\n        return mod\n    except AttributeError as e:\n        raise MetaFromString(str(e)) from None\n\n\ndef _find_subclass(spec: str, superclass: TypeForm):\n    module_name = None\n    if \":\" in spec:\n        module_name, var = spec.split(\":\", 1)\n    else:\n        var = spec\n\n    match = re.fullmatch(r\"(?P<base>[^\\s\\[\\]]+)(\\[(?P<generic>.+)\\])?\", var)\n    if match is None:\n        raise MetaFromString(f\"Failed to parse '{spec}' as a class name\")\n    base = match.group(\"base\")\n    generic = match.group(\"generic\")\n\n    if module_name is None and not base.isidentifier():\n        if \".\" in base:\n            # This effectively adds some basic support for module.symbol, not just module:symbol\n            module_name, base = base.rsplit(\".\", 1)\n        if not base.isidentifier():\n            raise MetaFromString(\n                f\"No subclass of {type_repr(superclass)} named {base!r} (invalid identifier)\"\n            )\n\n    if module_name is not None:\n        module = _module_from_name(module_name)\n        # TODO: think about this type ignore\n        value = _maybe_generic(\n            _module_getattr(module, base),\n            generic,\n            template=superclass,  # type: ignore[arg-type]\n        )\n        if is_subtype(value, superclass):\n            return value\n        raise MetaFromString(\n            f\"Expected a subtype of {type_repr(superclass)}, got {type_repr(value)}\"\n        )\n\n    superclass_class_origin = getattr(superclass, \"__origin__\", superclass)\n    if superclass_class_origin in {object, typing.Any, typing_extensions.Any}:\n        try:\n            return _maybe_generic(\n                _module_getattr(_module_from_name(\"__main__\"), base),\n                generic,\n                template=superclass,  # type: ignore[arg-type]\n            )\n        except MetaFromString:\n            pass\n        try:\n            return _maybe_generic(\n                _module_getattr(_module_from_name(\"builtins\"), base),\n                generic,\n                template=superclass,  # type: ignore[arg-type]\n            )\n        except MetaFromString:\n            pass\n        raise MetaFromString(\n            f\"Could not find {spec!r}, try a fully qualified name e.g. module_name:{spec}\"\n        ) from None\n    if not is_instantiable_type(superclass_class_origin):\n        raise MetaFromString(f\"Could not find subclasses of {type_repr(superclass)}\")\n\n    assert superclass_class_origin is not type\n\n    visited_subclasses = set()\n    all_subclasses = collections.deque(superclass_class_origin.__subclasses__())\n    all_subclasses.appendleft(superclass)\n\n    candidates = []\n    while all_subclasses:\n        cls = all_subclasses.popleft()\n        if cls in visited_subclasses:\n            continue\n        visited_subclasses.add(cls)\n        if cls.__name__ == base:\n            assert module_name is None\n            candidates.append(_maybe_generic(cls, generic, template=superclass))  # type: ignore[arg-type]\n        cls_origin = getattr(cls, \"__origin__\", cls)\n        assert cls_origin is not type\n        all_subclasses.extend(cls_origin.__subclasses__())\n\n    if len(candidates) == 0:\n        raise MetaFromString(f\"No subclass of {type_repr(superclass)} named {base!r}\")\n    if len(candidates) > 1:\n        raise MetaFromString(\n            f\"Multiple subclasses of {type_repr(superclass)} named {base!r}: \"\n            f\"{', '.join(type_repr(c) for c in candidates)}\"\n        )\n    return candidates[0]\n\n\ndef _maybe_generic(\n    cls: type, generic: str | None, template: InstantiableType\n) -> Callable[..., Any]:\n    if generic is None:\n        return cls\n\n    assert isinstance(generic, str)\n    generic_args_str = generic.split(\",\")\n    args: list[object] = []\n    for i, arg_str in enumerate(generic_args_str):\n        arg_str = arg_str.strip()\n        if \":\" in arg_str:\n            module_name, arg = arg_str.split(\":\", 1)\n            args.append(_module_getattr(_module_from_name(module_name), arg))\n        elif arg_str == \"...\":\n            args.append(...)\n        else:\n            # TODO: note this assumes covariance, also give a better error\n            superclass = template.__args__[i]  # type: ignore[union-attr]\n            args.append(_find_subclass(arg_str, superclass))\n\n    origin: Any = getattr(cls, \"__origin__\", cls)\n    return origin[*args]\n\n\ndef _return_prospective(obj: Any, annotation: TypeForm, factory: str) -> Any:\n    if annotation not in {\n        object,\n        typing.Any,\n        typing_extensions.Any,\n    } and not isinstance(annotation, typing.TypeVar):\n        if is_subtype_instance(obj, annotation):\n            # Allow things to be instances!\n            # In some sense, this is just working around deficiencies in casting...\n            return lambda: obj\n    elif not callable(obj):\n        assert is_subtype_instance(obj, annotation)\n        # Also allow things to be instances if we would just error on the next line\n        return lambda: obj\n\n    if not callable(obj):\n        raise MetaFromString(f\"Expected {obj} from {factory!r} to be callable\")\n    if isinstance(obj, type) and not is_subtype(obj, annotation):\n        extra = \"\"\n        if getattr(annotation, \"__module__\", None) == \"__main__\":\n            if any(\n                hasattr(sys.modules[\"__main__\"], (witness := parent).__name__)\n                for parent in obj.__mro__\n            ):\n                extra = f\" (there may be confusion between {type_repr(witness)} and __main__:{witness.__name__})\"\n        raise MetaFromString(\n            f\"Expected {type_repr(obj)} from {factory!r} to be a subtype of {type_repr(annotation)}{extra}\"\n        )\n    return obj\n\n\ndef get_unspecified_from_annotation(annotation: TypeForm) -> Callable[..., Any] | None:\n    if typing.get_origin(annotation) is type:\n        base_type = typing.get_args(annotation)[0]\n        if is_union_type(base_type):\n            # No unspecified for type[SpecialForm] e.g. type[int | str]\n            # TODO: annotated\n            return None\n        return type[base_type]  # type: ignore[return-value]\n\n    if is_union_type(annotation):\n        type_args = typing.get_args(annotation)\n        if type_args and len(type_args) == 2 and type(None) in type_args:\n            unwrapped_optional = [t for t in type_args if t is not type(None)][0]\n            if callable(unwrapped_optional):\n                return unwrapped_optional\n        return None\n\n    if is_instantiable_type(annotation):\n        return annotation  # type: ignore[return-value]\n\n    if annotation is None:\n        return lambda: None\n\n    # Probably a special form\n    return None\n\n\nclass standard(MetaFactory):\n    def __init__(\n        self,\n        *,\n        annotation: TypeForm | MISSING_TYPE = MISSING,\n        unspecified: Callable[..., Any] | None = None,\n        default_module: str | types.ModuleType | None | MISSING_TYPE = MISSING,\n    ) -> None:\n        super().__init__()\n        self._annotation = annotation\n        self.original_unspecified = unspecified\n        self._default_module = default_module\n\n    def __repr__(self) -> str:\n        return f\"standard(annotation={self.annotation!r}, unspecified={self.original_unspecified!r}, default_module={self.default_module!r})\"\n\n    @property\n    def annotation(self) -> TypeForm:\n        if isinstance(self._annotation, MISSING_TYPE):\n            assert not isinstance(self.field_annotation, MISSING_TYPE)\n            return self.field_annotation\n        return self._annotation\n\n    @property\n    def default_module(self) -> types.ModuleType | str | None:\n        if isinstance(self._default_module, MISSING_TYPE):\n            if isinstance(self.field_module, MISSING_TYPE):\n                # TODO: maybe make this assert and make artificial use cases pass a value explicitly\n                return None\n            return self.field_module\n        if isinstance(self._default_module, str):\n            return _module_from_name(self._default_module)\n        return self._default_module\n\n    @functools.cached_property\n    def computed_unspecified(self) -> Callable[..., Any] | None:\n        return (\n            get_unspecified_from_annotation(self.annotation)\n            if self.original_unspecified is None\n            else self.original_unspecified\n        )\n\n    def unspecified_factory(self) -> Callable[..., Any] | None:\n        if (\n            self.computed_unspecified is not None\n            and typing.get_origin(self.computed_unspecified) is type\n            and typing.get_args(self.computed_unspecified)\n        ):\n            base_type = typing.get_args(self.computed_unspecified)[0]\n            # TODO: remove special handling here and elsewhere by moving logic to collect_params\n            return lambda: base_type\n\n        return self.computed_unspecified\n\n    def from_string(self, factory: str) -> Callable[..., Any]:\n        if \":\" in factory:\n            module_name, var = factory.split(\":\", 1)\n\n            # fun lambda case\n            # TODO: add docs for fun lambda case\n            if module_name == \"lambda\" or module_name.startswith(\"lambda \"):\n                default_module = self.default_module\n                if isinstance(default_module, MISSING_TYPE) or default_module is None:\n                    eval_ctx = None\n                else:\n                    eval_ctx = default_module\n\n                try:\n                    if isinstance(ast.parse(factory).body[0].value, ast.Lambda):  # type: ignore[attr-defined]\n                        return eval_in_context(factory, eval_ctx)\n                except Exception as e:\n                    raise MetaFromString(\n                        f\"Could not interpret {factory!r} as a function: {e}\"\n                    ) from None\n                raise AssertionError\n\n            # we've just got something explicitly specified\n            module = _module_from_name(module_name)\n\n            match = re.fullmatch(r\"(?P<base>[^\\s\\[\\]]+)(\\[(?P<generic>.+)\\])?\", var)\n            if match is None:\n                raise MetaFromString(f\"Failed to parse {factory!r} as a class name\")\n            base = match.group(\"base\")\n            generic = match.group(\"generic\")\n\n            # TODO: think about this type ignore\n            typ = _maybe_generic(_module_getattr(module, base), generic, template=self.annotation)  # type: ignore[arg-type]\n            return _return_prospective(typ, self.annotation, factory=factory)\n\n        try:\n            if self.annotation in {object, typing.Any, typing_extensions.Any}:\n                return _find_subclass(factory, self.annotation)\n\n            if typing.get_origin(self.annotation) is type:\n                base_type = typing.get_args(self.annotation)[0]\n                assert isinstance(base_type, type)\n                typ = _find_subclass(factory, base_type)\n                return lambda: typ\n\n            if is_union_type(self.annotation):\n                if self.original_unspecified is not None:\n                    try:\n                        if is_instantiable_type(self.original_unspecified):\n                            return _find_subclass(factory, self.original_unspecified)\n                    except MetaFromString:\n                        pass\n                for t in typing.get_args(self.annotation):\n                    try:\n                        if is_instantiable_type(t):\n                            return _find_subclass(factory, t)\n                    except MetaFromString:\n                        pass\n                if type(None) in typing.get_args(self.annotation) and factory == \"None\":\n                    return lambda: None\n                raise MetaFromString(f\"Could not produce a union instance from {factory!r}\")\n\n            if is_instantiable_type(self.annotation):\n                return _find_subclass(factory, self.annotation)\n\n            if self.annotation is None and factory == \"None\":\n                return lambda: None\n\n        except MetaFromString as e:\n            try:\n                default_module = self.default_module\n                if isinstance(default_module, str):\n                    default_module = _module_from_name(default_module)\n                if default_module is not None:\n                    obj = _module_getattr(default_module, factory)\n                    return _return_prospective(obj, self.annotation, factory=factory)\n\n            except MetaFromString:\n                pass\n\n            raise e\n\n        # Probably a special form\n        raise MetaFromString(\n            f\"Could not produce a {type_repr(self.annotation)} instance from {factory!r}\"\n        )\n\n    def perform_cast(self, value: str):\n        if self.original_unspecified is not None:\n            try:\n                return _simplistic_try_cast(value, self.original_unspecified)\n            except CastError:\n                pass\n        return _simplistic_try_cast(value, self.annotation)\n"
  },
  {
    "path": "chz/field.py",
    "content": "from __future__ import annotations\n\nimport functools\nimport sys\nfrom typing import Any, Callable\n\nimport chz\nfrom chz.mungers import Munger, default_munger\nfrom chz.tiepin import TypeForm\nfrom chz.util import MISSING, MISSING_TYPE\n\n_FieldValidator = Callable[[Any, str], None]\n\n\ndef field(\n    *,\n    # default related\n    default: Any | MISSING_TYPE = MISSING,\n    default_factory: Callable[[], Any] | MISSING_TYPE = MISSING,\n    # munger related\n    munger: Munger | Callable[[Any, Any], Any] | None = None,\n    x_type: TypeForm | MISSING_TYPE = MISSING,\n    converter: Callable[[Any], Any] | None = None,\n    # blueprint related\n    meta_factory: chz.factories.MetaFactory | None | MISSING_TYPE = MISSING,\n    blueprint_unspecified: Callable[..., Any] | MISSING_TYPE = MISSING,\n    blueprint_cast: Callable[[str], object] | None = None,\n    # misc\n    validator: _FieldValidator | (list[_FieldValidator] | None) = None,\n    repr: bool | Callable[[Any], str] = True,\n    doc: str = \"\",\n    metadata: dict[str, Any] | None = None,\n) -> Any:\n    \"\"\"Customise a field in a chz class.\n\n    Args:\n        default: The default value for the field (if any).\n\n        default_factory:\n            A function that returns the default value for the field.\n            Useful for mutable types, for instance, `default_factory=list`.\n\n            This does not interact at all with parametrisation. Perhaps a better name would be\n            lazy_default (but unfortunately, this is not supported by PEP 681, so static type\n            checkers would lose the ability to understand the class).\n\n        munger: Lets you adjust the value of a field. Essentially works the same as\n            an init_property.\n\n        x_type: Useful in combination with mungers. This specifies the type before munging that\n            will be used for parsing and type checking.\n\n        converter: Synonym for munger that works better with static type checkers. It accepts\n            a munger object or a callable that will be called as fn(value, chzself=chzself).\n\n        meta_factory:\n            Represents the set of possible callables that can give us a value of a given type.\n\n        blueprint_unspecified:\n            Used to construct the meta_factory, if meta_factory is unspecified. This is the\n            default callable `Blueprint` may attempt to call to get a value of the expected type.\n            See the documentation in chz.factories for more information.\n\n            In particular, the following two are equivalent:\n            ```\n            x: Base = field(blueprint_unspecified=Sub)\n            x: Base = field(meta_factory=chz.factories.subclass(Base, default_cls=Sub))\n            ```\n\n        blueprint_cast: A function that takes a str and returns an object. On failure to cast,\n            it should raise `CastError`. Used to achieve custom parsing behaviour from the command\n            line. Takes priority over the `__chz_cast__` dunder method (if present on the\n            target type).\n\n        validator: A function or list of functions that validate the field.\n            Field validators take two arguments: the instance of the class\n            and the name of the field.\n\n        repr: Whether to include the field in the `__repr__` of the class. This can also be a\n            callable to customise the repr of the field.\n\n        doc: The docstring for the field. Used in `--help`.\n\n        metadata: Arbitrary user-defined metadata to attach to the field.\n            Useful when extending `chz`.\n    \"\"\"\n    return Field(\n        name=\"\",\n        raw_type=\"\",\n        default=default,\n        default_factory=default_factory,\n        munger=munger,\n        raw_x_type=x_type,\n        converter=converter,\n        meta_factory=meta_factory,\n        blueprint_unspecified=blueprint_unspecified,\n        blueprint_cast=blueprint_cast,\n        validator=validator,\n        repr=repr,\n        doc=doc,\n        metadata=metadata,\n    )\n\n\nclass Field:\n    def __init__(\n        self,\n        *,\n        name: str,\n        raw_type: TypeForm | str,\n        default: Any = MISSING,\n        default_factory: Callable[[], Any] | MISSING_TYPE = MISSING,\n        munger: Munger | Callable[[Any, Any], Any] | None = None,\n        raw_x_type: TypeForm | MISSING_TYPE = MISSING,\n        converter: Callable[[Any], Any] | None = None,\n        meta_factory: chz.factories.MetaFactory | None | MISSING_TYPE = MISSING,\n        blueprint_unspecified: Callable[..., Any] | MISSING_TYPE = MISSING,\n        blueprint_cast: Callable[[str], object] | None = None,\n        validator: _FieldValidator | (list[_FieldValidator] | None) = None,\n        repr: bool | Callable[[Any], str] = True,\n        doc: str = \"\",\n        metadata: dict[str, Any] | None = None,\n    ):\n        if default.__class__.__hash__ is None:\n            raise ValueError(\n                f\"Mutable default {type(default)} for field \"\n                f\"{name} is not allowed: use default_factory\"\n            )\n\n        if (\n            meta_factory is not MISSING\n            and meta_factory is not None\n            and not isinstance(meta_factory, chz.factories.MetaFactory)\n        ):\n            raise TypeError(f\"meta_factory must be a MetaFactory, not {type(meta_factory)}\")\n\n        if blueprint_unspecified is not MISSING:\n            if not callable(blueprint_unspecified):\n                raise TypeError(\n                    f\"blueprint_unspecified must be callable, not {type(blueprint_unspecified)}\"\n                )\n            if meta_factory is not MISSING:\n                raise ValueError(\"Cannot specify both meta_factory and blueprint_unspecified\")\n\n        if default_factory is not MISSING:\n            if not callable(default_factory):\n                raise TypeError(f\"default_factory must be callable, not {type(default_factory)}\")\n            if isinstance(default_factory, chz.factories.MetaFactory):\n                raise TypeError(\n                    \"default_factory must be a callable that returns a value, \"\n                    \"not a MetaFactory. Note that default_factory must be callable without any \"\n                    \"arguments and does not interact with parametrisation.\"\n                )\n\n        if converter is not None:\n            if munger is not None:\n                raise ValueError(\"Cannot specify both converter and munger\")\n            if not callable(converter):\n                raise TypeError(f\"converter must be callable, not {type(converter)}\")\n            if isinstance(converter, Munger):\n                munger = converter\n            else:\n                # Note: when the munger arg is a function, it is called as munger(chzself, value),\n                # but converters must be defined with the value as the only positional parameter,\n                # and so they are called as converter(value, chzself=chzself).\n                # TODO: change the signature of functions passed to the `munger` argument to be\n                # compatible with `converter`?\n                c = converter\n                munger = lambda s, v: c(v, chzself=s)  # type: ignore\n\n        if munger is not None and not callable(munger):\n            raise TypeError(f\"munger must be callable, not {type(munger)}\")\n\n        if validator is None:\n            validator = []\n        elif not isinstance(validator, list):\n            validator = [validator]\n\n        self._name = name\n        self._raw_type = raw_type\n        self._raw_x_type = raw_x_type\n        self._default = default\n        self._default_factory = default_factory\n        self._meta_factory = meta_factory\n        self._blueprint_unspecified = blueprint_unspecified\n        self._munger = munger\n        self._validator: list[_FieldValidator] = validator\n        self._blueprint_cast = blueprint_cast\n        self._repr = repr\n        self._doc = doc\n        self._metadata = metadata\n\n        # We used to pass the actual globals around, but cloudpickle did not like that\n        # when it tried to pickle chz classes by value in __main__\n        # Note that this means that if we're using postponed annotations or quoted annotations\n        # in __main__ that self.type will likely fail if this is ever pickled and unpickled\n        self._user_module: str = \"\"\n\n    @property\n    def logical_name(self) -> str:\n        for magic_prefix in (\"隐\", \"_X_\"):\n            if self._name.startswith(magic_prefix):\n                raise RuntimeError(f\"Magic prefix {magic_prefix} no longer supported, use X_\")\n        if self._name.startswith(\"X_\"):\n            return self._name.removeprefix(\"X_\")\n        return self._name\n\n    @property\n    def x_name(self) -> str:\n        return \"X_\" + self.logical_name\n\n    @functools.cached_property\n    def final_type(self) -> TypeForm:\n        if not self._name:\n            raise RuntimeError(\n                \"Something has gone horribly awry; are you using a chz.Field in a dataclass?\"\n            )\n        # Delay the eval until after the class\n        if isinstance(self._raw_type, str):\n            # TODO: handle forward ref\n            assert self._user_module\n            if self._user_module not in sys.modules:\n                raise RuntimeError(\n                    f\"Could not find module {self._user_module}; possibly a pickling issue?\"\n                )\n            user_globals = sys.modules[self._user_module].__dict__\n            return eval(self._raw_type, user_globals)\n        return self._raw_type\n\n    @functools.cached_property\n    def x_type(self) -> TypeForm:\n        if isinstance(self._raw_x_type, MISSING_TYPE):\n            return self.final_type\n        return self._raw_x_type\n\n    @property\n    def meta_factory(self) -> chz.factories.MetaFactory | None:\n        if self._meta_factory is None:\n            return None\n\n        if isinstance(self._meta_factory, MISSING_TYPE):\n            if isinstance(self._blueprint_unspecified, MISSING_TYPE):\n                unspec = None\n            else:\n                unspec = self._blueprint_unspecified\n\n            import chz.factories\n\n            ret = chz.factories.standard(\n                annotation=self.x_type, unspecified=unspec, default_module=self._user_module\n            )\n            ret.field_annotation = self.x_type\n            ret.field_module = self._user_module\n            return ret\n\n        self._meta_factory.field_annotation = self.x_type\n        self._meta_factory.field_module = self._user_module\n        return self._meta_factory\n\n    def get_munger(self) -> Callable[[Any], None] | None:\n        if self._munger is None:\n            return None\n\n        if isinstance(self._munger, Munger):\n            m = self._munger\n        else:\n            assert callable(self._munger)\n            m = default_munger(self._munger)\n\n        # Must return a new callable every time\n        return lambda chzself: m(getattr(chzself, self.x_name), chzself=chzself, field=self)\n\n    @property\n    def metadata(self) -> dict[str, Any] | None:\n        return self._metadata\n\n    def __repr__(self):\n        return f\"Field(name={self._name!r}, type={self.final_type!r}, ...)\"\n\n    def versioning_key(self) -> tuple[str, ...]:\n        from chz.tiepin import approx_type_hash\n\n        raw_type_key = approx_type_hash(self._raw_type)\n\n        if self._default is MISSING:\n            default_key = \"\"\n        elif self._default.__repr__ is not object.__repr__:\n            default_key = repr(self._default)\n        else:\n            default_key = self._default_factory.__class__.__name__\n\n        if isinstance(self._default_factory, MISSING_TYPE):\n            default_factory_key = \"\"\n        else:\n            # TODO: support lambdas\n            default_factory_key = (\n                self._default_factory.__module__ + \".\" + self._default_factory.__name__\n            )\n        return (self._name, raw_type_key, default_key, default_factory_key)\n"
  },
  {
    "path": "chz/mungers.py",
    "content": "from __future__ import annotations\n\nfrom typing import TYPE_CHECKING, Any, Callable, Mapping, TypeVar, overload\n\nif TYPE_CHECKING:\n    from frozendict import frozendict\n\n    from chz.field import Field\n\n\n_T = TypeVar(\"_T\")\n_K = TypeVar(\"_K\")\n_V = TypeVar(\"_V\")\n\n\nclass Munger:\n    \"\"\"Marker class for mungers\"\"\"\n\n    def __call__(self, value: Any, *, chzself: Any = None, field: Field | None = None) -> Any:\n        raise NotImplementedError\n\n\nclass if_none(Munger):\n    \"\"\"If None, munge the field to the result of an arbitrary function of the chz object.\"\"\"\n\n    def __init__(self, replacement: Callable[[Any], Any]):\n        self.replacement = replacement\n\n    def __call__(self, value: _T | None, *, chzself: Any = None, field: Field | None = None) -> _T:\n        if value is not None:\n            return value\n        return self.replacement(chzself)\n\n\nclass attr_if_none(Munger):\n    \"\"\"If None, munge the field to another attribute of the chz object.\"\"\"\n\n    def __init__(self, replacement_attr: str):\n        self.replacement_attr = replacement_attr\n\n    def __call__(self, value: _T | None, *, chzself: Any = None, field: Field | None = None) -> _T:\n        if value is not None:\n            return value\n        return getattr(chzself, self.replacement_attr)\n\n\nclass default_munger(Munger):\n    def __init__(self, fn: Callable[[Any, Any], Any]):\n        self.fn = fn\n\n    def __call__(self, value: Any, *, chzself: Any = None, field: Field | None = None) -> Any:\n        # Note: when the munger arg is a function, it is called as munger(chzself, value),\n        # and we keep that calling convention here. See also the comment in Field.__init__.\n        return self.fn(chzself, value)\n\n\nclass freeze_dict(Munger):\n    \"\"\"Freezes a dictionary value so the object is hashable.\"\"\"\n\n    @overload\n    def __call__(\n        self, value: Mapping[_K, _V], *, chzself: Any = None, field: Field | None = None\n    ) -> frozendict[_K, _V]: ...\n\n    @overload\n    def __call__(\n        self, value: Mapping[_K, _V] | None, *, chzself: Any = None, field: Field | None = None\n    ) -> frozendict[_K, _V] | None: ...\n\n    def __call__(\n        self, value: Mapping[_K, _V] | None, *, chzself: Any = None, field: Field | None = None\n    ) -> frozendict[_K, _V] | None:\n        from frozendict import frozendict\n\n        if value is not None and not isinstance(value, frozendict):\n            return frozendict[_K, _V](value)  # pyright: ignore[reportUnknownArgumentType]\n        return value\n"
  },
  {
    "path": "chz/py.typed",
    "content": ""
  },
  {
    "path": "chz/tiepin.py",
    "content": "\"\"\"\n\nIt's a fair question why this module exists, instead of using something third party.\n\nThere are two things I would have liked to farm out: 1) is_subtype_instance, 2) _simplistic_try_cast.\n\nFor is_subtype_instance, I would have liked to use `typeguard`. Unfortunately, the `typeguard`\nversion we were on did not support a lot of basic things. We couldn't upgrade either, because the\nnew version had breaking changes and more importantly created ref cycles in places that caused us\nto hold on to GPU tensors for longer than we should have, causing GPU OOMs. Update: I eventually\ngot this fixed upstream.\n\nFor _simplistic_try_cast, despite its name, seems to work better than most things out there for our\nuse case. This is also nice to be able to customise for chz's purposes.\n\nI also have another motivation, which is by writing my own Python runtime type checker, I'll\nbecome a better maintainer of typing.py / typing_extensions.py upstream.\n\n\"\"\"\n\nimport ast\nimport collections.abc\nimport functools\nimport hashlib\nimport importlib\nimport inspect\nimport operator\nimport sys\nimport types\nimport typing\n\nimport typing_extensions\n\n\ndef type_repr(typ) -> str:\n    # Similar to typing._type_repr\n    if isinstance(typ, (types.GenericAlias, typing._GenericAlias)):\n        if typ.__origin__.__module__ in {\"typing\", \"typing_extensions\", \"collections.abc\"}:\n            if typ.__origin__ is collections.abc.Callable:\n                return repr(typ).removeprefix(\"collections.abc.\").removeprefix(\"typing.\")\n\n            # Based on typing._GenericAlias.__repr__\n            name = typ.__origin__.__name__\n            if typ.__args__:\n                args = \", \".join([type_repr(a) for a in typ.__args__])\n            else:\n                args = \"()\"\n            return f\"{name}[{args}]\"\n\n        return repr(typ)\n\n    if isinstance(typ, (type, types.FunctionType)):\n        module = getattr(typ, \"__module__\", None)\n        name = getattr(typ, \"__qualname__\", None)\n        if name is None:\n            name = getattr(typ, \"__name__\", None)\n        if name is not None:\n            if module == \"typing\":\n                return f\"{module}.{name}\"\n            if module is not None and module != \"builtins\" and module != \"__main__\":\n                return f\"{module}:{name}\"\n            return name\n\n    if typ is ...:\n        return \"...\"\n    return repr(typ)\n\n\ndef _approx_type_to_bytes(t) -> bytes:\n    # This tries to keep the resulting value similar with and without __future__ annotations\n    # As a result, the conversion is approximate. For instance, `builtins.float` and\n    # `class float: ...` will look the same.\n    # If you need something more discerning, maybe just use pickle? Although note that pickle\n    # doesn't work on at least forward refs and non-module level typevars\n    origin = getattr(t, \"__origin__\", None)\n    args = getattr(t, \"__args__\", ())\n\n    if origin is None:\n        if isinstance(t, type):\n            # don't use t.__module__, so that we're more likely to preserve hashes\n            # with and without future annotations\n            origin_bytes = t.__name__.encode(\"utf-8\")\n        elif isinstance(t, typing._SpecialForm):\n            origin_bytes = t._name.encode(\"utf-8\")\n        elif isinstance(t, typing.TypeVar):\n            origin_bytes = t.__name__.encode(\"utf-8\")\n        elif isinstance(t, typing.ForwardRef):\n            origin_bytes = t.__forward_arg__.encode(\"utf-8\")\n        elif isinstance(t, str):\n            origin_bytes = t.encode(\"utf-8\")\n        elif isinstance(t, (bytes, int, type(...), type(None))):\n            # enums?\n            origin_bytes = repr(t).encode(\"utf-8\")\n        else:\n            raise TypeError(f\"Cannot convert {t} of {type(t)} to bytes\")\n    else:\n        origin_bytes = _approx_type_to_bytes(origin)\n\n    arg_bytes = (b\"[\" + b\",\".join(_approx_type_to_bytes(a) for a in args) + b\"]\") if args else b\"\"\n    return origin_bytes + arg_bytes\n\n\ndef approx_type_hash(t) -> str:\n    return hashlib.sha1(_approx_type_to_bytes(t)).hexdigest()\n\n\ndef eval_in_context(annot: str, obj: object) -> typing.Any:\n    # Based on inspect.get_annotations\n    if isinstance(obj, type):\n        obj_globals = None\n        module_name = getattr(obj, \"__module__\", None)\n        if module_name:\n            module = sys.modules.get(module_name, None)\n            if module:\n                obj_globals = getattr(module, \"__dict__\", None)\n        obj_locals = dict(vars(obj))\n        unwrap = obj\n    elif isinstance(obj, types.ModuleType):\n        obj_globals = getattr(obj, \"__dict__\", None)\n        obj_locals = None\n        unwrap = None\n    elif callable(obj):\n        obj_globals = getattr(obj, \"__globals__\", None)\n        obj_locals = None\n        unwrap = obj\n    elif obj is None:\n        obj_globals = None\n        obj_locals = None\n        unwrap = None\n    else:\n        raise TypeError(f\"{obj!r} is not a module, class, or callable.\")\n\n    if unwrap is not None:\n        while True:\n            if hasattr(unwrap, \"__wrapped__\"):\n                unwrap = unwrap.__wrapped__\n                continue\n            if isinstance(unwrap, functools.partial):\n                unwrap = unwrap.func\n                continue\n            break\n        if hasattr(unwrap, \"__globals__\"):\n            obj_globals = unwrap.__globals__\n\n    assert isinstance(annot, str)\n    return eval(annot, obj_globals, obj_locals)\n\n\ndef maybe_eval_in_context(annot: str, obj: object) -> typing.Any:\n    if isinstance(annot, str):\n        return eval_in_context(annot, obj)\n    if annot is inspect.Parameter.empty:\n        return typing.Any\n    return annot\n\n\nif sys.version_info >= (3, 11):\n    typing_Never = (\n        typing.NoReturn,\n        typing_extensions.NoReturn,\n        typing_extensions.Never,\n        typing.Never,\n    )\nelse:\n    typing_Never = (typing.NoReturn, typing_extensions.NoReturn, typing_extensions.Never)\n\n\nTypeForm = object\nInstantiableType: typing.TypeAlias = type | types.GenericAlias  # | typing._GenericAlias\n\n\ndef is_instantiable_type(t: TypeForm) -> typing.TypeGuard[InstantiableType]:\n    origin = getattr(t, \"__origin__\", t)\n    return isinstance(origin, type) and origin is not type\n\n\ndef is_union_type(t: TypeForm) -> bool:\n    # This has gotten a little messy with Python 3.14\n    origin = getattr(t, \"__origin__\", t)\n    return origin is typing.Union or isinstance(t, types.UnionType) or t is types.UnionType\n\n\ndef is_typed_dict(t: TypeForm) -> bool:\n    return isinstance(t, (typing._TypedDictMeta, typing_extensions._TypedDictMeta))\n\n\nclass CastError(Exception):\n    pass\n\n\ndef _module_from_name(name: str) -> types.ModuleType:\n    try:\n        return importlib.import_module(name)\n    except ImportError as e:\n        raise CastError(f\"Could not import module {name!r} ({type(e).__name__}: {e})\") from None\n\n\ndef _module_getattr(mod: types.ModuleType, attr: str) -> typing.Any:\n    try:\n        for a in attr.split(\".\"):\n            mod = getattr(mod, a)\n        return mod\n    except AttributeError:\n        raise CastError(f\"No attribute named {attr!r} in module {mod.__name__}\") from None\n\n\ndef _sort_for_union_preference(typs: tuple[TypeForm, ...]):\n    def sort_key(typ):\n        typ = getattr(typ, \"__origin__\", typ)\n        if typ is str:\n            # sort str to last, because anything can be cast to str\n            return 1\n        if typ is typing.Literal or typ is typing_extensions.Literal:\n            # sort literals to first, because they exact match\n            return -2\n        if typ is type(None) or typ is None:\n            # None exact matches as well (like all singletons)\n            return -1\n        return 0\n\n    # note this is a stable sort, so we preserve user ordering\n    return sorted(typs, key=sort_key)\n\n\ndef is_args_unpack(t: TypeForm) -> bool:\n    return getattr(t, \"__unpacked__\", False) or getattr(t, \"__origin__\", t) in {\n        typing.Unpack,\n        typing_extensions.Unpack,\n    }\n\n\ndef is_kwargs_unpack(t: TypeForm) -> bool:\n    return getattr(t, \"__origin__\", t) in {typing.Unpack, typing_extensions.Unpack}\n\n\ndef _unpackable_arg_length(t: TypeForm) -> tuple[int, bool]:\n    item_args = None\n    if getattr(t, \"__unpacked__\", False):\n        assert t.__origin__ is tuple  # TODO\n        item_args = t.__args__\n    elif getattr(t, \"__origin__\", t) in {typing.Unpack, typing_extensions.Unpack}:\n        assert len(t.__args__) == 1\n        assert t.__args__[0].__origin__ is tuple\n        item_args = t.__args__[0].__args__\n    else:\n        return (1, False)\n\n    if not item_args or item_args[-1] is ...:\n        assert len(item_args) == 2\n        return (0, True)\n\n    min_length = 0\n    unbounded = False\n    for item_arg in item_args:\n        arg_length, arg_unbounded = _unpackable_arg_length(item_arg)\n        min_length += arg_length\n        unbounded |= arg_unbounded\n    return (min_length, unbounded)\n\n\ndef _cast_unpacked_tuples(\n    inst_items: list[str], args: tuple[TypeForm, ...]\n) -> tuple[typing.Any, ...]:\n    # Cursed PEP 646 stuff\n    arg_lengths = [_unpackable_arg_length(arg) for arg in args]\n    min_length = sum(arg_length for arg_length, _ in arg_lengths)\n\n    if len(inst_items) < min_length:\n        raise CastError(\n            f\"Could not cast {repr(','.join(inst_items))} to {type_repr(tuple[*args])} \"\n            \"because of length mismatch\"\n        )\n\n    ret = []\n    i = 0\n    for arg, (arg_length, arg_unbounded) in zip(args, arg_lengths):\n        if is_args_unpack(arg):\n            if arg_unbounded:\n                arg_length += len(inst_items) - min_length\n                min_length = len(inst_items)\n            if getattr(arg, \"__origin__\", arg) in {typing.Unpack, typing_extensions.Unpack}:\n                assert len(arg.__args__) == 1\n                assert arg.__args__[0].__origin__ is tuple\n                arg = arg.__args__[0]\n\n            arg = arg.__args__\n            if len(arg) == 0:\n                ret.extend(inst_items[i : i + arg_length])\n            elif len(arg) == 2 and arg[-1] is ...:\n                ret.extend(\n                    _cast_unpacked_tuples(inst_items[i : i + arg_length], (arg[0],) * arg_length)\n                )\n            else:\n                ret.extend(_cast_unpacked_tuples(inst_items[i : i + arg_length], arg))\n        else:\n            assert arg_length == 1\n            assert not arg_unbounded\n            ret.append(_simplistic_try_cast(inst_items[i], arg))\n\n        i += arg_length\n    return tuple(ret)\n\n\ndef _simplistic_try_cast(inst_str: str, typ: TypeForm):\n    origin = getattr(typ, \"__origin__\", typ)\n    if is_union_type(origin):\n        # sort str to last spot\n        args = _sort_for_union_preference(getattr(typ, \"__args__\", ()))\n        for arg in args:\n            try:\n                return _simplistic_try_cast(inst_str, arg)\n            except CastError:\n                pass\n        raise CastError(f\"Could not cast {repr(inst_str)} to {type_repr(typ)}\")\n\n    if origin is typing.Any or origin is typing_extensions.Any or origin is object:\n        try:\n            return ast.literal_eval(inst_str)\n        except (ValueError, SyntaxError):\n            pass\n        # Also accept some lowercase spellings\n        if inst_str in {\"true\", \"false\"}:\n            return inst_str == \"true\"\n        if inst_str in {\"none\", \"null\", \"NULL\"}:\n            return None\n        return inst_str\n\n    if isinstance(origin, typing.TypeVar):\n        if origin.__constraints__:\n            for constraint in origin.__constraints__:\n                try:\n                    return _simplistic_try_cast(inst_str, constraint)\n                except CastError:\n                    pass\n            raise CastError(f\"Could not cast {repr(inst_str)} to {type_repr(typ)}\")\n        if origin.__bound__:\n            return _simplistic_try_cast(inst_str, origin.__bound__)\n        return _simplistic_try_cast(inst_str, object)\n\n    if origin is typing.Literal or origin is typing_extensions.Literal:\n        values_by_type = {}\n        for arg in getattr(typ, \"__args__\", ()):\n            values_by_type.setdefault(type(arg), []).append(arg)\n        for literal_typ, literal_values in values_by_type.items():\n            try:\n                value = _simplistic_try_cast(inst_str, literal_typ)\n                if value in literal_values:\n                    return value\n            except CastError:\n                pass\n        raise CastError(f\"Could not cast {repr(inst_str)} to {type_repr(typ)}\")\n\n    if origin is None or origin is type(None):\n        if inst_str == \"None\":\n            return None\n        raise CastError(f\"Could not cast {repr(inst_str)} to {type_repr(typ)}\")\n\n    if origin is bool:\n        if inst_str in {\"t\", \"true\", \"True\", \"1\"}:\n            return True\n        if inst_str in {\"f\", \"false\", \"False\", \"0\"}:\n            return False\n        raise CastError(f\"Could not cast {repr(inst_str)} to {type_repr(typ)}\")\n\n    if origin is str:\n        return inst_str\n\n    if origin is float:\n        try:\n            return float(inst_str)\n        except ValueError as e:\n            raise CastError(f\"Could not cast {repr(inst_str)} to {type_repr(typ)}\") from e\n    if origin is int:\n        try:\n            return int(inst_str)\n        except ValueError as e:\n            raise CastError(f\"Could not cast {repr(inst_str)} to {type_repr(typ)}\") from e\n\n    if origin is list or origin is collections.abc.Sequence or origin is collections.abc.Iterable:\n        if not inst_str:\n            return []\n        args = getattr(typ, \"__args__\", ())\n        item_type = args[0] if args else typing.Any\n\n        if inst_str[0] in {\"[\", \"(\"}:\n            try:\n                value = ast.literal_eval(inst_str)\n            except (ValueError, SyntaxError):\n                raise CastError(f\"Could not cast {repr(inst_str)} to {type_repr(typ)}\") from None\n            if is_subtype_instance(value, typ):\n                return value\n            raise CastError(f\"Could not cast {repr(inst_str)} to {type_repr(typ)}\")\n\n        inst_items = inst_str.split(\",\") if inst_str else []\n        ret = [_simplistic_try_cast(item, item_type) for item in inst_items]\n        if origin is list:\n            return ret\n        return tuple(ret)\n\n    if origin is tuple:\n        args = getattr(typ, \"__args__\", ())\n        inst_items = inst_str.split(\",\") if inst_str else []\n        if len(args) == 0:\n            return tuple(inst_items)\n        if len(args) == 2 and args[-1] is ...:\n            item_type = args[0]\n            return tuple(_simplistic_try_cast(item, item_type) for item in inst_items)\n\n        num_unpack = sum(is_args_unpack(arg) for arg in args)\n        if num_unpack == 0:\n            # Great, normal heterogenous tuple\n            if len(args) != len(inst_items):\n                raise CastError(\n                    f\"Could not cast {repr(inst_str)} to {type_repr(typ)} because of length mismatch\"\n                    + (\n                        f\". Homogeneous tuples should be typed as tuple[{type_repr(args[0])}, ...] not tuple[{type_repr(args[0])}]\"\n                        if len(args) == 1\n                        else \"\"\n                    )\n                )\n            return tuple(\n                _simplistic_try_cast(item, item_typ) for item, item_typ in zip(inst_items, args)\n            )\n        else:\n            # Cursed PEP 646 stuff\n            return _cast_unpacked_tuples(inst_items, args)\n\n    if origin is dict or origin is collections.abc.Mapping:\n        if not inst_str:\n            return {}\n        if inst_str[0] == \"{\":\n            try:\n                value = ast.literal_eval(inst_str)\n            except (ValueError, SyntaxError):\n                raise CastError(f\"Could not cast {repr(inst_str)} to {type_repr(typ)}\") from None\n            if is_subtype_instance(value, typ):\n                return value\n        raise CastError(f\"Could not cast {repr(inst_str)} to {type_repr(typ)}\")\n\n    if origin is collections.abc.Callable:\n        # TODO: also support type, callback protocols\n        # TODO: unify with factories.from_string\n        # TODO: needs module context\n        if \":\" in inst_str:\n            try:\n                module_name, var = inst_str.split(\":\", 1)\n                module = _module_from_name(module_name)\n                value = _module_getattr(module, var)\n                if not is_subtype_instance(value, typ):\n                    raise CastError(f\"{type_repr(value)} is not a subtype of {type_repr(typ)}\")\n            except CastError as e:\n                raise CastError(\n                    f\"Could not cast {repr(inst_str)} to {type_repr(typ)}. {e}\"\n                ) from None\n\n            return value\n        else:\n            raise CastError(\n                f\"Could not cast {repr(inst_str)} to {type_repr(typ)}. Try using a fully qualified name, e.g. module_name:{inst_str}\"\n            )\n\n    if \"torch\" in sys.modules:\n        import torch\n\n        if origin is torch.dtype:\n            value = getattr(torch, inst_str, None)\n            if value and isinstance(value, torch.dtype):\n                return value\n            raise CastError(f\"Could not cast {repr(inst_str)} to {type_repr(typ)}\")\n\n    if \"datetime\" in sys.modules:\n        import datetime\n\n        if origin is datetime.datetime:\n            try:\n                return datetime.datetime.fromisoformat(inst_str)\n            except ValueError:\n                raise CastError(f\"Could not cast {repr(inst_str)} to {type_repr(typ)}\") from None\n\n    if \"enum\" in sys.modules:\n        import enum\n\n        if isinstance(origin, type) and issubclass(origin, enum.Enum):\n            try:\n                # Look up by name\n                return origin[inst_str]\n            except KeyError:\n                pass\n\n            # Fallback to looking up by value\n            for member in origin:\n                try:\n                    value = _simplistic_try_cast(inst_str, type(member.value))\n                except CastError:\n                    continue\n                if value == member.value:\n                    return member\n            raise CastError(f\"Could not cast {repr(inst_str)} to {type_repr(typ)}\")\n\n    if \"fractions\" in sys.modules:\n        import fractions\n\n        if origin is fractions.Fraction:\n            try:\n                return fractions.Fraction(inst_str)\n            except ValueError as e:\n                raise CastError(f\"Could not cast {repr(inst_str)} to {type_repr(typ)}\") from e\n\n    if \"pathlib\" in sys.modules:\n        import pathlib\n\n        if origin is pathlib.Path:\n            return pathlib.Path(inst_str)\n\n    if hasattr(origin, \"__chz_cast__\"):\n        return origin.__chz_cast__(inst_str)\n\n    if not isinstance(origin, type):\n        raise CastError(f\"Unrecognised type object {type_repr(typ)}\")\n\n    raise CastError(f\"Could not cast {repr(inst_str)} to {type_repr(typ)}\")\n\n\nclass _SignatureOf:\n    def __init__(self, fn: typing.Callable, strip_self: bool = False):\n        self.fn = fn\n        self._sig = inspect.signature(fn)\n\n        self.pos = []\n        self.kwonly = {}\n        self.varpos = None\n        self.varkw = None\n\n        for param in self._sig.parameters.values():\n            if param.kind in {param.POSITIONAL_OR_KEYWORD, param.POSITIONAL_ONLY}:\n                self.pos.append(param)\n            elif param.kind is param.KEYWORD_ONLY:\n                self.kwonly[param.name] = param\n            elif param.kind is param.VAR_POSITIONAL and param.name != \"__chz_args\":\n                self.varpos = param\n            elif param.kind is param.VAR_KEYWORD:\n                self.varkw = param\n\n        if strip_self:\n            if self.pos[0].name != \"self\":\n                raise ValueError(f\"Cannot strip self from signature of {self.fn}\")\n            self.pos = self.pos[1:]\n\n        self.ret = self._sig.return_annotation\n        if isinstance(self.ret, str):\n            self.ret = eval_in_context(self.ret, self.fn)\n\n\ndef is_subtype(left: TypeForm, right: TypeForm) -> bool:\n    left_origin = getattr(left, \"__origin__\", left)\n    left_args = getattr(left, \"__args__\", ())\n    right_origin = getattr(right, \"__origin__\", right)\n    right_args = getattr(right, \"__args__\", ())\n\n    if left_origin is typing.Any or left_origin is typing_extensions.Any:\n        return True\n    if right_origin is typing.Any or right_origin is typing_extensions.Any:\n        return True\n    if left_origin is None:\n        if right_origin is None or right_origin is type(None):\n            return True\n\n    if is_union_type(right_origin):\n        if is_union_type(left_origin):\n            possible_left_types = left_args\n        else:\n            possible_left_types = [left]\n        return all(\n            any(is_subtype(possible_left, right_arg) for right_arg in right_args)\n            for possible_left in possible_left_types\n        )\n\n    if right_origin is typing.Literal or right_origin is typing_extensions.Literal:\n        if left_origin is typing.Literal or left_origin is typing_extensions.Literal:\n            return all(left_arg in right_args for left_arg in left_args)\n        return False\n\n    if left_origin is typing.Literal or left_origin is typing_extensions.Literal:\n        return all(is_subtype_instance(left_arg, right) for left_arg in left_args)\n\n    if isinstance(left_origin, typing.TypeVar):\n        if left_origin == right_origin:\n            return True\n        bound = left_origin.__bound__\n        if bound is None:\n            bound = object\n        if is_subtype(bound, right_origin):\n            return True\n        if left_origin.__constraints__:\n            return any(\n                is_subtype(left_arg, right_origin) for left_arg in left_origin.__constraints__\n            )\n        return False\n\n    if isinstance(right_origin, typing.TypeVar):\n        if right_origin.__constraints__:\n            return any(\n                is_subtype(left_origin, constraint) for constraint in right_origin.__constraints__\n            )\n        if right_origin.__bound__:\n            return is_subtype(left_origin, right_origin.__bound__)\n        return True\n\n    if typing_extensions.is_protocol(left) and typing_extensions.is_protocol(right):\n        left_attrs = typing_extensions.get_protocol_members(left)\n        right_attrs = typing_extensions.get_protocol_members(right)\n        if not right_attrs.issubset(left_attrs):\n            return False\n\n        # TODO: this is incorrect\n        return True\n\n    if typing_extensions.is_protocol(right):\n        if not isinstance(left_origin, type):\n            return False\n\n        right_attrs = typing_extensions.get_protocol_members(right)\n        if not all(hasattr(left_origin, attr) for attr in right_attrs):\n            return False\n\n        # TODO: this is incorrect\n        return True\n\n    if isinstance(left, _SignatureOf) and isinstance(right, _SignatureOf):\n        empty = inspect.Parameter.empty\n\n        for left_param, right_param in zip(left.pos, right.pos):\n            if right_param.kind is right_param.POSITIONAL_OR_KEYWORD:\n                if right_param.name != left_param.name:\n                    return False\n                if left_param.kind is left_param.POSITIONAL_ONLY:\n                    return False\n            if right_param.default is not empty and left_param.default is empty:\n                return False\n            left_param_annot = maybe_eval_in_context(left_param.annotation, left.fn)\n            right_param_annot = maybe_eval_in_context(right_param.annotation, right.fn)\n            if not is_subtype(right_param_annot, left_param_annot):\n                return False\n\n        if len(left.pos) < len(right.pos):\n            # Okay if left has a *args that accepts all the extra args\n            if left.varpos is None:\n                return False\n            left_varpos_annot = maybe_eval_in_context(left.varpos.annotation, left.fn)\n            for i in range(len(left.pos), len(right.pos)):\n                right_param = right.pos[i]\n                right_param_annot = maybe_eval_in_context(right_param.annotation, right.fn)\n                if not is_subtype(right_param_annot, left_varpos_annot):\n                    return False\n\n        if len(left.pos) > len(right.pos):\n            # Must either have a default or correspond to a required keyword-only arg\n            for i in range(len(right.pos), len(left.pos)):\n                left_param = left.pos[i]\n                if left_param.default is not empty:\n                    continue\n                if (\n                    left_param.name in right.kwonly\n                    and left_param.kind is left_param.POSITIONAL_OR_KEYWORD\n                ):\n                    continue\n                return False\n\n        for name in left.kwonly.keys() & right.kwonly.keys():\n            right_param = right.kwonly[name]\n            left_param = left.kwonly[name]\n            if right_param.default is not empty and left_param.default is empty:\n                return False\n            left_param_annot = maybe_eval_in_context(left_param.annotation, left.fn)\n            right_param_annot = maybe_eval_in_context(right_param.annotation, right.fn)\n            if not is_subtype(right_param_annot, left_param_annot):\n                return False\n\n        for name in left.kwonly.keys() - right.kwonly.keys():\n            # Must either have a default or match a varkwarg\n            left_param = left.kwonly[name]\n            if left_param.default is not empty:\n                continue\n            if right.varkw is not None:\n                left_param_annot = maybe_eval_in_context(left_param.annotation, left.fn)\n                right_varkw_annot = maybe_eval_in_context(right.varkw.annotation, right.fn)\n                if is_subtype(right_varkw_annot, left_param_annot):\n                    continue\n            return False\n\n        right_only_kwonly = right.kwonly.keys() - left.kwonly.keys()\n        if right_only_kwonly:\n            # Must correspond to a positional-or-keyword arg\n            left_pos_or_kw = {p.name: p for p in left.pos if p.kind is p.POSITIONAL_OR_KEYWORD}\n            for name in right_only_kwonly:\n                if name not in left_pos_or_kw:\n                    return False\n                left_param = left_pos_or_kw[name]\n                if right.kwonly[name].default is not empty and left_param.default is empty:\n                    return False\n                left_param_annot = maybe_eval_in_context(left_param.annotation, left.fn)\n                right_param_annot = maybe_eval_in_context(right.kwonly[name].annotation, right.fn)\n                if not is_subtype(right_param_annot, left_param_annot):\n                    return False\n\n        if right.varkw is not None:\n            if left.varkw is None:\n                return False\n            right_varkw_annot = maybe_eval_in_context(right.varkw.annotation, right.fn)\n            left_varkw_annot = maybe_eval_in_context(left.varkw.annotation, left.fn)\n            if not is_subtype(right_varkw_annot, left_varkw_annot):\n                return False\n\n        if right.ret is not empty and left.ret is not empty:\n            # TODO: handle Cls.__init__ like below\n            if not is_subtype(left.ret, right.ret):\n                return False\n\n        return True\n\n    if left_origin is collections.abc.Callable and right_origin is collections.abc.Callable:\n        *left_params, left_ret = left_args\n        *right_params, right_ret = right_args\n        if len(left_params) != len(right_params):\n            return False\n        if not is_subtype(left_ret, right_ret):\n            return False\n        return all(\n            is_subtype(right_param, left_param)\n            for left_param, right_param in zip(left_params, right_params)\n        )\n\n    if is_typed_dict(left_origin) and is_typed_dict(right_origin):\n        if not right_origin.__required_keys__.issubset(left_origin.__required_keys__):\n            return False\n        left_hints = typing_extensions.get_type_hints(left_origin)\n        right_hints = typing_extensions.get_type_hints(right_origin)\n        for k, v in right_hints.items():\n            if k not in left_hints:\n                return False\n            if not is_subtype(left_hints[k], v):\n                # Technically this should be invariant due to mutability\n                return False\n        return True\n\n    # TODO: handle other special forms\n\n    if left_origin is right_origin and left_args == right_args:\n        return True\n\n    try:\n        if not issubclass(left_origin, right_origin):\n            return False\n    except TypeError:\n        return False\n\n    # see comments in is_subtype_instance\n    # TODO: add invariance\n    # TODO: think about some of this logic more carefully\n    if hasattr(left_origin, \"__class_getitem__\") and hasattr(right_origin, \"__class_getitem__\"):\n        if (\n            issubclass(right_origin, collections.abc.Mapping)\n            and typing.Generic not in left_origin.__mro__\n            and typing.Generic not in right_origin.__mro__\n        ):\n            if left_args:\n                left_key, left_value = left_args\n            else:\n                left_key, left_value = typing.Any, typing.Any\n            if right_args:\n                right_key, right_value = right_args\n            else:\n                right_key, right_value = typing.Any, typing.Any\n            return is_subtype(left_key, right_key) and is_subtype(left_value, right_value)\n\n        if left_origin is tuple and right_origin is tuple:\n            if not left_args:\n                left_args = (typing.Any, ...)\n            if not right_args:\n                right_args = (typing.Any, ...)\n            if len(right_args) == 2 and right_args[1] is ...:\n                return all(is_subtype(left_arg, right_args[0]) for left_arg in left_args)\n            if len(left_args) == 2 and left_args[1] is ...:\n                return False\n            return len(left_args) == len(right_args) and all(\n                is_subtype(left_arg, right_arg)\n                for left_arg, right_arg in zip(left_args, right_args)\n            )\n\n        if (\n            issubclass(right_origin, collections.abc.Iterable)\n            and typing.Generic not in left_origin.__mro__\n            and typing.Generic not in right_origin.__mro__\n        ):\n            if left_args:\n                (left_item,) = left_args\n            else:\n                left_item = typing.Any\n            if right_args:\n                (right_item,) = right_args\n            else:\n                right_item = typing.Any\n            return is_subtype(left_item, right_item)\n\n    return True\n\n\ndef is_subtype_instance(inst: typing.Any, typ: TypeForm) -> bool:\n    if typ is typing.Any or typ is typing_extensions.Any:\n        return True\n\n    if typ is None and inst is None:\n        return True\n\n    if isinstance(typ, typing.TypeVar):\n        if typ.__constraints__:\n            # types must match exactly\n            return any(\n                type(inst) is getattr(c, \"__origin__\", c) and is_subtype_instance(inst, c)\n                for c in typ.__constraints__\n            )\n        if typ.__bound__:\n            return is_subtype_instance(inst, typ.__bound__)\n        return True\n\n    if isinstance(typ, typing.NewType):\n        return isinstance(inst, typ.__supertype__)\n\n    origin: typing.Any\n    args: typing.Any\n    if sys.version_info >= (3, 10) and isinstance(typ, types.UnionType):\n        origin = typing.Union\n    else:\n        origin = getattr(typ, \"__origin__\", typ)\n\n    args = getattr(typ, \"__args__\", ())\n    del typ\n\n    if origin is typing.Union:\n        return any(is_subtype_instance(inst, t) for t in args)\n    if origin is typing.Literal or origin is typing_extensions.Literal:\n        return inst in args\n\n    if origin is typing.LiteralString:\n        return isinstance(inst, str)\n\n    if is_typed_dict(origin):\n        if not isinstance(inst, dict):\n            return False\n\n        for k, v in typing_extensions.get_type_hints(origin).items():\n            if k in inst:\n                if not is_subtype_instance(inst[k], v):\n                    return False\n            elif k in origin.__required_keys__:\n                return False\n        return True\n\n    # Pydantic implements generics in a special way. Just delegate validation to Pydantic.\n    # Note that all pydantic models have __pydantic_generic_metadata__, even non-generic ones.\n    if hasattr(origin, \"__pydantic_generic_metadata__\"):\n        from pydantic import ValidationError\n\n        try:\n            origin.model_validate(inst)\n            return True\n        except ValidationError:\n            return False\n\n    if typing_extensions.is_protocol(origin):\n        if getattr(origin, \"_is_runtime_protocol\", False):\n            return isinstance(inst, origin)\n        if origin in type(inst).__mro__:\n            return True\n        annotations = typing_extensions.get_type_hints(origin)\n        for attr in sorted(typing_extensions.get_protocol_members(origin)):\n            if not hasattr(inst, attr):\n                return False\n            if attr in annotations:\n                if not is_subtype_instance(getattr(inst, attr), annotations[attr]):\n                    return False\n            elif callable(getattr(origin, attr)):\n                if attr == \"__call__\" and isinstance(inst, (type, types.FunctionType)):\n                    # inst will have a better inspect.signature than inst.__call__\n                    inst_attr = inst\n                else:\n                    inst_attr = getattr(inst, attr)\n\n                if not callable(inst_attr):\n                    return False\n                try:\n                    signature = _SignatureOf(getattr(origin, attr), strip_self=True)\n                except ValueError:\n                    continue\n                if not is_subtype_instance(inst_attr, signature):\n                    return False\n            else:\n                raise AssertionError(f\"Unexpected protocol member {attr} for {origin}\")\n        return True\n\n    if isinstance(origin, _SignatureOf):\n        try:\n            inst_sig = _SignatureOf(inst)\n        except ValueError:\n            return True\n\n        return is_subtype(inst_sig, origin)\n\n    # We're done handling special forms, now just need to handle things like generics\n    if not isinstance(origin, type):\n        # TODO: handle other special forms before exit on this branch\n        return False\n    if not isinstance(inst, origin):\n        # PEP 484 duck type compatibility\n        if origin is complex and isinstance(inst, (int, float)):\n            return True\n        if origin is float and isinstance(inst, int):\n            return True\n        if origin is bytes and isinstance(inst, (bytearray, memoryview)):\n            # TODO: maybe remove bytearray and memoryview ducktyping based on PEP 688\n            return True\n\n        if inst in typing_Never:\n            return True\n        if issubclass(type(inst), typing_extensions.Any) or (\n            sys.version_info >= (3, 11) and issubclass(type(inst), typing.Any)\n        ):\n            return True\n        return False\n\n    assert isinstance(inst, origin)\n    if not args:\n        return True\n\n    # TODO: there's some confusion when checking issubclass against a generic collections.abc\n    # base class, since you don't actually know whether the generic args of typ / origin correspond\n    # to the generic args of the base class. So if we detect a user defined generic (i.e. based\n    # on presence of Generic in the mro), we just fall back and don't assume we know the semantics\n    # of what the generic args are.\n    if issubclass(origin, collections.abc.Mapping) and typing.Generic not in origin.__mro__:\n        key_type, value_type = args\n        return all(\n            is_subtype_instance(key, key_type) and is_subtype_instance(value, value_type)\n            for key, value in inst.items()\n        )\n    if origin is tuple:\n        if len(args) == 2 and args[1] is ...:\n            return all(is_subtype_instance(i, args[0]) for i in inst)\n        if len(inst) != len(args):\n            return False\n        return all(is_subtype_instance(i, t) for i, t in zip(inst, args))\n\n    if issubclass(origin, collections.abc.Iterable) and typing.Generic not in origin.__mro__:\n        (item_type,) = args\n        return all(is_subtype_instance(item, item_type) for item in inst)\n\n    if origin is type:\n        (type_type,) = args\n        return issubclass(inst, type_type)\n\n    if origin is collections.abc.Callable:\n        try:\n            inst_sig = inspect.signature(inst)\n        except ValueError:\n            return True\n        *params, ret = args\n        if params != [...]:\n            try:\n                bound = inst_sig.bind(*params)\n            except TypeError:\n                return False\n            for param, callable_param_type in bound.arguments.items():\n                param = inst_sig.parameters[param]\n                param_annot = maybe_eval_in_context(param.annotation, inst)\n                # ooh, contravariance\n                if param.kind is param.VAR_POSITIONAL:\n                    if any(not is_subtype(cpt, param_annot) for cpt in callable_param_type):\n                        return False\n                elif not is_subtype(callable_param_type, param_annot):\n                    return False\n        if inst_sig.return_annotation is not inst_sig.empty:\n            ret_annot = maybe_eval_in_context(inst_sig.return_annotation, inst)\n            # inspect.signature(Cls) will have Cls.__init__, which is annotated as -> None\n            if not (isinstance(inst, type) and ret_annot is None and is_subtype(inst, ret)):\n                if ret_annot is None:\n                    ret_annot = type(None)\n                elif ret_annot in typing_Never:\n                    ret_annot = ret\n                if ret_annot != ret and not is_subtype(ret_annot, ret):\n                    return False\n        return True\n\n    # We don't really know how to handle user defined generics\n    if hasattr(inst, \"__orig_class__\"):\n        # If we have an __orig_class__ and the origins match, check the args (assuming that they\n        # are invariant, although maybe covariant is a better guess?)\n        if inst.__orig_class__.__origin__ is origin:\n            return inst.__orig_class__.__args__ == args\n    # Otherwise, fail open\n    return True\n\n    # TODO: overloads\n    # TODO: paramspec / concatenate\n    # TODO: typeguard\n    # TODO: annotated\n    # TODO: self\n    # TODO: pep 692 unpack\n    # TODO: typevartuple??\n\n\ndef simplified_union(types):\n    if len(types) == 0:\n        return typing.Never\n    if len(types) == 1:\n        return types[0]\n\n    union_types = []\n    for typ in types:\n        if getattr(typ, \"__args__\", None) is None and any(\n            is_subtype(typ, member) for member in union_types\n        ):\n            continue\n        union_types.append(typ)\n\n    types = union_types\n    union_types = []\n    for typ in reversed(types):\n        if getattr(typ, \"__args__\", None) is None and any(\n            is_subtype(typ, member) for member in union_types\n        ):\n            continue\n        union_types.append(typ)\n\n    return functools.reduce(operator.or_, union_types)\n\n\ndef _simplistic_type_of_value(value: object) -> TypeForm:\n    # TODO: maybe remove this? Its current use is in diagnostics (for providing the actual type),\n    # but is_subtype_instance is in a position to provide better diagnostics\n    if hasattr(type(value), \"__class_getitem__\"):\n        if isinstance(value, collections.abc.Mapping) and typing.Generic not in type(value).__mro__:\n            return type(value)[\n                simplified_union([_simplistic_type_of_value(k) for k in value.keys()]),\n                simplified_union([_simplistic_type_of_value(v) for v in value.values()]),\n            ]\n        if isinstance(value, tuple):\n            if len(value) <= 10:\n                return type(value)[tuple(_simplistic_type_of_value(v) for v in value)]\n            return type(value)[simplified_union([_simplistic_type_of_value(v) for v in value]), ...]\n        if (\n            isinstance(value, collections.abc.Iterable)\n            and typing.Generic not in type(value).__mro__\n        ):\n            return type(value)[simplified_union([_simplistic_type_of_value(v) for v in value])]\n\n    if isinstance(value, type):\n        return type[value]\n\n    return type(value)\n"
  },
  {
    "path": "chz/universal.py",
    "content": "if __name__ == \"__main__\":\n    import chz\n\n    chz.entrypoint(object)\n"
  },
  {
    "path": "chz/util.py",
    "content": "class MISSING_TYPE:\n    def __repr__(self) -> str:\n        return \"MISSING\"\n\n\nMISSING = MISSING_TYPE()\n"
  },
  {
    "path": "chz/validators.py",
    "content": "from __future__ import annotations\n\nimport collections\nimport collections.abc\nimport re\nfrom typing import Any, Callable, Literal\n\nimport chz\nfrom chz.field import Field\nfrom chz.tiepin import _simplistic_type_of_value, is_subtype_instance, type_repr\n\n\nclass validate:\n    def __init__(self, fn: Callable[[Any], None]):\n        self.fn = fn\n\n    def __set_name__(self, owner: Any, name: str) -> None:\n        _ensure_chz_validators(owner)\n        owner.__chz_validators__.append(self.fn)\n        setattr(owner, name, self.fn)\n\n\ndef _ensure_chz_validators(cls: Any) -> None:\n    if \"__chz_validators__\" not in cls.__dict__:\n        # make a copy of the parent's validators, if any\n        validators: list[Callable[[object], None]] = []\n        for base in cls.__bases__:\n            validators.extend(getattr(base, \"__chz_validators__\", []))\n        cls.__chz_validators__ = validators\n\n\ndef for_all_fields(fn: Callable[[Any, str], None]) -> Callable[[Any], None]:\n    def inner(self: Any) -> None:\n        for field in self.__chz_fields__.values():\n            fn(self, field.x_name)\n\n    return inner\n\n\ndef instancecheck(self: Any, attr: str) -> None:\n    \"\"\"A good old fashioned isinstance check based on the annotated type of the field.\"\"\"\n    typ = self.__chz_fields__[attr.removeprefix(\"X_\")].final_type\n    value = getattr(self, attr)\n    if not isinstance(value, typ):\n        raise TypeError(f\"Expected {attr} to be {type_repr(typ)}, got {type_repr(type(value))}\")\n\n\ndef typecheck(self: Any, attr: str) -> None:\n    \"\"\"A fancy type check based on the annotated type of the field.\"\"\"\n\n    field = self.__chz_fields__[attr.removeprefix(\"X_\")]\n    typ = field.x_type\n\n    value = getattr(self, attr)\n\n    if not is_subtype_instance(value, typ):\n        # TODO: is_subtype_instance is in a much better place to return diagnostics\n        if getattr(typ, \"__origin__\", None) is Literal:\n            raise TypeError(f\"Expected {attr} to be {type_repr(typ)}, got {value!r}\")\n        raise TypeError(\n            f\"Expected {attr} to be {type_repr(typ)}, got {type_repr(_simplistic_type_of_value(value))}\"\n        )\n\n\ndef instance_of(typ: type) -> Callable[[Any, str], None]:\n    \"\"\"Check the attribute is an instance of the given type.\"\"\"\n\n    def inner(self: Any, attr: str) -> None:\n        value = getattr(self, attr)\n        if not isinstance(value, typ):\n            raise TypeError(f\"Expected {attr} to be {type_repr(typ)}, got {type_repr(type(value))}\")\n\n    return inner\n\n\ndef gt(base) -> Callable[[Any, str], None]:\n    def inner(self: Any, attr: str) -> None:\n        value = getattr(self, attr)\n        if not value > base:\n            raise ValueError(f\"Expected {attr} to be greater than {base}, got {value}\")\n\n    return inner\n\n\ndef lt(base) -> Callable[[Any, str], None]:\n    def inner(self: Any, attr: str) -> None:\n        value = getattr(self, attr)\n        if not value < base:\n            raise ValueError(f\"Expected {attr} to be less than {base}, got {value}\")\n\n    return inner\n\n\ndef ge(base) -> Callable[[Any, str], None]:\n    def inner(self: Any, attr: str) -> None:\n        value = getattr(self, attr)\n        if not value >= base:\n            raise ValueError(f\"Expected {attr} to be greater or equal to {base}, got {value}\")\n\n    return inner\n\n\ndef le(base) -> Callable[[Any, str], None]:\n    def inner(self: Any, attr: str) -> None:\n        value = getattr(self, attr)\n        if not value <= base:\n            raise ValueError(f\"Expected {attr} to be less or equal to {base}, got {value}\")\n\n    return inner\n\n\ndef valid_regex(self: Any, attr: str) -> None:\n    \"\"\"Check the attribute is a valid regex.\"\"\"\n    import re\n\n    value = getattr(self, attr)\n    try:\n        re.compile(value)\n    except re.error as e:\n        raise ValueError(f\"Invalid regex in {attr}: {e}\") from None\n\n\ndef const_default(self: Any, attr: str) -> None:\n    \"\"\"Check the attribute matches the field's default value.\"\"\"\n    from chz.util import MISSING_TYPE\n\n    field: Field = self.__chz_fields__[attr.removeprefix(\"X_\")]\n    default = field._default\n    if isinstance(default, MISSING_TYPE):\n        raise ValueError(\n            \"const_default requires a default value (default_factory is not supported)\"\n        )\n\n    value = getattr(self, attr)\n    if value != default:\n        raise ValueError(f\"Expected {attr} to match the default {default!r}, got {value!r}\")\n\n\ndef _decorator_typecheck(self: Any) -> None:\n    for field in self.__chz_fields__.values():\n        typecheck(self, field.x_name)\n        # TODO: typecheck(self, field.logical_name)\n\n\ndef check_field_consistency_in_tree(obj: Any, fields: set[str], regex_root: str = \"\") -> None:\n    \"\"\"\n    This isn't itself a validator. See test_validate_field_consistency for example usage.\n    This is effectively a way to paper over a potential missing feature in chz.\n    \"\"\"\n    values: dict[tuple[str, str], dict[object, list[str]]] = collections.defaultdict(\n        lambda: collections.defaultdict(list)\n    )\n\n    def inner(obj: Any, obj_path: str):\n        assert chz.is_chz(obj)\n\n        for f in obj.__chz_fields__.values():\n            value = getattr(obj, f.logical_name)\n            field_path = f\"{obj_path}.{f.logical_name}\" if obj_path else f.logical_name\n            regex_match = re.search(regex_root, obj_path)\n            if f.logical_name in fields and regex_match:\n                values[(regex_match.group(), f.logical_name)][value].append(field_path)\n\n            if chz.is_chz(value):\n                inner(value, field_path)\n            if isinstance(value, collections.abc.Mapping):\n                for k, v in value.items():\n                    if chz.is_chz(v):\n                        inner(v, f\"{field_path}.{k}\")\n            elif isinstance(value, collections.abc.Sequence):\n                for i, v in enumerate(value):\n                    if chz.is_chz(v):\n                        inner(v, f\"{field_path}.{i}\")\n\n    inner(obj, \"\")\n\n    def paths_repr(paths: list[str]) -> str:\n        if len(paths) <= 3:\n            return \", \".join(paths)\n        return \", \".join(paths[:3]) + f\", ... ({len(paths) - 3} more)\"\n\n    for (_, field), value_to_paths in values.items():\n        if len(value_to_paths) > 1:\n            raise ValueError(\n                f\"Field {field!r} has inconsistent values in object tree:\\n\"\n                + \"\\n\".join(\n                    f\"{value!r} at {paths_repr(paths)}\" for value, paths in value_to_paths.items()\n                )\n            )\n\n\ndef _find_original_definitions(instance: Any) -> dict[str, tuple[Field, type]]:\n    \"\"\"Find the original field definitions in the parent classes of the instance.\"\"\"\n\n    assert chz.is_chz(instance)\n    fields = {}\n    for cls in reversed(type(instance).__mro__):\n        if not chz.is_chz(cls):\n            continue\n        for field in chz.chz_fields(cls).values():\n            if field.logical_name not in fields:\n                fields[field.logical_name] = (field, cls)\n    return fields\n\n\ndef is_override(\n    instance: Any, attr: str, *, original_defs: dict[str, tuple[Field, type]] | None = None\n) -> None:\n    \"\"\"\n    Validator that checks if a field is an override of a field of the same type in a parent class.\n\n    This validator will error out if either:\n    - the field doesn't exist in any parent\n    - the type of field on the child is not a subtype of the type of the field on the parent\n\n    This is especially useful in case someone renames a field name in the parent class.\n    You'll get an error message rather than your override being silently ignored.\n    \"\"\"\n    if original_defs is None:\n        original_defs = _find_original_definitions(instance)\n\n    logical_name = attr.removeprefix(\"X_\")\n    assert logical_name in original_defs\n\n    original_field, original_class = original_defs[logical_name]\n\n    if original_class is type(instance):\n        raise ValueError(\n            f\"Field {logical_name} does not exist in any parent classes of {type_repr(type(instance))}\"\n        )\n\n    instance_value = getattr(instance, attr)\n    if not chz.tiepin.is_subtype_instance(instance_value, original_field.final_type):\n        raise ValueError(\n            f\"{type_repr(type(instance))}.{attr}' must be an instance of \"\n            f\"{type_repr(original_field.final_type)} to match the type on the original definition \"\n            f\"in {type_repr(original_class)}\"\n        )\n\n\nclass IsOverrideMixin:\n    \"\"\"A mixin that checks if fields are overrides of fields in parent classes.\n\n    The following:\n\n    ```\n    @chz.chz\n    class Foo:\n        x: int = chz.field(default=1, validator=is_override)\n        y: int = chz.field(default=1, validator=is_override)\n    ```\n\n    is equivalent to:\n\n    ```\n    @chz.chz\n    class Foo(IsOverrideMixin):\n        x: int = chz.field(default=1)\n        y: int = chz.field(default=1)\n    ```\n    \"\"\"\n\n    @validate\n    def _check_overrides(self) -> None:\n        fields = getattr(self, \"__chz_fields__\", None)\n        if fields is None:\n            return\n\n        original_defs = _find_original_definitions(self)\n        for field in fields.values():\n            is_override(self, field.x_name, original_defs=original_defs)\n"
  },
  {
    "path": "docs/01_quickstart.md",
    "content": "## Quick start\n\nTurn any function into a command line tool:\n\n```python\nimport chz\n\ndef main(name: str, age: int) -> None:\n    print(f\"Hello, {name}! You are {age} years old.\")\n\nif __name__ == \"__main__\":\n    chz.entrypoint(main)\n\n# python script.py name=foo age=21\n```\n\nOr instantiate a class containing your configuration:\n\n```python\nimport chz\n\n@chz.chz\nclass PersonConfig:\n    name: str\n    age: int\n\ndef main(c: PersonConfig) -> None:\n    print(f\"Hello, {c.name}! You are {c.age} years old.\")\n\nif __name__ == \"__main__\":\n    chz.nested_entrypoint(main)\n\n# python script.py name=foo age=21\n```\n\n### [Next section — Object Model](./02_object_model.md)\n"
  },
  {
    "path": "docs/02_object_model.md",
    "content": "## Declarative object model\n\nIn the beginning there was `attrs`... although people may be more familiar with its stripped down\nnephew, `dataclasses`. `chz` continues in the same tradition.\n\nThis should feel familiar:\n\n```python\n@chz.chz\nclass Experiment:\n    name: str\n    steps: int\n    checkpoint_dir: str = \"az://oai/default\"\n```\n\nA quick comparison to `dataclasses`:\n- `chz` is not meant as a better `class`, but as a solution for configuration. It is opinionated\n  and specialised in various ways that `dataclasses` is not.\n- `chz` has exclusively keyword-only fields. This is generally saner and solves various problems\n  with `dataclasses`, especially in situations involving inheritance.\n- `chz` is immutable only. Configuration should not be mutable. `chz` supports partial\n  application in ways that should hopefully obviate the need for mutable configuration\n  (as we'll see later); you can also `chz.replace` to get a new object.\n- `chz`'s implementation makes different tradeoffs\n\n## Fields, and specifying defaults\n\n`chz` lets you customise the fields of your objects using the `chz.field` function.\n\nThe following example shows different ways you can specify the default value for a field:\n\n```python\n@chz.chz\nclass Experiment:\n    name: str\n    steps: int\n\n    # directly assign a default value, useful for simple, immutable types\n    checkpoint_dir: str = \"az://oai/default\"\n    # via the `default` argument to `chz.field`, useful if you need to customise your field\n    # (like hiding it from the repr), but still have a default\n    password: str = chz.field(default=\"hunter2\", repr=False)\n    # via the `default_factory` argument to `chz.field`, useful if the default is mutable or\n    # expensive to compute\n    dataset: list[str] = chz.field(default_factory=download_all_of_wikipedia, doc=\"A dataset!\")\n```\n\nSee [`chz.field` docs](./22_field_api.md) for more details.\n\n## Immutability\n\n`chz` objects are immutable. This is a deliberate and non-negotiable design choice:\n\n```python\ne = Experiment(name=\"train_job\", steps=100)\ne.checkpoint_dir = \"az://this/wont/work\"  # raises FrozenInstanceError\n```\n\nThat said, there are a couple patterns that are useful. If you need to compute derived data from\nexisting fields, use `@chz.init_property` (or `@property` or `@functools.cached_property`):\n\n```python\n@chz.chz\nclass Experiment:\n    name: str\n    steps: int\n\n    @chz.init_property\n    def log_path(self) -> str:\n        return re.sub(r\"[^a-zA-Z]\", \"\", self.name)\n```\n\n`chz.init_property` works exactly like `functools.cached_property`, except that it is automatically\naccessed during initialisation. This surfaces errors more reliably. Think of this as a replacement\nfor `dataclasses.field(init=False)`.\n\nFor complex initialisation logic, `chz` has a\n[`Blueprint` mechanism](04_command_line.md#blueprints-and-partial-application) that is really\npowerful. This allows you to accomplish things like partial application, where you only specify some\nof your attributes at a time, or type aware parsing.\n\nNote that if you already have a `chz` object and you want to replace a field on it, you can use\n`chz.replace`; this works similarly to `dataclasses.replace`.\n\n## No `__post_init__`\n\nNote that `chz` does **not** have a `__post_init__` equivalent.\n\nIf you wanted a `__post_init__` to do additional validation, `chz` has first-class support for\nvalidation. See [validation](./03_validation.md) for details.\n\nIf you need arbitrary logic to determine a default value, consider using `default_factory`.\n\nIf you need to munge your field based on the value of other fields, consider using `@property` to\ndo something equivalent, or a `munger`.\n\nSee [details and examples](./21_post_init.md) for more guidance with this use case. The details\ndocument also describes the \"magix prefix\" mechanism (`X_`) you may see in use with `chz`.\n\n### [Next section — Validation](./03_validation.md)\n"
  },
  {
    "path": "docs/03_validation.md",
    "content": "## Validation\n\n`chz` supports validation in a manner similar to `attrs`, but slightly nicer for class-level\nvalidation. `chz` supports both field-level validation and class-level validation.\n\n```python\nfrom chz.validators import typecheck, gt\n\n@chz.chz\nclass Fraction:\n    # specify a validator for a given field\n    numerator: int = chz.field(validator=typecheck)\n    # or even multiple validators for a field!\n    denominator: int = chz.field(validator=[typecheck, gt(0)])\n\n    # class-level validator that can check multiple fields\n    @chz.validate\n    def _check_reduced(self):\n        if math.gcd(self.numerator, self.denominator) > 1:\n            raise ValueError(\"Fraction is not reduced\")\n\nFraction(numerator=\"asdf\", denominator=4)  # raises TypeError: Expected numerator to be int, got str\nFraction(numerator=2, denominator=0)  # raises ValueError: Expected denominator to be greater than 0, got 0\nFraction(numerator=2, denominator=4)  # raises ValueError: Fraction is not reduced\nFraction(numerator=1, denominator=2)  # works great!\n```\n\nValidation happens as part of the generated `__init__`.\n\nAll `@chz.init_property` defined on your class will also be accessed at `__init__` time, ensuring\nthat any errors raised when computing those properties are surfaced early.\n\n## Type checking\n\n`chz` is usable alongside static type checking. It also contains some facilities to do runtime\ntype checking.\n\n`chz` does not currently default to doing runtime type checking. The upsides are limited, since:\n- `chz` has powerful, type-aware command line parsing\n- `chz` can be understood by static type checkers\n\nHowever, runtime type checking has several downsides: it's slow, it's not actually sound, so cannot\nbe a substitute for a static type checker, it impedes certain kinds of interesting metaprogramming.\nIt's less clear how one would opt-out of runtime type checking than it is to opt-in (just add\na validator).\n\n`chz` does not do implicit casting, like `pydantic`. I find this to be a huge footgun.\nPython is a strongly typed language and this is for the better. `chz` does allow for some forms\nof explicitly opted-in casting, as part of [the `Blueprint` mechanism](04_command_line.md#blueprints-and-partial-application).\n\nWith all that said, it remains easy to add runtime type checking! We saw an example of this on a\nper-field basis above, but here's how to easily do this for all fields in a class:\n\n```python\n@chz.chz(typecheck=True)\nclass TypeCheckedAlphabet:\n    alpha: int\n    beta: str\n    gamma: bytes\n\n# This is approximately equivalent to adding the following validator:\n#     @chz.validate\n#     def typecheck_all_fields(self):\n#         from chz.validators import for_all_fields, typecheck\n#         for_all_fields(typecheck)(self)\n```\n\n`chz`'s runtime type checking is also quite advanced and better in several respects than other\nopen source libraries.\n\n## Validation and inheritance\n\n`chz`'s validation works as expected in the presence of inheritance: both class-level and\nfield-level validators are inherited by the child class.\n\nThere is one caveat: if you clobber a field in a child class, you will also clobber any field-level\nvalidator specified in a parent class for that field, unless you explicitly respecify it.\n\n`chz` currently does not allow overriding validators in subclasses. This is because it would\nrepresent a Liskov substitution principle violation (and use cases are niche). If you need\nthis, have your validator call some other method which you can then freely override.\n\n`chz` has some built-in validation, for instance, ensuring that fields do not clobber methods or\nproperties defined on the parent class, etc.\n\n### [Next section — Command line](./04_command_line.md)\n"
  },
  {
    "path": "docs/04_command_line.md",
    "content": "## Command line parsing\n\nType aware CLIs are really great.\nThese let you focus on writing code, with types, and you get a CLI for free.\n\n`chz` gives this to you as well:\n\n```python\ndef launch_run(name: str, steps: int, checkpoint_dir: str = \"az://oai/default\"):\n    ...\n\nif __name__ == \"__main__\":\n    chz.entrypoint(launch_run)\n\n# The command line:\n# name=foo steps=100\n# becomes:\n# launch_run(name=\"foo\", steps=100)\n```\n\n`chz` will also let you parse into an object:\n\n```python\n@chz.chz\nclass Experiment:\n    name: str\n    steps: int\n    checkpoint_dir: str = \"az://oai/default\"\n\n\ndef main():\n    experiment = chz.entrypoint(Experiment)\n    ...\n\nif __name__ == \"__main__\":\n    main()\n\n# The command line:\n# name=foo steps=100 checkpoint_dir=az://oai/somewhere\n# becomes:\n# experiment = Experiment(name=\"foo\", steps=100, checkpoint_dir=\"az://oai/somewhere\")\n```\n\nIf you have a `main` function that takes a single argument that's a `chz` object, you can have it\nserve as an entrypoint by using `chz.nested_entrypoint`:\n\n```python\ndef main(experiment: Experiment):\n    ...\n\nif __name__ == \"__main__\":\n    chz.nested_entrypoint(main)\n```\n\nAll of this is pretty straightforward. Here's a case `chz` handles that's a bit more interesting:\nhandling parsing when some of your fields are nested `chz` objects.\n\n```python\n@chz.chz\nclass Model:\n    encoding: str\n\n@chz.chz\nclass Experiment:\n    name: str\n    steps: int\n    model: Model\n\n# name=foo steps=100 model.encoding=gpt2\n# becomes\n# Experiment(name=\"foo\", steps=100, model=Model(encoding=\"gpt2\"))\n```\n\n## Hyphens\n\nIf you like `--hyphens` for your command line arguments, just use\n`chz.entrypoint(..., allow_hyphens=True)` or similar.\n\nBut the zero hyphen life is pretty great once you get used to it.\n\n## Polymorphic construction\n\nHere's something only `chz` lets you do... what if you want `Model` to be polymorphic? That is,\nmaybe you want to be able to specify `model=Transformer` or `model=Diffusion`.\n\nOr maybe you want to construct a field by calling some arbitrary factory function.\n\n```python\nimport chz\n\ndef wikipedia_text(seed: int) -> Dataset:\n    \"\"\"Function that produces a dataset.\"\"\"\n\n@chz.chz\nclass Model:\n    encoding_name: str = \"gpt2\"\n\n@chz.chz\nclass Transformer(Model):\n    n_layers: int = 1000\n    d_model: int = 100000\n\n@chz.chz\nclass Experiment:\n    model: Model\n    dataset: Dataset\n\nexperiment = chz.entrypoint(Experiment)\n\n# The command line:\n# model=Transformer model.n_layers=10 dataset=wikipedia_text dataset.seed=217\n# becomes:\n# Experiment(model=Transformer(n_layers=10), dataset=wikipedia_text(seed=217))\n```\n\nThis is really powerful. Recursive polymorphic construction allows you to separate concerns more\nclearly and helps reduce boilerplate.\n\nIf you're familiar with the \"callable\" pattern we sometimes end up with in our frameworks (e.g.\n\"dataset_callable\") -- this enables having an interface that isn't a complete kludge. This\nencourages modularity and allows for easy dependency injection. It is common for other libraries\nin this space, to end up infecting all of your code; polymorphic construction helps\nyou avoid this.\n\nIn other other words, many tools will let you construct an `X` by specifying `...` to feed to\n`X(...)`. But chz lets you construct an `X` by specifying both callee and arguments in `...(...)`\n\nAnyway, hopefully this all adds up to fewer 1000 line launch scripts or registries or horrible\ninterfaces for parametrising datasets when using `chz`.\n\nThis is probably the primary interesting feature in `chz`.\n\n## Wildcards\n\nIt can be a little tiresome specifying fully qualified paths for every field you want to set.\nTo aid with this, `chz` supports wildcards in your blueprint arguments using \"...\". For example:\n```\nmodel=Transform ...encoding=gpt2 model...activation_fn=gelu\n```\nThis will set `encoding` on all nested objects that take an `encoding` argument, and set\n`activation_fn` on all nested objects inside of `model` that take an `activation_fn` argument.\nNote that wildcards can match multiple (potentially nested) fields.\n\nWildcard use is somewhat discouraged, particularly so outside of a command line context.\n\n## Discoverability, `--help`, and errors\n\nPrograms that use `chz.entrypoint` also get you a reasonable `--help` out of the box.\n```\n$ python script.py --help\nWARNING: Missing required arguments for parameter(s): dataset\n\nEntry point: Experiment\n\nArguments:\n  model                Model    Model (meta_factory)\n  model.encoding_name  str      'gpt2' (default)\n  dataset              Dataset  -\n```\n\nOne important note about `--help`: polymorphic construction means that arguments you specify can\nchange the set of arguments you need to specify. For instance, in the above example,\n`model=Transformer` will allow you to also specify `model.n_layers` and `model.d_model`.\n\nHowever, passing `--help` to a `chz` script along with arguments, will show you all the arguments\nyou can specify given the arguments you've already specified. That is, passing\n`model=Transformer --help` will show `model.n_layers` and `model.d_model` in the output.\n```\n$ python script.py model=Transformer --help\nWARNING: Missing required arguments for parameter(s): dataset\n\nEntry point: Experiment\n\nArguments:\n  model                Model    Transformer (from command line)\n  model.encoding_name  str      'gpt2' (default)\n  model.n_layers       int      1000 (default)\n  model.d_model        int      100000 (default)\n  dataset              Dataset  -\n```\n\nNote that `--help` will also show you the mapping of arguments you specify to fields:\n```\n$ python script.py model=Transformer ...n_layers=10 model.encoding_name=cl100k_base --help\nWARNING: Missing required arguments for parameter(s): dataset\n\nEntry point: Experiment\n\nArguments:\n  model                Model    Transformer (from command line)\n  model.encoding_name  str      cl100k_base (from command line)\n  model.n_layers       int      10 (from command line)\n  model.d_model        int      100000 (default)\n  dataset              Dataset  -\n```\n\nIf you misspell an argument, `chz` will tell you what you probably meant. This fuzzy detection logic\nworks well even for wildcard arguments.\n```\nchz.blueprint.ExtraneousBlueprintArg: Extraneous Blueprint argument 'modell' for __main__.Experiment\nDid you mean 'model'?\n```\n\nFinally, while `chz` allows you to clobber arguments, it does not allow arguments to go completely\nunused. This is important for sanity, but somehow a common bug in some CLI libraries, like `fire`.\n\n## Variadic parameters\n\nchz supports polymorphic construction through variadic parameters. This works for lists, tuples,\ndicts (with str keys) and `TypedDict`s:\n\n```python\n@chz.chz\nclass Eval:\n    name: str\n\n@chz.chz\nclass Experiment:\n    evals: list[Eval]\n\nexperiment = chz.entrypoint(Experiment)\n\n# The command line:\n# evals.0.name=foo evals.1.name=bar\n# becomes:\n# Experiment(evals=[Eval(name=\"foo\"), Eval(name=\"bar\")])\n```\n\nVariadic parameters can also be polymorphic, for instance, you could do:\n```python\n# evals.0=EvalSubclass evals.0.name=foo evals.1.name=bar\n# becomes:\n# Experiment(evals=[EvalSubclass(name=\"foo\"), Eval(name=\"bar\")])\n```\n\n## `Blueprint`s, briefly\n\nWe'll talk about `Blueprint`s more in the next section. For now, all you need to know is that the\n`Blueprint` class is the API that powers `chz`'s command line functionality. The `chz.entrypoint`\nfunction we saw above is basically doing:\n```\ndef entrypoint(target: Callable[..., _T]) -> _T:\n    return Blueprint(target).make_from_argv(sys.argv[1:])\n```\n\n## Casting\n\nThe arguments you provide on the command line are strings. However, `chz` wants to give you your\narguments with the correct type. By default, `chz` will try to cast your arguments for you to\nthe correct type.\n\nThis casting is a process you may wish to customise.\n\nThe first method is by attaching a `__chz_cast__` classmethod to the target type.\n```python\n@dataclass\nclass Duration:\n    seconds: int\n\n    @classmethod\n    def __chz_cast__(cls, value: str):\n        try:\n            return Duration(int(value.strip(\"hms\")) * {\"h\": 3600, \"m\": 60, \"s\": 1}[value[-1]])\n        except Exception as e:\n            raise CastError(f\"Could not cast {value!r} to {cls.__name__}\") from e\n\n@chz.chz\nclass Args:\n    t: Duration\n\nassert chz.Blueprint(Args).apply({\"t\": Castable(\"1h\")}).make() == Args(t=Duration(3600))\n```\nIn the above, since `Duration` is used in the annotation, `chz` will attempt to use\n`Duration.__chz_cast__` to cast `Castable(\"1h\")` to the correct type.\n\nThe second method is by specifying a per-field function to `blueprint_cast` via `chz.field`:\n```python\ndef cast_binary(value: str) -> int:\n    try:\n        return int(value, 2)\n    except Exception as e:\n        raise CastError(f\"Could not cast {value!r} to binary\") from e\n\n@chz.chz\nclass Args:\n    binary: int = chz.field(blueprint_cast=cast_binary)\n\nassert chz.Blueprint(Args).apply({\"binary\": Castable(\"101\")}).make() == Args(binary=5)\n```\n\nField level casts will override the `__chz_cast__` method if both are applicable.\n\nCasting only applies to `Blueprint` (not `__init__` of your `chz` class), and only if the value\npassed to the `Blueprint` is a `Castable`. Python is a strongly typed language, this is a good\nthing, `chz` will not change your types willy nilly.\n\n### CLI from a class\n\n`chz` lets you easily create a script entrypoint based on the methods on a class using\n`chz.methods_entrypoint`.\n\nFor example, given main.py:\n```python\nimport chz\n\n@chz.chz\nclass Run:\n    name: str\n\n    def launch(self, cluster: str):\n        \"Launch a job on a cluster\"\n        return (\"launch\", self, cluster)\n\nif __name__ == \"__main__\":\n    print(chz.methods_entrypoint(Run))\n```\n\nTry out the following command line invocations:\n```\npython main.py launch self.name=job cluster=owl\npython main.py launch --help\npython main.py --help\n```\n\nNote that you can rename the `self` argument in your method to something else.\n\n### Universal CLI\n\n```python\nimport chz\n\nchz.entrypoint(object)\n```\n\nThis script probably isn't actually directly useful, but just to show you the power of `chz`, it\nwill let you call most functions or create most objects.\nTry:\n- `python -m chz.universal '=print' '0=hello' '1=lambda name: name + \"!\"' '1.name=world'`\n- `python -m chz.universal '=calendar:Calendar' --help`\nSee e.g. `test_root_polymorphism` for how you might actually want to use this.\n\n### [Next section — Blueprints](./05_blueprint.md)\n"
  },
  {
    "path": "docs/05_blueprint.md",
    "content": "## Blueprints and partial application\n\n`chz` has a `Blueprint` mechanism that powers the command line functionality. `chz.entrypoint` is\njust a thin wrapper around `Blueprint`. The `Blueprint` mechanism is a Python interface that allows\nyou to do advanced initialisation of objects.\n\nIn particular, it enables partial application of arguments to a `Blueprint`. Since `chz` objects\nare immutable, this can be a good substitute for a complex initialisation procedure that relies\non mutability.\n\n```python\nblueprint = chz.Blueprint(Experiment)\n# Note that apply modifies the blueprint in place, use blueprint.clone() to make a copy\nblueprint.apply({\"encoding_name\": \"gpt2\", \"...n_layers\": 100})\nblueprint.apply({\"model\": Transformer})\nblueprint.apply({\"model.n_layers\": 10_000})\nblueprint.apply({\"model.n_layers\": Castable(\"10_000\")})\n\nexperiment = blueprint.make()\n# experiment = Experiment(model=Transformer(n_layers=10_000), encoding_name=\"gpt2\")\n```\n\nPartial application is lazy and non-destructive. In particular, if you do something incorrect, you\nwill only get errors when you actually try to instantiate, via `make`.\n\nIf for some reason you need the type aware casting logic that `chz` you get via the command line,\nyou can opt in to it when using `Blueprint.apply` by wrapping your value in `Castable`, e.g.\n`blueprint.apply({\"n_layers\": Castable(\"100\")})`.\n\nNote that if you already have a `chz` object and you want to replace a field on it, you can use\n`chz.replace`; this works similarly to `dataclasses.replace`.\n\n## Blueprint polymorphism recap\n\nRoughly, the core idea of polymorphic construction is that instead of only being able to assign\nvalues to fields, you can also assign the return values of a call to fields:\n\nIf `chz` sees `field=value`, this is similar to `X(field=value)`. But if `chz` sees\n`field=value field.a=1 field.b=2`, this is similar to `X(field=value(a=1, b=2))`.\n\nFor a full explanation of the Blueprint algorithm, see [Blueprint Algorithm](#blueprint-algorithm).\n\n### Discovery and interpretation of valid polymorphic values\n\nWhen figuring out which class you mean to instantiate when you do `model=Transformer`, `chz` will\nlook at all currently created subclasses of `Model` to find the right one. When calling functions,\n`chz` will look at all the functions in the module of the relevant config.\n\nYou can also specify a fully qualified path like `module:ClassName` / `package.module:function`\nand `chz` will import and find your object. This can let you avoid ambiguity or reliance on\nimport time side effects.\n\nThis discovery process can also be customised via `meta_factory`. This is an advanced feature,\nsee `chz/factories.py` for more details.\n\nTODO: talk about some of the more advanced tricks here (i.e. look at some of the things from\n`test_factories.py`)\n\n## `blueprint_unspecified`\n\nThis is easiest to understand by example.\n\n```python\n@chz.chz\nclass Model: ...\n\n@chz.chz\nclass Transformer(Model): ...\n\n@chz.chz\nclass Experiment:\n    model: Model = chz.field(blueprint_unspecified=Transformer)\n```\n\nSay you have an entrypoint that can run an experiment on an arbitrary model.\n\nBut in practice, you mostly want to run experiments on `Transformer`s. Rather than force your users\nto have to specify `model=Transformer` every time, you can use `blueprint_unspecified` to specify\nwhat `chz` should attempt to polymorphically construct if there isn't an argument specified.\n\n#### Confusion about `blueprint_unspecified` and `default/default_factory`\n\nUsers of `chz` are commonly confused by the relationship between `blueprint_unspecified` and\n`default/default_factory`. There is no relationship! `Blueprint` will **never** look at the value\nof `default/default_factory`. The primary interaction with `Blueprint`s is that their absence or\npresence will mark an argument as required or not.\n\nI recommend when in doubt not using `default/default_factory` for fields you wish to\npolymorphically construct.\n\n(One could ask why `chz` doesn't attempt to infer `blueprint_unspecified` from\n`default/default_factory`. This is a good question, but has a longer answer than is worth going\ninto here)\n\n## Presets or shared configuration\n\nPartial application gives you the ability to add presets.\nFor example, consider a typical experiment command line:\n```\n               ⤹ preset name\npython main.py small_gpt seed=217 name=just_a_lil_guy\n               ~~~~~~~~~\n```\n\nYou could mimic this with something like:\n\n```python\n@chz.chz\nclass Experiment: ...\n\npresets: dict[str, chz.Blueprint] = {\n    \"small_gpt\": chz.Blueprint(Experiment).apply(\n        {\"seed\": 0, \"model\": Transformer, \"model.n_layers\": 4},\n        layer_name=\"small gpt preset\",\n    ),\n    ...\n}\n\ndef main():\n    preset, *argv = sys.argv[1:]\n    blueprint = presets[preset].clone()\n    experiment = blueprint.make_from_argv(argv)\n```\n\nThe layer name is a subtle thing that's quite important, since adding `--help` to any command line\nwill show you exactly where each value being used is coming from:\n```\nArguments:\n  model                Model    Transformer (from small gpt preset)\n  model.encoding_name  str      'gpt2' (default)\n  model.n_layers       int      4 (from small gpt preset)\n  ...\n```\n\nI will some day add built-in support for presets in `chz` in the future.\nFor now, add your own extensions to manipulate `Blueprint`s.\n\n## Custom tooling\n\nThe `Blueprint` APIs are powerful. At OpenAI, there's a number of interesting custom tools\nthat build on top of the `Blueprint` APIs.\n\nIn particular, take a look at `Blueprint._make_lazy`. It's also worth familiarising yourself with\nthe `_ArgumentMap` class.\n\nDon't be scared by the underscores. Just add tests for the extensions you write.\n\n## Undocumented Blueprint features\n\nThere are a number of powerful `Blueprint` features that are not yet documented.\nThe good news is they all have tests that demonstrate their usage.\n\nI mention this here because if you hit some case you would like to express, it's possible that\nthere is a way to express this.\n\n## Blueprint algorithm\n\nThe source code is of course the best source of truth.\n\nVery very roughly, the algorithm is:\n\n1. Blueprint arguments are \"layers\" of dicts from arguments (possibly wildcard) to value provided.\n2. For a given parameter `foo.bar`, find the latest layer that has an argument matching\n   the `foo.bar` parameter.\n3. If there is no matching argument, check to see if we can call something to construct the value.\n\n    1. Check to see if there is a callable specified by `blueprint_unspecified`\n    2. Otherwise, use `chz`'s best guess (if `chz` has one)\n    3. Attempt to call this function, with recursive discovery of parameters.\n    4. If this doesn't work out, we'll use `default/default_factory` if it exists, if not, we'll\n       error for missing a required argument.\n\n4. If there is such an argument, we now attempt to use it!\n5. Check if it's a valid value for the parameter (or is a `Castable` that can be casted to the\n   correct type). This is done by checking if the value is of the right type and if there are not\n   additional subarguments specified.\n6. Otherwise, attempt to use the value as a callable we can call to construct the value (or a\n   `Castable` that can be casted to a callable).\n\n### [Next section — Serialisation](./06_serialisation.md)\n"
  },
  {
    "path": "docs/06_serialisation.md",
    "content": "## Serialisation and deserialisation\n\n`chz` will one day have a great story for versioned serialisation and deserialisation.\n\nThe main obstacle is that I'm busy and cowardly. Note also that it's easy to roll your own\n*un*versioned serialisation and deserialisation.\n\nThere are two utility functions in `chz` that you may find useful:\n`chz.beta_to_blueprint_values` and `chz.asdict`.\n\nExample:\n```python\nimport chz\n\n@chz.chz\nclass P:\n    x: float\n    y: float\n\n@chz.chz\nclass C:\n    s: str\n    p: P\n\nobj = C(s=\"foo\", p=P(x=1.0, y=2.0))\n\nprint(chz.beta_to_blueprint_values(obj))\n# {'s': 'foo', 'p': <class '__main__.P'>, 'p.x': 1.0, 'p.y': 2.0}\nprint(chz.Blueprint(type(obj)).apply(chz.beta_to_blueprint_values(obj)).make())\n# C(s='foo', p=P(x=1.0, y=2.0))\n\nprint(chz.asdict(obj))\n# {'s': 'foo', 'p': {'x': 1.0, 'y': 2.0}}\n```\n\n<details>\n<summary>Thoughts on pickle</summary>\n\nPickle is actually totally fine here, if you don't need human readability.\n\n`chz` is powerful enough that the ability to execute arbitrary code when deserialising is mostly\ngoing to be the same as `pickle`'s.\n\nThe other thing `pickle` doesn't give you is versioning. Here's a dumb hack that allows evolution\nfor basic field additions.\n\n```python\nimport pickle\nimport chz\nfrom chz.util import MISSING\n\n@chz.chz\nclass A:\n    a: int\n\nd = pickle.dumps(A(a=5))\n\n@chz.chz\nclass A:\n    a: int\n    b: bool = True\n\n    def __setstate__(self, state):\n        for field in self.__chz_fields__.values():\n            if field.x_name not in state:\n                if field._default is not MISSING:\n                    state[field.x_name] = field._default\n                if field._default_factory is not MISSING:\n                    state[field.x_name] = field._default_factory()\n        self.__dict__.update(state)\n        return self\n\nprint(pickle.loads(d))\n```\n</details>\n\n### [Next section — Post Init](./21_post_init.md)\n"
  },
  {
    "path": "docs/21_post_init.md",
    "content": "## No `__post_init__`; details and examples\n\nThere are a couple reasons why `chz` does not have a `__post_init__` equivalent for munging\nyour fields:\n- `__post_init__` has bad ergonomics with immutable objects (e.g. you need to use\n  `object.__setattr__` or some wrapper to mutate fields)\n- `__post_init__` encourages non-local initialisation behaviour that can be hard to reason about\n- `__post_init__`'s interaction with `super` is easy to mess up\n- `__post_init__`'s interaction with static type checkers is bad (if you use munging in\n  `__post_init__` to narrow types)\n\n### Caveat!!\n\nIf I were to rewrite `chz` from scratch, I would do things a little bit differently here (and I\nmay yet change some of this stuff). In a tale as old as time, I ended up where we are today via a\nseveral changes in response to several things over a period of several years. I have one more big\nchange planned at some point.\n\nMost notably, chz sort of predates PEP 681. E.g. the `X_` stuff that is most incompatible with\nPEP 681 was made inconvenient (it used to be `隐_`), but these things proved useful and I relented.\n\n### No `__post_init__`\n\nHere is some detail about the constraints posed by wanting static type checking to work.\nTake a look at the following example:\n\n```python\n# This will error since chz does not have a __post_init__\n@chz.chz\nclass Experiment:\n    name: str\n    steps: int\n    wandb_log_name: Optional[str] = None\n\n    def __post_init__(self):\n        if self.wandb_log_name is None:\n            self.wandb_log_name = self.name\n        raise NotImplementedError(\"chz does not actually have a __post_init__; this is a hypothetical\")\n```\n\nNow, anytime you try to use `experiment.wandb_log_name` you'll have to assert that it is not None\nbecause the type checker doesn't know that `__post_init__` will always set it. This is a sorry\nstate of affairs.\n\n### Solution that fully works with static type checkers\n\nCompare that to doing:\n\n```python\n# Recommended solution\n@chz.chz\nclass Experiment:\n    name: str\n    steps: int\n    wandb_log_name: Optional[str] = None\n\n    @chz.init_property\n    def wandb_log_name_value(self) -> str:\n        return self.wandb_log_name or self.name\n```\n\nNow use the `experiment.wandb_log_name_value` attribute instead. And if you mess it up and use the\nwrong one, the type checker will warn you, since the types are different!\n\nIt does suck that you have to come up with a different name like `wandb_log_name_value`.\nIf you do this, I recommend using the `_value` suffix for this.\n\n### Alternative \"magic prefix\" solution (does not fully work with static type checkers)\n\n```python\n# Alternative \"magic prefix\" solution\n@chz.chz\nclass Experiment:\n    name: str\n    steps: int\n    # chz will magically strip the \"X_\" in the __init__ parameter, and do the equivalent of\n    # `self.X_wandb_log_name = wandb_log_name` in __init__.\n    X_wandb_log_name: Optional[str] = None\n\n    @chz.init_property\n    def wandb_log_name(self) -> str:\n        return self.X_wandb_log_name or self.name\n\n\n# Now you can instantiate your object using `wandb_log_name` as a parameter\n# ...but static type checkers will complain about all direct instantiations of your object\nexperiment = Experiment(name=\"train_job\", steps=100, wandb_log_name=None)\nassert experiment.wandb_log_name == \"train_job\"\n```\n\nOne note for why this design: because definitions with the same name in classes clobber each other,\nthe field name needs to be different from the `init_property` name. Otherwise, `chz` is not able\nto access the `chz.field` spec / default value for the field.\n\n### Alternative \"munging\" solution (mostly works with static type checkers)\n\n\n```python\n# Alternative \"munging\" solution\n@chz.chz\nclass Experiment:\n    name: str\n    steps: int\n    # The value passed to the constructor will end up processed by the munger function\n    # wandb_log_name: str = chz.field(munger=lambda self, value: value or self.name)\n\n    # You can use the combinators in chz.mungers too, for example:\n    # wandb_log_name: Optional[str] = chz.field(munger=attr_if_none(\"name\"))\n\n    # If the value passed to `__init__` can be of another type (say None), you can use x_type so\n    # that type aware parsing continues to work\n    wandb_log_name: str = chz.field(munger=attr_if_none(\"name\"), x_type=str | None)\n\n\nexperiment = Experiment(name=\"train_job\", steps=100, wandb_log_name=None)\n```\n\nAs you can see, mungers can access any other attribute (which itself may be munged).\nchz will handle the ordering well.\n\nIf you do something recursive, you will get an error. The way to handle this is to explicitly\naccess the raw unmunged value, which you can find on `self` with the `X_` prefix.\n\nCurrently munged values are validated both before and after munging. This allows you to rely on\nvalidation during munging and as an invariant. The exact logic here may change in the future.\n\nNote that munging is best for defaulting logic. If you wish to simply change command line parsing\nlogic, consider using `blueprint_cast` and `__chz_cast__` instead.\n\n### Mechanics and `X_` prefix\n\nHere's what chz is basically doing under the hood. When it sees:\n\n```python\n@chz.chz\nclass Args:\n    foo: int = chz.field(default=1)\n```\nIt will convert this to:\n```python\n@chz.chz\nclass Args:\n    def __init__(self, foo: int = 1):\n        self.X_foo = foo\n        ...  # some other stuff, like validation\n\n    @chz.init_property\n    def foo(self) -> int:\n        return self.X_foo\n```\nIf there's a munger for the field `foo`, then the `init_property` added will do whatever the\nmunger does, instead of just returning `self.X_foo`.\n\nThis design has several advantages:\n- This handles any graph of field reference between munging in attributes well without e.g. forcing\n  ordering constraints on definitions\n- We preserve a lot of information about intent\n- We don't have to worry about non-idempotent munging, e.g. when doing `chz.replace`\n- In fact, we can even detect impure or non-idempotent `init_property`\n- Similarly, when deserialising, we could detect if `init_property` behaviour has changed\n- It keeps semantics relatively consistent between all options discussed on this page\n- It works well when you inherit from another chz class, but wish to override the field with an\n  `init_property`\n\n\n### [Next section — Field API](./22_field_api.md)\n"
  },
  {
    "path": "docs/22_field_api.md",
    "content": "## `chz.field`\n\n`chz.field` takes the following parameters:\n\n#### `default`\n\nLike with `dataclasses`, the default value for the field (if any).\n\n#### `default_factory`\n\nLike with `dataclasses`, a function that returns the default value for the field.\nUseful for mutable types, for instance, `default_factory=list`.\n\nNote: this does not interact with parametrisation / `Blueprint` / `blueprint_unspecified` / `meta_factory`.\nThe only thing that matters to parametrisation is presence or absence of a\n`default` or `default_factory`.\n\nPerhaps a better name would be `lazy_default` (but unfortunately, this is not supported by PEP 681,\nso static type checkers would lose the ability to understand the class).\n\n#### `validator`\n\nA function or list of functions that validate the field.\nField validators take two arguments: the instance of the class and the name of the field.\n\nSee also: [Validation](./03_validation.md)\n\n#### `repr`\n\nIf a boolean, whether or not to include the field in the `__repr__` of the class.\nIf a callable, will be used to construct the `repr` of the field.\n\n#### `doc`\n\nThe docstring for the field. Used in `--help`.\n\n#### `metadata`\n\nArbitrary user-defined metadata to attach to the field. Useful when extending `chz`.\n\n#### `munger`\n\nLets you adjust the value of a field. Essentially works the same as an init_property.\n\nSee also: [Alternative \"munging\" solution](./21_post_init.md)\n\n#### `x_type`\n\nUseful in combination with mungers. This specifies the type before munging that\nwill be used for parsing and type checking.\n\n#### `meta_factory`\n\nA metafactory represents the set of possible callables that can give us a value of a given type.\n\nDescribes the set of callables that are capable of returning a valid value for the field if given a\nnon-zero number of arguments.\n\nFor instance, the meta factory `chz.factories.subclass(Model)` is a description of the set of\ncallables that are capable of producing a `Model` (e.g. `{Transformer, Diffusion}`).\n\nThis was more useful in previous versions of `chz`, but now `chz` infers what you want to do\nmore reliably.\n\nSee also: the docs in `chz/factories.py`\n\n#### `blueprint_unspecified`\n\nThis is the default callable `Blueprint` may attempt to call to get a value of the expected type.\n\nSee [Blueprint](./05_blueprint.md#blueprint_unspecified)\n\n#### `blueprint_cast`\n\nA function that takes a str and returns an object. On failure to cast,\nit should raise `CastError`. Used to achieve custom parsing behaviour from the command\nline. Takes priority over the `__chz_cast__` dunder method (if present on the\ntarget type).\n\nSee also: [Casting](./04_command_line.md#casting)\n\n### [Next section — Philosophy](./91_philosophy.md)\n"
  },
  {
    "path": "docs/91_philosophy.md",
    "content": "# Philosophy\n\nThere are a few different ideas in `chz` and not all of them are equally valuable or well designed.\n\nIn particular, if you're using `chz` just for the object model, I'm not sure how useful it is\ncompared to other libraries (see [alternatives](./92_alternatives.md) for many options).\n\nConfiguration may never be easy! How you design your configurations may well be a bigger deal\nthan what configuration system you use. I have seen `chz` used in ways and at a scale that I\nnever envisioned.\n\nI hope this library is useful to you, and if not, I hope it encourages you to build the tools\nyou want (because you deserve them!)\n\nThe rest of this page is just rambling about things.\n\n## Modularity\n\nWe've had some monolithic configuration at OpenAI that made systems painful to work\nwith. Some ways to avoid this are deeper nested configuration hierarchies (so things can be\nself-contained / testable / reusable) and polymorphism (to reduce the cartesian product\nexplosion of config space).\n\nI hope that the `chz` classes you define can be reused across multiple entrypoints. At some\npoint, trying to do everything within a single entrypoint makes it hard to create a great\nexperience for all users. Sharing `chz` classes but specialising your own entrypoint with\npartial application seems like a good balance.\n\nAs a corollary, this has made me hesitant about adding things to `chz` class definitions that\nprimarily affect `Blueprint`s that use those classes (e.g. even `blueprint_unspecified`).\n\nOne downside of pushing for modularity and self-contained-ness is that it makes situations\nwhere you want to access fields of parents or siblings more awkward. I recommend at least using\nvalidators to ensure consistency. (There are some undocumented features that help with this,\nand I'll likely add more things here in the future)\n\nWildcards are a somewhat controversial feature, but they do at least lower the cost of having\nfairly deep configuration hierarchies.\n\n## Partial application\n\nI've seen some users have a bit of a learning curve when first encountering `chz.Blueprint`, but\nI'm actually fairly convinced repeated partial application and a one-time initialisation and\nvalidation is a good pattern in a lot of use cases (even if you don't use the command line stuff).\n\nIf you find yourself doing `chz.replace` a lot, ask yourself if you should be using `Blueprint`!\n\n## Managing state\n\n`chz` objects are immutable in the sense that you cannot reassign a field. This was a design\nchoice informed by scars from a previous system (and one that I think has been quite healthy).\n\nNote however that fields on `chz` objects can be mutable objects. It can be convenient to have state\non your configuration objects (e.g. this lets you reuse your polymorphic hierarchy). Currently,\nI recommend patterns like:\n\n```python\n@chz.chz\nclass Config:\n    state: types.SimpleNamespace\n\n    def get_state(self):\n        return self.state\n\n    def set_state(self, state):\n        if state is None:\n            self.state.value = 0\n        else:\n            self.state.value = state.value\n\n    def mutate(self):\n        self.state.value += 1\n```\n\nNot coincidentally, this will remind you of the pattern I use in `fiddle` (OpenAI's dataloader).\n\nCurrently, I leave the details of state management to downstream applications, but let me know\nif you think I should merge some of these things into `chz`.\n\n### [Next section — Alternatives](./92_alternatives.md)\n"
  },
  {
    "path": "docs/92_alternatives.md",
    "content": "## Alternatives\n\nThe most common question I get when someone first sees `chz` is \"...but have you heard about X?\"\n\nHere are some values for X that I have heard of. A lot of these libraries are great; `chz` builds\noff of ideas from multiple of them, executes some things differently, and also has some novel\nfeatures.\n\nAnyway, here is a list of things `chz` is not:\n\n(data model)\n\n- [dataclasses](https://docs.python.org/3/library/dataclasses.html)\n- [attrs](https://www.attrs.org/en/stable/)\n- [msgspec](https://jcristharif.com/msgspec/)\n- [pydantic](https://docs.pydantic.dev/)\n- hyperparams (internal)\n\n(serialisation)\n\n- [msgspec](https://jcristharif.com/msgspec/)\n- [cattrs](https://catt.rs/en/stable/readme.html#features)\n- [apischema](https://wyfo.github.io/apischema/)\n- [marshmallow](https://marshmallow.readthedocs.io/en/stable/)\n- [dacite](https://github.com/konradhalas/dacite)\n- [dataclasses_json](https://github.com/lidatong/dataclasses-json)\n- dump (internal)\n\n(cli)\n\n- [fire](https://github.com/google/python-fire)\n- [appeal](https://github.com/larryhastings/appeal)\n- [typer](https://typer.tiangolo.com/)\n- smokey (internal)\n- ein (internal)\n\n(runtime typing)\n\n- [typeguard](https://github.com/agronholm/typeguard)\n- [trycast](https://github.com/davidfstr/trycast)\n- [runtype](https://github.com/erezsh/runtype)\n\n(config solutions)\n\n- [hydra](https://hydra.cc/docs/intro/)\n- [the other fiddle](https://github.com/google/fiddle)\n- [gin](https://github.com/google/gin-config)\n- [hyperstate](https://github.com/cswinter/hyperstate)\n\nI don't think there's anything on this list that covers the same set of functionality I'm aiming\nfor here. I also have specific bones to pick with some of these libraries :-)\n\nLet me know if you think there's a feature that would be constructive to add!\n"
  },
  {
    "path": "docs/93_testimonials.md",
    "content": "## Testimonials\n\nUnsolicited feedback from users of `chz`. To be honest, I'm surprised people like it this much:\n\n> “pretty much perfectly what I always wanted for configs”\n\nszymon\n\n> “open-sourcing chz would increase annual world GDP growth > .1%”\n\ndaniel selsam\n\n> “chz was really really good insight :) ty for making a lot of things a lot simpler”\n\nhunter\n\n> “i really like chz. thank you for your service making it. at previous companies i feel like we struggled to create a similar config service”\n\nmostafa\n\n> “chz is amazing; i’ve never used it before, and after using it for 30 minutes i’m glad [we've switched to using chz]”\n\nalex nichol\n\n> “chz has quickly become one of my favorite Python libraries”\n\nchris koch\n\n> “chz is so good :froge-chefkiss: thanks for building it!!”\n\ndmed\n\n> “Hey, I just want to mention how much I love chz. Config management is one of the hardest things to get right on large software projects and I've never seen anything as good as chz!”\n\nadam lerer\n"
  },
  {
    "path": "pyproject.toml",
    "content": "[project]\nname = \"chz\"\nversion = \"0.4.0\"\ndescription = \"chz is a library for managing configuration\"\nreadme = \"README.md\"\nlicense = {file = \"LICENSE\"}\nauthors = [{name = \"Shantanu Jain\"}, {email = \"shantanu@openai.com\"}]\ndependencies = [\n    \"typing-extensions>=4.13\",\n]\nrequires-python = \">=3.11\"\n\n[project.urls]\nhomepage = \"https://github.com/openai/chz\"\nrepository = \"https://github.com/openai/chz\"\nchangelog = \"https://github.com/openai/chz/blob/main/CHANGELOG.md\"\n\n[build-system]\nbuild-backend = \"setuptools.build_meta\"\nrequires = [\"setuptools>=62.4\", \"wheel\"]\n\n[tool.setuptools.packages.find]\ninclude = [\"chz*\"]\n\n[tool.pytest.ini_options]\naddopts = [\n    \"--strict-markers\",\n    \"--strict-config\",\n    \"-p\", \"no:pytest_mock_resources\", \"-p\", \"no:httpx\", \"-p\", \"no:aiohttp\", \"-p\", \"no:faker\",\n    \"-p\", \"no:ddtrace\", \"-p\", \"no:ddtrace.pytest_bdd\", \"-p\", \"no:ddtrace.pytest_benchmark\",\n    \"-p\", \"no:hypothesispytest\", \"-p\", \"no:anyio\", \"-p\", \"no:benchmark\", \"-p\", \"no:pytest_mock\",\n    \"-p\", \"no:typeguard\", \"-p\", \"no:asyncio\",\n]\n\n[tool.mypy]\nstrict = true\n\ndisallow_untyped_decorators = true\ndisallow_any_generics = true\ndisallow_untyped_calls = true\n\ndisallow_subclassing_any = false\ndisallow_incomplete_defs = false\ndisallow_untyped_defs = false\nwarn_return_any = false\n\nwarn_unreachable = true\n\n[[tool.mypy.overrides]]\nmodule = [\"chz.tiepin\"]\nignore_errors = true\n\n[tool.coverage.report]\nexclude_lines = [\n    \"pragma: no cover\",\n    \"raise AssertionError\",\n    \"raise NotImplementedError\",\n    \"if MYPY\",\n    \"if TYPE_CHECKING\",\n    \"elif TYPE_CHECKING\",\n]\n"
  },
  {
    "path": "tests/test_blueprint.py",
    "content": "import pytest\n\nimport chz\nfrom chz.blueprint import (\n    Castable,\n    ConstructionException,\n    ExtraneousBlueprintArg,\n    InvalidBlueprintArg,\n    MissingBlueprintArg,\n)\n\n\ndef test_entrypoint():\n    def foo(a: int, b: str, c: float = 1.0):\n        return locals()\n\n    argv = [\"a=1\", \"b=str\", \"c=5\"]\n    assert chz.entrypoint(foo, argv=argv) == foo(1, \"str\", 5)\n\n    argv = [\"a=1\", \"b=str\"]\n    assert chz.entrypoint(foo, argv=argv) == foo(1, \"str\", 1)\n\n    # test allow_hyphens\n    assert chz.entrypoint(foo, argv=argv, allow_hyphens=True) == foo(1, \"str\", 1)\n\n    argv = [\"--a=1\", \"--b=str\", \"c=5\"]\n    with pytest.raises(\n        ExtraneousBlueprintArg,\n        match=(\n            r\"Extraneous argument '--a' to Blueprint for test_blueprint.test_entrypoint.<locals>.foo \\(from command line\\)\"\n            \"\\nDid you mean to use allow_hyphens=True in your entrypoint?\"\n        ),\n    ):\n        chz.entrypoint(foo, argv=argv)\n    assert chz.entrypoint(foo, argv=argv, allow_hyphens=True) == foo(1, \"str\", 5)\n\n    # test positional\n    argv = [\"a\", \"1\", \"b\", \"str\"]\n    with pytest.raises(\n        ValueError, match=\"Invalid argument 'a'. Specify arguments in the form key=value\"\n    ):\n        chz.entrypoint(foo, argv=argv)\n\n\ndef test_entrypoint_nested():\n    @chz.chz\n    class X:\n        a: int\n        b: str\n        c: float = 1.0\n\n    def main(x: X) -> list[X]:\n        return [x]\n\n    argv = [\"a=1\", \"b=str\", \"c=5\"]\n    assert chz.nested_entrypoint(main, argv=argv) == [X(a=1, b=\"str\", c=5)]\n\n    argv = [\"a=1\", \"b=str\"]\n    assert chz.nested_entrypoint(main, argv=argv) == [X(a=1, b=\"str\", c=1)]\n\n\ndef test_apply_strictness():\n    \"\"\"Test strictness of application when configured and non-strictness when not.\"\"\"\n\n    @chz.chz\n    class X:\n        hello: int = 5\n\n    # misspelled! No error on application, but error on make.\n    misspelled_bp = chz.Blueprint(X).apply({\"hllo\": 1})\n    with pytest.raises(ExtraneousBlueprintArg):\n        misspelled_bp.make()\n\n    # In strict mode, we get an error on apply.\n    with pytest.raises(ExtraneousBlueprintArg):\n        chz.Blueprint(X).apply({\"hllo\": 1}, strict=True)\n\n\ndef test_basic_function_blueprint():\n    def foo(a: int, b: int | str, c: bool = False, d: bytes = b\"\"):\n        return locals()\n\n    # regular\n    assert chz.Blueprint(foo).apply({\"a\": 1, \"b\": \"str\"}).make() == foo(1, \"str\", False, b\"\")\n    # default\n    assert chz.Blueprint(foo).apply({\"a\": 1, \"b\": \"2\", \"c\": True}).make() == foo(1, \"2\", True, b\"\")\n    # clobbered\n    assert chz.Blueprint(foo).apply({\"a\": 1, \"b\": \"str\"}).apply({\"a\": 2}).make() == foo(\n        2, \"str\", False, b\"\"\n    )\n\n    # castable\n    assert chz.Blueprint(foo).apply(\n        {\"a\": Castable(\"1\"), \"b\": Castable(\"str\"), \"c\": Castable(\"True\")}\n    ).make() == foo(1, \"str\", True, b\"\")\n\n    with pytest.raises(TypeError, match=\"Expected 'a' to be int, got str\"):\n        chz.Blueprint(foo).apply({\"a\": \"asdf\"}).make()\n    with pytest.raises(\n        InvalidBlueprintArg,\n        match=(\n            \"- Failed to interpret it as a value:\\n\"\n            \"Could not cast 'asdf' to int\\n\\n\"\n            \"- Failed to interpret it as a factory for polymorphic construction:\\n\"\n            \"No subclass of int named 'asdf'\"\n        ),\n    ):\n        chz.Blueprint(foo).apply({\"a\": Castable(\"asdf\")}).make()\n\n\ndef test_basic_class_blueprint():\n    @chz.chz\n    class X:\n        a: int\n        b: str\n\n    # regular\n    assert chz.Blueprint(X).apply({\"a\": 1, \"b\": \"str\"}).make() == X(a=1, b=\"str\")\n    # clobbered\n    assert chz.Blueprint(X).apply({\"a\": 1, \"b\": \"str\"}).apply({\"a\": 2}).make() == X(a=2, b=\"str\")\n\n    # castable\n    assert chz.Blueprint(X).apply({\"a\": Castable(\"1\"), \"b\": \"str\"}).make() == X(a=1, b=\"str\")\n\n    with pytest.raises(TypeError, match=\"Expected 'a' to be int, got str\"):\n        chz.Blueprint(X).apply({\"a\": \"asdf\"}).make()\n    with pytest.raises(\n        InvalidBlueprintArg,\n        match=(\n            \"- Failed to interpret it as a value:\\n\"\n            \"Could not cast 'asdf' to int\\n\\n\"\n            \"- Failed to interpret it as a factory for polymorphic construction:\\n\"\n            \"No subclass of int named 'asdf'\"\n        ),\n    ):\n        chz.Blueprint(X).apply({\"a\": Castable(\"asdf\")}).make()\n\n\ndef test_blueprint_unused():\n    def foo(alpha: int, beta: str = \"\"):\n        return locals()\n\n    with pytest.raises(\n        ExtraneousBlueprintArg,\n        match=\"Extraneous argument 'missing' to Blueprint for test_blueprint.test_blueprint_unused.<locals>.foo\",\n    ):\n        assert chz.Blueprint(foo).apply({\"alpha\": 1, \"missing\": \"oops\"}).make()\n\n    @chz.chz\n    class Foo:\n        alpha: int\n        beta: str\n\n    assert chz.Blueprint(Foo).apply({\"alpha\": 1, \"beta\": \"str\"}).make() == Foo(alpha=1, beta=\"str\")\n\n    with pytest.raises(\n        ExtraneousBlueprintArg,\n        match=\"Extraneous argument 'missing' to Blueprint for test_blueprint.test_blueprint_unused.<locals>.Foo\",\n    ):\n        assert chz.Blueprint(Foo).apply({\"alpha\": 1, \"missing\": \"oops\"}).make()\n\n    with pytest.raises(ExtraneousBlueprintArg, match=r\"Did you mean 'alpha'\"):\n        assert chz.Blueprint(Foo).apply({\"alpha\": 1, \"aleph\": \"oops\"}).make()\n\n    @chz.chz\n    class Bar:\n        foo: Foo\n        gamma: int\n\n    assert (\n        chz.Blueprint(Bar).apply({\"foo.alpha\": 1, \"foo.beta\": \"str\", \"gamma\": 1}).make()\n    ) == Bar(foo=Foo(alpha=1, beta=\"str\"), gamma=1)\n\n    with pytest.raises(ExtraneousBlueprintArg, match=r\"Did you mean 'foo\\.alpha'\"):\n        assert (\n            chz.Blueprint(Bar)\n            .apply({\"foo.alpha\": 1, \"foo.beta\": \"str\", \"foo.aleph\": \"oops\"})\n            .make()\n        )\n    with pytest.raises(\n        ExtraneousBlueprintArg,\n        match=r\"Did you get the nesting wrong, maybe you meant 'foo.alpha'\\?\",\n    ):\n        assert (\n            chz.Blueprint(Bar)\n            .apply({\"foo.alpha\": 1, \"foo.beta\": \"str\", \"alpha\": \"oops\", \"gamma\": 1})\n            .make()\n        )\n\n    assert (\n        chz.Blueprint(Bar).apply({\"...alpha\": 1, \"...beta\": \"str\", \"...gamma\": 1}).make()\n    ) == Bar(foo=Foo(alpha=1, beta=\"str\"), gamma=1)\n\n    with pytest.raises(ExtraneousBlueprintArg, match=r\"Did you mean '\\.\\.\\.alpha'\"):\n        assert chz.Blueprint(Bar).apply({\"...alpha\": 1, \"...aleph\": \"oops\"}).make()\n\n    with pytest.raises(ExtraneousBlueprintArg, match=r\"Did you mean '\\.\\.\\.foo\\.\\.\\.alpha'\"):\n        assert chz.Blueprint(Bar).apply({\"...alpha\": 1, \"...foo...aleph\": \"oops\"}).make()\n\n    @chz.chz\n    class Baz:\n        bar: Bar\n        delta: int\n\n    assert (\n        chz.Blueprint(Baz)\n        .apply({\"bar.foo.alpha\": 1, \"bar.foo.beta\": \"str\", \"bar.gamma\": 1, \"delta\": 1})\n        .make()\n    ) == Baz(bar=Bar(foo=Foo(alpha=1, beta=\"str\"), gamma=1), delta=1)\n\n    with pytest.raises(ExtraneousBlueprintArg, match=r\"Did you mean 'bar\\.foo\\.alpha'\"):\n        assert (\n            chz.Blueprint(Baz)\n            .apply({\"bar.foo.alpha\": 1, \"bar.foo.beta\": \"str\", \"bar.foo.aleph\": \"oops\"})\n            .make()\n        )\n\n    with pytest.raises(ExtraneousBlueprintArg, match=r\"Did you mean 'bar\\.foo\\.\\.\\.alpha'\"):\n        assert chz.Blueprint(Baz).apply({\"...alpha\": 1, \"bar.foo...aleph\": \"oops\"}).make()\n\n    with pytest.raises(ExtraneousBlueprintArg, match=r\"Did you mean '\\.\\.\\.bar\\.\\.\\.foo\\.alpha'\"):\n        assert chz.Blueprint(Baz).apply({\"...alpha\": 1, \"...bar...foo.aleph\": \"oops\"}).make()\n\n    with pytest.raises(ExtraneousBlueprintArg, match=r\"Did you mean '\\.\\.\\.bar\\.\\.\\.foo\\.alpha'\"):\n        assert chz.Blueprint(Baz).apply({\"...alpha\": 1, \"...bar...foZ.aleph\": \"oops\"}).make()\n\n\ndef test_blueprint_unused_nested_default():\n    @chz.chz\n    class Sub:\n        alpha: int = 1\n        beta: str = \"str\"\n\n    @chz.chz\n    class Main:\n        sub: Sub\n        gamma: int\n\n    with pytest.raises(ExtraneousBlueprintArg, match=r\"Did you mean 'sub\\.beta'\"):\n        chz.Blueprint(Main).apply({\"sub.bata\": \"str\"}).make()\n\n\ndef test_blueprint_missing_args():\n    @chz.chz\n    class Alpha:\n        alpha: int\n        beta: str\n\n    with pytest.raises(\n        MissingBlueprintArg, match=r\"Missing required arguments for parameter\\(s\\): alpha, beta\"\n    ):\n        chz.Blueprint(Alpha).make()\n\n    @chz.chz\n    class Main:\n        alpha: Alpha\n\n    with pytest.raises(\n        MissingBlueprintArg,\n        match=r\"Missing required arguments for parameter\\(s\\): alpha.alpha, alpha.beta\",\n    ):\n        chz.Blueprint(Main).make()\n\n    with pytest.raises(\n        MissingBlueprintArg, match=r\"Missing required arguments for parameter\\(s\\): alpha.beta\"\n    ):\n        chz.Blueprint(Main).apply({\"alpha.alpha\": 1}).make()\n\n    @chz.chz\n    class MainDefault:\n        alpha: Alpha = chz.field(default=Alpha(alpha=1, beta=\"str\"))\n        other: int\n\n    with pytest.raises(\n        MissingBlueprintArg, match=r\"Missing required arguments for parameter\\(s\\): other\"\n    ):\n        chz.Blueprint(MainDefault).make()\n\n\ndef three_item_dataset(first: str = \"a\", second: str = \"b\", third: str = \"c\") -> list[str]:\n    return [first, second, third]\n\n\n@chz.chz\nclass Model:\n    family: str\n    n_layers: int\n    salt: bytes = b\"salt\"\n\n\n@chz.chz\nclass Experiment:\n    model: Model\n    dataset: list[str] = chz.field(doc=\"Yummy data!\")\n\n\ndef test_nested_construction():\n    expected = Experiment(\n        model=Model(family=\"linear\", n_layers=1, salt=b\"0000\"), dataset=[\"a\", \"b\", \"c\"]\n    )\n    assert (\n        chz.Blueprint(Experiment)\n        .apply(\n            {\n                \"model.family\": \"linear\",\n                \"model.n_layers\": 1,\n                \"model.salt\": b\"0000\",\n                \"dataset\": [\"a\", \"b\", \"c\"],\n            }\n        )\n        .make()\n        == expected\n    )\n\n\ndef test_nested_construction_with_default_value():\n    expected = Experiment(\n        model=Model(family=\"linear\", n_layers=1, salt=b\"salt\"), dataset=[\"a\", \"b\", \"c\"]\n    )\n    assert (\n        chz.Blueprint(Experiment)\n        .apply({\"model.family\": \"linear\", \"model.n_layers\": 1, \"dataset\": [\"a\", \"b\", \"c\"]})\n        .make()\n        == expected\n    )\n\n\ndef test_nested_construction_with_factory_dataset():\n    expected = Experiment(\n        model=Model(family=\"linear\", n_layers=1, salt=b\"0000\"), dataset=[\"first\", \"b\", \"third\"]\n    )\n    assert (\n        chz.Blueprint(Experiment)\n        .apply(\n            {\n                \"model.family\": \"linear\",\n                \"model.n_layers\": 1,\n                \"model.salt\": b\"0000\",\n                \"dataset\": three_item_dataset,\n                \"dataset.first\": \"first\",\n                \"dataset.third\": \"third\",\n            }\n        )\n        .make()\n        == expected\n    )\n\n\ndef test_nested_construction_with_wildcards():\n    expected = Experiment(\n        model=Model(family=\"linear\", n_layers=1, salt=b\"0000\"), dataset=[\"first\", \"b\", \"third\"]\n    )\n    assert (\n        chz.Blueprint(Experiment)\n        .apply(\n            {\n                \"...family\": \"linear\",\n                \"...n_layers\": 1,\n                \"...salt\": b\"0000\",\n                \"...dataset\": three_item_dataset,\n                \"...first\": \"first\",\n                \"...third\": \"third\",\n            }\n        )\n        .make()\n        == expected\n    )\n\n    assert (\n        chz.Blueprint(Experiment)\n        .apply(\n            {\n                \"...family\": \"linear\",\n                \"...n_layers\": 1,\n                \"...salt\": b\"0000\",\n                \"...dataset\": Castable(\"three_item_dataset\"),\n                \"dataset...first\": \"first\",\n                \"...third\": \"third\",\n            }\n        )\n        .make()\n        == expected\n    )\n\n    assert (\n        chz.Blueprint(Experiment)\n        .apply(\n            {\n                \"...family\": \"linear\",\n                \"...n_layers\": 1,\n                \"...salt\": b\"0000\",\n                \"...dataset\": Castable(\"three_item_dataset\"),\n                \"...dataset...first\": \"first\",  # even more wildcard\n                \"...third\": \"third\",\n            }\n        )\n        .make()\n        == expected\n    )\n\n\ndef test_nested_all_defaults():\n    @chz.chz\n    class X:\n        value: int = 0\n\n    @chz.chz\n    class Y:\n        x: X\n        y: int\n\n    assert chz.Blueprint(Y).apply({\"y\": 5}).make() == Y(x=X(value=0), y=5)\n\n\ndef test_nested_not_all_defaults():\n    @chz.chz\n    class X:\n        value: int\n\n    @chz.chz\n    class Y:\n        x: X\n\n    @chz.chz\n    class Parent:\n        child: Y | None = None\n\n    assert chz.Blueprint(Parent).make() == Parent(child=None)\n\n\ndef test_nested_all_defaults_primitive():\n    @chz.chz\n    class X:\n        value: int\n\n    @chz.chz\n    class Y:\n        x: X | None = None\n\n    assert chz.Blueprint(Y).make() == Y(x=None)\n\n\ndef test_nested_all_defaults_unspecified_nested():\n    @chz.chz\n    class Value:\n        value: int\n\n    @chz.chz\n    class A:\n        value: Value\n\n    @chz.chz\n    class B(A):\n        value: Value = Value(value=1)\n\n    @chz.chz\n    class Main:\n        a: A = chz.field(blueprint_unspecified=B)\n\n    assert chz.Blueprint(Main).make() == Main(a=B(value=Value(value=1)))\n\n\ndef test_nested_construction_with_default_factory():\n    @chz.chz\n    class ChildV1:\n        required1: int\n\n    @chz.chz\n    class ChildV2:\n        required2: int\n\n    @chz.chz\n    class Parent:\n        child_v1: ChildV1 = chz.field(default_factory=lambda: ChildV1(required1=1))\n        child_v2: ChildV2 = chz.field(default_factory=lambda: ChildV2(required2=2))\n\n    assert chz.Blueprint(Parent).apply({}).make() == Parent(\n        child_v1=ChildV1(required1=1), child_v2=ChildV2(required2=2)\n    )\n\n\ndef test_help():\n    assert (\n        chz.Blueprint(Experiment).get_help()\n        == \"\"\"\\\nWARNING: Missing required arguments for parameter(s): model.family, model.n_layers, dataset\n\nEntry point: test_blueprint:Experiment\n\nArguments:\n  model           test_blueprint:Model  -\n  model.family    str                   -\n  model.n_layers  int                   -\n  model.salt      bytes                 b'salt' (default)\n  dataset         list[str]             -                  Yummy data!\n\"\"\"\n    )\n\n    assert (\n        chz.Blueprint(Experiment).apply({\"model.family\": \"gpt\"}, layer_name=\"gpt config\").get_help()\n        == \"\"\"\\\nWARNING: Missing required arguments for parameter(s): model.n_layers, dataset\n\nEntry point: test_blueprint:Experiment\n\nArguments:\n  model           test_blueprint:Model  test_blueprint:Model (meta_factory)\n  model.family    str                   'gpt' (from gpt config)\n  model.n_layers  int                   -\n  model.salt      bytes                 b'salt' (default)\n  dataset         list[str]             -                                    Yummy data!\n\"\"\"\n    )\n\n    @chz.chz\n    class Foo:\n        a: int = chz.field(default_factory=lambda: 1 + 1)\n\n    assert (\n        chz.Blueprint(Foo).get_help()\n        == \"\"\"\\\nEntry point: test_blueprint:test_help.<locals>.Foo\n\nArguments:\n  a  int  (lambda: 1 + 1)() (default)\n\"\"\"\n    )\n\n    assert (\n        chz.Blueprint(Foo).apply({\"a\": Castable(\"2\")}, layer_name=\"preset\").get_help()\n        == \"\"\"\\\nEntry point: test_blueprint:test_help.<locals>.Foo\n\nArguments:\n  a  int  2 (from preset)\n\"\"\"\n    )\n\n\ndef test_logical_name_blueprint():\n    @chz.chz\n    class X:\n        X_seed1: int\n        X_seed2: int\n\n        @property\n        def seed1(self):\n            return self.X_seed1 + 100\n\n        @property\n        def seed2(self):\n            return self.X_seed2 + 100\n\n    x = chz.Blueprint(X).apply({\"seed1\": 1, \"seed2\": 2}).make()\n    assert x.seed1 == 101\n    assert x.seed2 == 102\n    assert x == X(seed1=1, seed2=2)\n\n\ndef test_blueprint_unpack_kwargs():\n    from typing import TypedDict, Unpack\n\n    class Args(TypedDict):\n        a: int\n        b: str\n\n    def foo(**kwargs: Unpack[Args]) -> Args:\n        return Args(**kwargs)\n\n    assert chz.Blueprint(foo).apply(\n        {\"a\": chz.Castable(\"1\"), \"b\": chz.Castable(\"2\")}\n    ).make() == Args(a=1, b=\"2\")\n\n    with pytest.raises(\n        MissingBlueprintArg, match=r\"Missing required arguments for parameter\\(s\\): b\"\n    ):\n        chz.Blueprint(foo).apply({\"a\": 1}).make()\n\n\ndef test_blueprint_castable_but_subpaths():\n    @chz.chz\n    class A:\n        field: str\n\n    @chz.chz\n    class Main:\n        a: A = chz.field(blueprint_cast=lambda s: A(field=s))\n\n    with pytest.raises(\n        InvalidBlueprintArg,\n        match=r\"\"\"Could not interpret argument 'works' provided for param 'a'...\n\n- Failed to interpret it as a value:\nNot a value, since subparameters were provided \\(e.g. 'a.field'\\)\n\n- Failed to interpret it as a factory for polymorphic construction:\nNo subclass of test_blueprint:test_blueprint_castable_but_subpaths.<locals>.A named 'works'\"\"\",\n    ):\n        chz.Blueprint(Main).apply({\"a\": Castable(\"works\"), \"a.field\": Castable(\"field\")}).make()\n\n\ndef test_blueprint_value_but_subpaths():\n    @chz.chz\n    class A:\n        field: int\n\n    @chz.chz\n    class Main:\n        a: A | None\n\n    with pytest.raises(\n        InvalidBlueprintArg,\n        match=r\"Could not interpret None provided for param 'a' as a value, \"\n        r\"since subparameters were provided \\(e.g. 'a.field'\\)\",\n    ):\n        chz.Blueprint(Main).apply({\"a\": None, \"a.field\": Castable(\"field\")}).make()\n\n\ndef test_blueprint_apply_subpath():\n    @chz.chz\n    class A:\n        field: int\n\n    @chz.chz\n    class AA:\n        a: A\n\n    @chz.chz\n    class Main:\n        a: AA\n        field: int = 0\n\n    assert chz.Blueprint(Main).apply({\"field\": 1}, subpath=\"a.a\").make() == Main(\n        a=AA(a=A(field=1)), field=0\n    )\n\n    assert chz.Blueprint(Main).apply({\"...field\": 1}, subpath=\"\").make() == Main(\n        a=AA(a=A(field=1)), field=1\n    )\n    assert chz.Blueprint(Main).apply({\"...field\": 1}, subpath=\"a\").make() == Main(\n        a=AA(a=A(field=1)), field=0\n    )\n\n    with pytest.raises(ExtraneousBlueprintArg, match=r\"Extraneous argument 'b.field' to Blueprint\"):\n        chz.Blueprint(Main).apply({\"field\": 1}, subpath=\"b\").make()\n\n\ndef test_blueprint_enum_all_defaults():\n    import enum\n\n    class E(enum.StrEnum):\n        foo = enum.auto()\n        bar = enum.auto()\n\n    @chz.chz\n    class Inner:\n        e: E = E.foo\n\n    @chz.chz\n    class Args:\n        e: E = E.foo\n        inner: Inner\n\n    assert chz.Blueprint(Args).make() == Args(e=E.foo, inner=Inner(e=E.foo))\n    assert chz.Blueprint(Args).apply({\"inner.e\": Castable(\"bar\")}).make() == Args(\n        e=E.foo, inner=Inner(e=E.bar)\n    )\n\n\ndef test_blueprint_functools_partial():\n    import functools\n\n    def foo(a: int = 1, b: int = 2):\n        return a, b\n\n    partial_foo = functools.partial(foo, a=3, b=4)\n\n    assert chz.Blueprint(partial_foo).apply({\"a\": 5}).make() == (5, 4)\n\n    @chz.chz\n    class Foo:\n        a: int = 1\n        b: int = 2\n\n    partial_foo = functools.partial(Foo, a=3, b=4)\n\n    assert chz.Blueprint(partial_foo).apply({\"a\": 5}).make() == Foo(a=5, b=4)\n\n    @chz.chz\n    class A:\n        a: str = \"a\"\n\n    @chz.chz\n    class B(A):\n        a: str = \"b\"\n\n    @chz.chz\n    class Main:\n        a: A = chz.field(blueprint_unspecified=B)\n        field: int = 0\n\n    partial_main = functools.partial(Main, field=1)\n\n    assert chz.Blueprint(partial_main).make() == Main(a=B(a=\"b\"), field=1)\n\n\ndef test_blueprint_unspecified_functools_partial():\n    @chz.chz\n    class A:\n        field: int\n        typ: str = \"a\"\n\n    @chz.chz\n    class B(A):\n        typ: str = \"b\"\n\n    import functools\n\n    @chz.chz\n    class Main:\n        a: A = chz.field(blueprint_unspecified=functools.partial(B, field=1))\n\n    assert chz.Blueprint(Main).make() == Main(a=B(field=1))\n\n    @chz.chz\n    class C(A):\n        missing: int\n\n    @chz.chz\n    class Main:\n        a: A = chz.field(blueprint_unspecified=functools.partial(C, field=2))\n\n    with pytest.raises(\n        MissingBlueprintArg, match=r\"Missing required arguments for parameter\\(s\\): a.missing\"\n    ):\n        chz.Blueprint(Main).make()\n\n\ndef test_blueprint_positional_only():\n    def pos_only(a: int = 42, /):\n        return a\n\n    assert chz.entrypoint(pos_only, argv=[]) == 42\n\n    def pos_only_no_default(a: int, /):\n        return a\n\n    with pytest.raises(\n        MissingBlueprintArg, match=r\"Missing required arguments for parameter\\(s\\): 0\"\n    ):\n        chz.entrypoint(pos_only_no_default, argv=[])\n\n    assert chz.entrypoint(pos_only_no_default, argv=[\"0=1\"]) == 1\n\n\ndef test_blueprint_args_kwargs():\n    def args_only(*args: int):\n        return args\n\n    assert chz.entrypoint(args_only, argv=[]) == ()\n    assert chz.entrypoint(args_only, argv=[\"0=1\", \"1=2\"]) == (1, 2)\n\n    def kwargs_only(**kwargs: int):\n        return kwargs\n\n    assert chz.entrypoint(kwargs_only, argv=[]) == {}\n    assert chz.entrypoint(kwargs_only, argv=[\"a=1\", \"b=2\"]) == {\"a\": 1, \"b\": 2}\n\n    def pos_only_args_kwargs(x: int, /, *args: int, **kwargs: int):\n        return x, args, kwargs\n\n    with pytest.raises(\n        MissingBlueprintArg, match=r\"Missing required arguments for parameter\\(s\\): 0\"\n    ):\n        chz.entrypoint(pos_only_args_kwargs, argv=[])\n\n    assert chz.entrypoint(pos_only_args_kwargs, argv=[\"0=1\", \"1=2\"]) == (1, (2,), {})\n    assert chz.entrypoint(pos_only_args_kwargs, argv=[\"0=1\", \"1=2\", \"a=3\", \"b=4\"]) == (\n        1,\n        (2,),\n        {\"a\": 3, \"b\": 4},\n    )\n\n    def poskw_args_kwargs(x: int, *args: int, **kwargs: int):\n        return x, args, kwargs\n\n    with pytest.raises(\n        MissingBlueprintArg, match=r\"Missing required arguments for parameter\\(s\\): x\"\n    ):\n        chz.entrypoint(poskw_args_kwargs, argv=[])\n    with pytest.raises(\n        ConstructionException,\n        match=r\"both positional-or-keyword and variadic positional parameters\",\n    ):\n        chz.entrypoint(poskw_args_kwargs, argv=[\"x=1\", \"0=2\"])\n\n    assert chz.entrypoint(poskw_args_kwargs, argv=[\"x=1\"]) == (1, (), {})\n"
  },
  {
    "path": "tests/test_blueprint_cast.py",
    "content": "# Note test_blueprint_meta_factory.py also contains tests relevant to casting\nfrom typing import Literal\n\nimport pytest\n\nimport chz\nfrom chz.blueprint import Castable, InvalidBlueprintArg\nfrom chz.tiepin import CastError\n\n\ndef test_castable():\n    @chz.chz\n    class A:\n        a: bool | Literal[\"both\"]\n\n    assert chz.Blueprint(A).apply({\"a\": Castable(\"True\")}).make() == A(a=True)\n    assert chz.Blueprint(A).apply({\"a\": Castable(\"False\")}).make() == A(a=False)\n    assert chz.Blueprint(A).apply({\"a\": Castable(\"both\")}).make() == A(a=\"both\")\n\n    with pytest.raises(\n        InvalidBlueprintArg,\n        match=r\"Could not cast 'maybe' to (Union\\[bool, Literal\\['both'\\]\\]|bool \\| typing.Literal\\['both'\\])\",\n    ):\n        assert chz.Blueprint(A).apply({\"a\": Castable(\"maybe\")}).make()\n\n    @chz.chz\n    class B:\n        b: str | None\n\n    assert chz.Blueprint(B).apply({\"b\": Castable(\"None\")}).make() == B(b=None)\n    assert chz.Blueprint(B).apply({\"b\": Castable(\"Nona\")}).make() == B(b=\"Nona\")\n\n    @chz.chz\n    class C:\n        a: tuple[int, ...]\n        b: tuple[str, int, str | None]\n\n    assert chz.Blueprint(C).apply({\"a\": Castable(\"1,2,3\"), \"b\": Castable(\"1,2,None\")}).make() == C(\n        a=(1, 2, 3), b=(\"1\", 2, None)\n    )\n    assert chz.Blueprint(C).apply({\"a\": Castable(\"1,2,3\"), \"b\": Castable(\"1,2,3\")}).make() == C(\n        a=(1, 2, 3), b=(\"1\", 2, \"3\")\n    )\n    with pytest.raises(\n        InvalidBlueprintArg,\n        match=(\n            \"- Failed to interpret it as a value:\\n\"\n            r\"Could not cast '1,2,3,4' to tuple\\[str, int, str \\| None\\] because of length mismatch\"\n            \"\\n\\n- Failed to interpret it as a factory for polymorphic construction:\\n\"\n            r\"No subclass of tuple\\[str, int, str \\| None\\] named '1,2,3,4' \\(invalid identifier\\)\"\n        ),\n    ):\n        assert chz.Blueprint(C).apply({\"a\": (1,), \"b\": Castable(\"1,2,3,4\")}).make()\n    with pytest.raises(\n        InvalidBlueprintArg, match=r\"No subclass of tuple\\[int, \\.\\.\\.\\] named 'asdf'\"\n    ):\n        assert chz.Blueprint(C).apply({\"a\": Castable(\"asdf\"), \"b\": (\"1\", 2, \"3\")}).make()\n\n    @chz.chz\n    class D:\n        a: Literal[\"a\", 123]\n\n    assert chz.Blueprint(D).apply({\"a\": Castable(\"a\")}).make() == D(a=\"a\")\n    assert chz.Blueprint(D).apply({\"a\": Castable(\"123\")}).make() == D(a=123)\n    with pytest.raises(InvalidBlueprintArg, match=r\"Could not cast 'b' to Literal\\['a', 123\\]\"):\n        assert chz.Blueprint(D).apply({\"a\": Castable(\"b\")}).make()\n\n\ndef test_castable_object_str():\n    @chz.chz\n    class A:\n        a: object\n\n    assert chz.Blueprint(A).apply({\"a\": Castable(\"1\")}).make() == A(a=1)\n    assert chz.Blueprint(A).apply({\"a\": Castable(\"1a\")}).make() == A(a=\"1a\")\n\n\ndef test_meta_factory_cast_unspecified():\n    @chz.chz\n    class Base:\n        value: int\n        cls: int = 0\n\n        @classmethod\n        def __chz_cast__(cls, data: str):  # noqa: ANN206\n            return Base(value=int(data))\n\n    @chz.chz\n    class DefaultChild(Base):\n        value: int\n        cls: int = 2\n\n        @classmethod\n        def __chz_cast__(cls, data: str):  # noqa: ANN206\n            return DefaultChild(value=int(data))\n\n    @chz.chz\n    class X:\n        a: Base = chz.field(\n            meta_factory=chz.factories.subclass(base_cls=Base, default_cls=DefaultChild)\n        )\n\n    assert chz.Blueprint(X).apply({\"a\": Castable(\"3\")}).make() == X(a=DefaultChild(value=3, cls=2))\n\n\ndef test_chz_cast_dunder():\n    from dataclasses import dataclass\n\n    @dataclass\n    class Duration:\n        seconds: int\n\n        @classmethod\n        def __chz_cast__(cls, value: str):  # noqa: ANN206\n            try:\n                return Duration(int(value.strip(\"hms\")) * {\"h\": 3600, \"m\": 60, \"s\": 1}[value[-1]])\n            except Exception as e:\n                raise CastError(f\"Could not cast {value!r} to {cls.__name__}\") from e\n\n    @chz.chz\n    class X:\n        t: Duration\n\n    assert chz.Blueprint(X).apply({\"t\": Castable(\"1h\")}).make() == X(t=Duration(3600))\n    assert chz.Blueprint(X).apply({\"t\": Castable(\"1m\")}).make() == X(t=Duration(60))\n    with pytest.raises(\n        InvalidBlueprintArg,\n        match=\"Failed to interpret it as a value:\\nCould not cast 'yikes' to Duration\",\n    ):\n        chz.Blueprint(X).apply({\"t\": Castable(\"yikes\")}).make()\n\n    @chz.chz\n    class Y:\n        t1: str | Duration\n        t2: Duration | str\n        t3: str | Duration\n        t4: Duration | str\n\n    assert chz.Blueprint(Y).apply(\n        {\n            \"t1\": Castable(\"1h\"),\n            \"t2\": Castable(\"1m\"),\n            \"t3\": Castable(\"yikes\"),\n            \"t4\": Castable(\"yikes\"),\n        }\n    ).make() == Y(t1=Duration(3600), t2=Duration(60), t3=\"yikes\", t4=\"yikes\")\n\n\ndef test_cast_per_field():\n    @chz.chz\n    class X:\n        a: str = chz.field(blueprint_cast=lambda x: x[0])\n        b: str = chz.field(blueprint_cast=lambda x: x[1])\n\n    assert chz.Blueprint(X).apply({\"a\": Castable(\"abc\"), \"b\": Castable(\"abc\")}).make() == X(\n        a=\"a\", b=\"b\"\n    )\n"
  },
  {
    "path": "tests/test_blueprint_errors.py",
    "content": "import pytest\n\nimport chz\nfrom chz.blueprint import ConstructionException, ExtraneousBlueprintArg\n\n\ndef test_target_bad_signature():\n    def bad(a: int, b: str): ...\n\n    bad.__text_signature__ = \"not a signature\"\n\n    with pytest.raises(ConstructionException, match=r\"Failed to get signature for bad\"):\n        chz.entrypoint(bad, argv=[])\n\n\ndef test_target_just_plain_old_bad():\n    with pytest.raises(ValueError, match=\"42 is not callable\"):\n        chz.entrypoint(42, argv=[])\n\n\ndef test_target_no_params_extraneous():\n    def good(): ...\n\n    with pytest.raises(\n        ExtraneousBlueprintArg, match=r\"Extraneous argument 'a' to Blueprint for .*\\.good\"\n    ):\n        chz.entrypoint(good, argv=[\"a=42\"])\n\n\ndef test_nested_target_default_values():\n    @chz.chz\n    class Main:\n        a: int\n\n    def good(m: Main, b=\"asdf\", c=1):\n        return m.a\n\n    assert chz.nested_entrypoint(good, argv=[\"a=42\"]) == 42\n\n    def bad(m: Main, b, c=1): ...\n\n    with pytest.raises(\n        ValueError,\n        match=r\"Nested entrypoints must take at most one argument without a default\",\n    ):\n        chz.nested_entrypoint(bad, argv=[\"a=42\"])\n\n\ndef test_blueprint_extraneous_valid_parent():\n    @chz.chz\n    class C:\n        field: int\n\n    @chz.chz\n    class B:\n        c: C\n\n    @chz.chz\n    class A:\n        b: B\n\n    with pytest.raises(\n        ExtraneousBlueprintArg,\n        match=r\"\"\"Extraneous argument 'b.cc.nope' to Blueprint for test_blueprint_errors:test_blueprint_extraneous_valid_parent.<locals>.A \\(from command line\\)\n\nParam 'b' is closest valid ancestor\nParam 'b' is set to test_blueprint_errors:test_blueprint_extraneous_valid_parent.<locals>.B \\(blueprint_unspecified\\)\nSubparam 'cc' does not exist on it\nAppend --help to your command to see valid arguments\"\"\",\n    ):\n        chz.entrypoint(A, argv=[\"b.cc.nope=0\"])\n"
  },
  {
    "path": "tests/test_blueprint_meta_factory.py",
    "content": "import typing\nfrom typing import Optional\n\nimport pytest\n\nimport chz\nfrom chz.blueprint import Castable, InvalidBlueprintArg, MissingBlueprintArg\n\n\nclass A:\n    pass\n\n\nclass B(A):\n    pass\n\n\nclass C(B):\n    pass\n\n\ndef test_meta_factory_subclass():\n    @chz.chz\n    class Main:\n        obj: A = chz.field(meta_factory=chz.factories.subclass(base_cls=A, default_cls=A))\n\n    argv = [\"obj=A\"]\n    ret = chz.entrypoint(Main, argv=argv)\n    assert type(ret.obj) is A\n\n    # Test basic subclass functionality, ie B -> A\n    argv = [\"obj=B\"]\n    ret = chz.entrypoint(Main, argv=argv)\n    assert type(ret.obj) is B\n\n    # Test multiple inheritance, ie C -> B -> A\n    argv = [\"obj=C\"]\n    ret = chz.entrypoint(Main, argv=argv)\n    assert type(ret.obj) is C\n\n\ndef test_meta_factory_subclass_limited():\n    # Test that a subclass of B is not accepted\n    @chz.chz\n    class Main:\n        obj: A = chz.field(meta_factory=chz.factories.subclass(base_cls=B, default_cls=A))\n\n    argv = [\"obj=A\"]\n    with pytest.raises(\n        InvalidBlueprintArg,\n        match=(\n            \"Failed to interpret it as a factory for polymorphic construction:\\n\"\n            \"No subclass of test_blueprint_meta_factory.B named 'A'\"\n        ),\n    ):\n        chz.entrypoint(Main, argv=argv)\n\n\ndef test_meta_factory_default_subclass():\n    @chz.chz\n    class Parent:\n        required0: int\n\n    @chz.chz\n    class Child2(Parent):\n        required2: int\n\n    @chz.chz\n    class Main:\n        field: Parent = chz.field(meta_factory=chz.factories.subclass(Parent, default_cls=Child2))\n\n    assert (chz.Blueprint(Main).apply({\"field.required0\": 0, \"field.required2\": 2}).make()) == Main(\n        field=Child2(required0=0, required2=2)\n    )\n    assert (\n        chz.Blueprint(Main)\n        .apply({\"field.required0\": 0, \"field\": Child2, \"field.required2\": 2})\n        .make()\n    ) == Main(field=Child2(required0=0, required2=2))\n\n    assert (\n        chz.Blueprint(Main).apply({\"field.required0\": 0}).get_help()\n        == \"\"\"\\\nWARNING: Missing required arguments for parameter(s): field.required2\n\nEntry point: test_blueprint_meta_factory:test_meta_factory_default_subclass.<locals>.Main\n\nArguments:\n  field            test_blueprint_meta_factory:test_meta_factory_default_subclass.<locals>.Parent    test_blueprint_meta_factory:test_meta_factory_default_subclass.<locals>.Child2 (meta_factory)\n  field.required0  int                                       0\n  field.required2  int                                       -\n\"\"\"\n    )\n\n    assert (\n        chz.Blueprint(Main).apply({\"field.required0\": 0, \"field\": Child2}).get_help()\n        == \"\"\"\\\nWARNING: Missing required arguments for parameter(s): field.required2\n\nEntry point: test_blueprint_meta_factory:test_meta_factory_default_subclass.<locals>.Main\n\nArguments:\n  field            test_blueprint_meta_factory:test_meta_factory_default_subclass.<locals>.Parent    test_blueprint_meta_factory:test_meta_factory_default_subclass.<locals>.Child2\n  field.required0  int                                       0\n  field.required2  int                                       -\n\"\"\"\n    )\n\n    @chz.chz\n    class Main2:\n        field: Parent | None = chz.field(\n            meta_factory=chz.factories.subclass(Parent, default_cls=Child2), default=None\n        )\n\n    assert (\n        chz.Blueprint(Main2).get_help()\n        == \"\"\"\\\nEntry point: test_blueprint_meta_factory:test_meta_factory_default_subclass.<locals>.Main2\n\nArguments:\n  field            test_blueprint_meta_factory.test_meta_factory_default_subclass.<locals>.Parent | None                 None (default)\n  field.required0  int                                       -\n  field.required2  int                                       -\n\"\"\"\n    )\n\n    assert (\n        chz.Blueprint(Main2).apply({\"field.required0\": 0}).get_help()\n        == \"\"\"\\\nWARNING: Missing required arguments for parameter(s): field.required2\n\nEntry point: test_blueprint_meta_factory:test_meta_factory_default_subclass.<locals>.Main2\n\nArguments:\n  field            test_blueprint_meta_factory.test_meta_factory_default_subclass.<locals>.Parent | None                 test_blueprint_meta_factory:test_meta_factory_default_subclass.<locals>.Child2 (meta_factory)\n  field.required0  int                                       0\n  field.required2  int                                       -\n\"\"\"\n    )\n\n\ndef test_meta_factory_blueprint_unspecified():\n    @chz.chz\n    class Parent:\n        required0: int\n\n    @chz.chz\n    class Child2(Parent):\n        required2: int\n\n    @chz.chz\n    class Main:\n        field: Parent = chz.field(blueprint_unspecified=Child2)\n\n    assert (chz.Blueprint(Main).apply({\"field.required0\": 0, \"field.required2\": 2}).make()) == Main(\n        field=Child2(required0=0, required2=2)\n    )\n    assert (\n        chz.Blueprint(Main)\n        .apply({\"field.required0\": 0, \"field\": Child2, \"field.required2\": 2})\n        .make()\n    ) == Main(field=Child2(required0=0, required2=2))\n\n    assert (\n        chz.Blueprint(Main).get_help()\n        == \"\"\"\\\nWARNING: Missing required arguments for parameter(s): field.required0, field.required2\n\nEntry point: test_blueprint_meta_factory:test_meta_factory_blueprint_unspecified.<locals>.Main\n\nArguments:\n  field            test_blueprint_meta_factory:test_meta_factory_blueprint_unspecified.<locals>.Parent                   test_blueprint_meta_factory:test_meta_factory_blueprint_unspecified.<locals>.Child2 (blueprint_unspecified)\n  field.required0  int                                       -\n  field.required2  int                                       -\n\"\"\"\n    )\n\n\ndef test_meta_factory_blueprint_unspecified_more():\n    @chz.chz\n    class Sub:\n        x: int = 1\n\n    @chz.chz\n    class Config:\n        sub: Sub\n        sub2: Sub\n\n    @chz.chz\n    class MySub(Sub):\n        x: int = 2\n\n    @chz.chz\n    class MySub2(Sub):\n        x: int = 3\n\n    @chz.chz\n    class MyConfig(Config):\n        sub: Sub = chz.field(blueprint_unspecified=MySub)\n        sub2: Sub = chz.field(blueprint_unspecified=MySub2)\n\n    # Check defaults get overwritten properly\n    config = chz.Blueprint(MyConfig).make()\n    assert config == MyConfig(sub=MySub(x=2), sub2=MySub2(x=3))\n\n    # Check you can still set the nested values properly\n    config = chz.Blueprint(MyConfig).apply_from_argv([\"sub.x=4\", \"sub2.x=5\"]).make()\n    assert config == MyConfig(sub=MySub(x=4), sub2=MySub2(x=5))\n\n    # Ensure that's okay to override a field with the base Sub class\n    config = chz.Blueprint(MyConfig).apply_from_argv([\"sub=Sub\"]).make()\n    assert config == MyConfig(sub=Sub(x=1), sub2=MySub2(x=3))\n\n    # Lastly, check that it's okay to override the fields with custom Sub classes\n    config = chz.Blueprint(MyConfig).apply_from_argv([\"sub=MySub2\", \"sub2=MySub\"]).make()\n    assert config == MyConfig(sub=MySub2(x=3), sub2=MySub(x=2))\n\n\ndef test_meta_factory_blueprint_unspecified_all_default_help():\n    @chz.chz\n    class X:\n        value: int = 0\n\n    @chz.chz\n    class Main:\n        field: object = chz.field(blueprint_unspecified=X)\n\n    assert (\n        chz.Blueprint(Main).get_help()\n        == \"\"\"\\\nEntry point: test_blueprint_meta_factory:test_meta_factory_blueprint_unspecified_all_default_help.<locals>.Main\n\nArguments:\n  field        object  test_blueprint_meta_factory:test_meta_factory_blueprint_unspecified_all_default_help.<locals>.X (meta_factory)\n  field.value  int     0 (default)\n\"\"\"\n    )\n\n\ndef test_meta_factory_blueprint_unspecified_optional():\n    @chz.chz\n    class X:\n        value: int = 0\n\n    @chz.chz\n    class Main:\n        value: int = 0\n        field: X | None = chz.field(blueprint_unspecified=X, default=None)\n\n    assert chz.Blueprint(Main).apply({\"...value\": 1}).make() == Main(value=1, field=X(value=1))\n\n    @chz.chz\n    class Main:\n        value: int = 0\n        field: X | None = chz.field(blueprint_unspecified=type(None), default=None)\n\n    assert chz.Blueprint(Main).apply({\"...value\": 1}).make() == Main(value=1, field=None)\n\n\ndef test_meta_factory_subclass_generic():\n    T = typing.TypeVar(\"T\")\n\n    @chz.chz\n    class Base(typing.Generic[T]):\n        pass\n\n    @chz.chz\n    class Sub(Base[int]):\n        value: int = 0\n\n    @chz.chz\n    class Main1:\n        obj: Base\n\n    argv = [\"obj=Base\"]\n    ret = chz.entrypoint(Main1, argv=argv)\n    assert type(ret.obj) is Base\n\n    argv = [\"obj=Sub\"]\n    ret = chz.entrypoint(Main1, argv=argv)\n    assert type(ret.obj) is Sub\n\n    @chz.chz\n    class Main2:\n        obj: Base[int]\n\n    argv = [\"obj=Base\"]\n    ret = chz.entrypoint(Main2, argv=argv)\n    assert type(ret.obj) is Base\n\n    argv = [\"obj=Sub\"]\n    ret = chz.entrypoint(Main2, argv=argv)\n    assert type(ret.obj) is Sub\n\n    argv = [\"obj=Sub\", \"obj.value=3\"]\n    ret = chz.entrypoint(Main2, argv=argv)\n    assert type(ret.obj) is Sub\n    assert ret.obj.value == 3\n\n\ndef test_meta_factory_optional():\n    @chz.chz\n    class Child2:\n        x: int\n\n    @chz.chz\n    class Parent:\n        child: Optional[Child2]  # noqa: UP045\n\n    @chz.chz\n    class Parent2:\n        child: Child2 | None\n\n    assert chz.Blueprint(Parent).apply({\"child.x\": 3}).make() == Parent(child=Child2(x=3))\n    assert chz.Blueprint(Parent2).apply({\"child.x\": 3}).make() == Parent2(child=Child2(x=3))\n\n\ndef test_meta_factory_union():\n    from dataclasses import dataclass\n\n    @dataclass\n    class O1: ...\n\n    @dataclass\n    class O2: ...\n\n    @chz.chz\n    class Main:\n        z: O1 | O2\n\n    assert chz.Blueprint(Main).apply({\"z\": Castable(\"O1\")}).make() == Main(z=O1())\n    assert chz.Blueprint(Main).apply({\"z\": Castable(\"O2\")}).make() == Main(z=O2())\n\n\ndef test_meta_factory_non_chz():\n    class Actor:\n        def __init__(self):\n            self.label = \"actor\"\n\n    class WakeActor(Actor):\n        def __init__(self):\n            self.label = \"wake_actor\"\n\n    @chz.chz\n    class Args:\n        actor: Actor = chz.field(meta_factory=chz.factories.subclass(Actor))\n\n    assert chz.Blueprint(Args).apply({\"actor\": Castable(\"Actor\")}).make().actor.label == \"actor\"\n    assert (\n        chz.Blueprint(Args).apply({\"actor\": Castable(\"WakeActor\")}).make().actor.label\n        == \"wake_actor\"\n    )\n\n    with pytest.raises(\n        MissingBlueprintArg, match=r\"Missing required arguments for parameter\\(s\\): actor\"\n    ):\n        chz.Blueprint(Args).make()\n\n\ndef test_meta_factory_function_lambda():\n    import calendar\n\n    @chz.chz\n    class Main:\n        a: A = chz.field(meta_factory=chz.factories.function(), default=object())\n        cal: calendar.Calendar = chz.field(\n            meta_factory=chz.factories.function(default_module=\"calendar\"), default=object()\n        )\n\n    argv = [\"a=lambda: A()\", \"cal=lambda d: Calendar(int(d))\", \"cal.d=3\"]\n    ret = chz.entrypoint(Main, argv=argv)\n    assert type(ret.a) is A\n    assert type(ret.cal) is calendar.Calendar\n    assert ret.cal.firstweekday == 3\n\n    @chz.chz\n    class Main:\n        a: A = chz.field(default=object())\n        cal: calendar.Calendar = chz.field(\n            meta_factory=chz.factories.standard(default_module=\"calendar\"), default=object()\n        )\n\n    argv = [\"a=lambda: A()\", \"cal=lambda d: Calendar(int(d))\", \"cal.d=3\"]\n    ret = chz.entrypoint(Main, argv=argv)\n    assert type(ret.a) is A\n    assert type(ret.cal) is calendar.Calendar\n    assert ret.cal.firstweekday == 3\n\n\ndef test_meta_factory_type_subclass():\n    @chz.chz\n    class Main:\n        a: type[A]\n\n    assert chz.entrypoint(Main, argv=[\"a=A\"]).a is A\n    assert chz.entrypoint(Main, argv=[\"a=B\"]).a is B\n    assert chz.entrypoint(Main, argv=[\"a=C\"]).a is C\n\n    with pytest.raises(\n        InvalidBlueprintArg, match=\"Could not interpret argument 'int' provided for param 'a'\"\n    ):\n        chz.entrypoint(Main, argv=[\"a=int\"])\n\n\ndef test_meta_factory_function_union():\n    @chz.chz\n    class A:\n        field: str = \"a\"\n\n    @chz.chz\n    class B(A): ...\n\n    def make_tuple(s0: B | None = None, s1: B | None = None):\n        if s0 is None:\n            s0 = B(field=\"s0default\")\n        if s1 is None:\n            s1 = B(field=\"s1default\")\n        return (s0, s1)\n\n    @chz.chz\n    class Main:\n        specs: tuple[A, ...] = chz.field(blueprint_unspecified=make_tuple)\n\n    assert chz.entrypoint(Main, argv=[\"specs.s1=B\"]).specs == (\n        B(field=\"s0default\"),\n        B(field=\"a\"),\n    )\n\n\ndef test_meta_factory_none():\n    @chz.chz\n    class Main:\n        a: A = chz.field(meta_factory=None)\n\n    with pytest.raises(\n        InvalidBlueprintArg, match=\"Could not cast 'A' to test_blueprint_meta_factory:A\"\n    ):\n        chz.entrypoint(Main, argv=[\"a=A\"])\n"
  },
  {
    "path": "tests/test_blueprint_methods.py",
    "content": "import re\nimport textwrap\nfrom unittest.mock import patch\n\nimport pytest\n\nimport chz\nfrom chz.blueprint import EntrypointHelpException, ExtraneousBlueprintArg\n\n\n@chz.chz\nclass Run1:\n    name: str\n\n    def launch(self, cluster: str):\n        \"\"\"Launch a job on a cluster.\"\"\"\n        return (\"launch\", self, cluster)\n\n    def history(self):\n        return (\"history\", self)\n\n\n@chz.chz\nclass RunDefault:\n    def launch(self, cluster: str):\n        return (\"launch\", self, cluster)\n\n\ndef test_methods_entrypoint():\n    assert chz.methods_entrypoint(Run1, argv=[\"launch\", \"self.name=job\", \"cluster=big\"]) == (\n        \"launch\",\n        Run1(name=\"job\"),\n        \"big\",\n    )\n    assert chz.methods_entrypoint(Run1, argv=[\"history\", \"self.name=job\"]) == (\n        \"history\",\n        Run1(name=\"job\"),\n    )\n\n    assert chz.methods_entrypoint(RunDefault, argv=[\"launch\", \"cluster=big\"]) == (\n        \"launch\",\n        RunDefault(),\n        \"big\",\n    )\n\n    with pytest.raises(ExtraneousBlueprintArg, match=\"Extraneous argument 'self.cluster'\"):\n        chz.methods_entrypoint(Run1, argv=[\"launch\", \"self.name=job\", \"self.cluster=big\"])\n\n\ndef test_methods_entrypoint_help():\n    with pytest.raises(\n        EntrypointHelpException,\n        match=\"\"\"\\\nEntry point: methods of test_blueprint_methods:Run1\n\nAvailable methods:\n  history\n  launch  Launch a job on a cluster.\n\"\"\",\n    ):\n        chz.methods_entrypoint(Run1, argv=[])\n\n    orig_get_help = chz.blueprint._blueprint.Blueprint.get_help\n    with (\n        # Disable color, which messes with the pytest.raises(..., match=...)\n        patch(\n            \"chz.blueprint._blueprint.Blueprint.get_help\",\n            lambda self, color: orig_get_help(self, color=False),\n        ),\n        pytest.raises(\n            EntrypointHelpException,\n            match=re.escape(\n                textwrap.dedent(\n                    \"\"\"\\\n                    WARNING: Missing required arguments for parameter(s): self.name, cluster\n\n                    Entry point: test_blueprint_methods:Run1.launch\n\n                      Launch a job on a cluster.\n\n                    Arguments:\n                      self       test_blueprint_methods:Run1  -\n                      self.name  str                          -\n                      cluster    str\"\"\"\n                ),\n            ),\n        ),\n    ):\n        chz.methods_entrypoint(Run1, argv=[\"launch\", \"--help\"])\n\n\n@chz.chz\nclass RunAltSelfParam:\n    name: str\n\n    def launch(run, cluster: str):\n        return (\"launch\", run, cluster)\n\n\ndef test_methods_entrypoint_self():\n    assert chz.methods_entrypoint(\n        RunAltSelfParam, argv=[\"launch\", \"run.name=job\", \"cluster=big\"]\n    ) == (\"launch\", RunAltSelfParam(name=\"job\"), \"big\")\n\n    with pytest.raises(ExtraneousBlueprintArg, match=\"Extraneous argument 'self.name'\"):\n        chz.methods_entrypoint(RunAltSelfParam, argv=[\"launch\", \"self.name=job\", \"cluster=big\"])\n\n    with pytest.raises(ExtraneousBlueprintArg, match=\"Extraneous argument 'run.name'\"):\n        chz.methods_entrypoint(Run1, argv=[\"launch\", \"run.name=job\", \"cluster=big\"])\n\n\n@chz.chz\nclass RunDefaultChild(RunDefault): ...\n\n\ndef test_methods_entrypoint_polymorphic():\n    assert chz.methods_entrypoint(\n        RunDefault, argv=[\"launch\", \"self=RunDefaultChild\", \"cluster=big\"]\n    ) == (\"launch\", RunDefaultChild(), \"big\")\n\n\ndef test_methods_entrypoint_transform():\n    def transform(blueprint, target, method):\n        if method == \"launch\":\n            return blueprint.apply({\"name\": \"job\"}, subpath=\"self\")\n        return blueprint\n\n    assert chz.methods_entrypoint(Run1, argv=[\"launch\", \"cluster=big\"], transform=transform) == (\n        \"launch\",\n        Run1(name=\"job\"),\n        \"big\",\n    )\n"
  },
  {
    "path": "tests/test_blueprint_reference.py",
    "content": "import pytest\n\nimport chz\nfrom chz.blueprint import InvalidBlueprintArg, MissingBlueprintArg, Reference\n\n\ndef test_blueprint_reference():\n    @chz.chz\n    class Main:\n        a: str\n        b: str\n\n    obj = chz.Blueprint(Main).apply({\"a\": \"foo\", \"b\": Reference(\"a\")}).make()\n    assert obj == Main(a=\"foo\", b=\"foo\")\n\n    obj = chz.Blueprint(Main).apply_from_argv([\"a=foo\", \"b@=a\"]).make()\n    assert obj == Main(a=\"foo\", b=\"foo\")\n\n    assert (\n        chz.Blueprint(Main).apply({\"a\": \"foo\", \"b\": Reference(\"a\")}).get_help()\n        == \"\"\"\\\nEntry point: test_blueprint_reference:test_blueprint_reference.<locals>.Main\n\nArguments:\n  a  str  'foo'\n  b  str  @=a\n\"\"\"\n    )\n\n    with pytest.raises(InvalidBlueprintArg, match=r\"Invalid reference target 'c' for param b\"):\n        chz.Blueprint(Main).apply({\"a\": \"foo\", \"b\": Reference(\"c\")}).make()\n\n\ndef test_blueprint_reference_multiple_invalid():\n    @chz.chz\n    class Main:\n        a: int\n        b: int\n        c: int\n\n    with pytest.raises(\n        InvalidBlueprintArg,\n        match=\"\"\"\\\nInvalid reference target 'x' for params a, b\n\nInvalid reference target 'bb' for param c\nDid you mean 'b'?\"\"\",\n    ):\n        chz.Blueprint(Main).apply_from_argv([\"a@=x\", \"b@=x\", \"c@=bb\"]).make()\n\n\ndef test_blueprint_reference_nested():\n    @chz.chz\n    class C:\n        c: int\n\n    @chz.chz\n    class B:\n        b: int\n        c: C\n\n    @chz.chz\n    class A:\n        a: int\n        b: B\n\n    obj = chz.Blueprint(A).apply_from_argv([\"a@=b.b\", \"b.c.c@=a\", \"b.b=5\"]).make()\n    assert obj == A(a=5, b=B(b=5, c=C(c=5)))\n\n\ndef test_blueprint_reference_wildcard():\n    @chz.chz\n    class B:\n        name: str\n\n    @chz.chz\n    class A:\n        name: str\n        b: B\n\n    @chz.chz\n    class Main:\n        name: str\n        a: A\n\n    obj = chz.Blueprint(Main).apply_from_argv([\"...name@=name\", \"name=foo\"]).make()\n    assert obj == Main(name=\"foo\", a=A(name=\"foo\", b=B(name=\"foo\")))\n\n    obj = chz.Blueprint(Main).apply_from_argv([\"...name@=a.b.name\", \"a.b.name=foo\"]).make()\n    assert obj == Main(name=\"foo\", a=A(name=\"foo\", b=B(name=\"foo\")))\n\n\ndef test_blueprint_reference_wildcard_default():\n    @chz.chz\n    class A:\n        name: str\n\n    @chz.chz\n    class Main:\n        name: str = \"foo\"\n        a: A\n\n    obj = chz.Blueprint(Main).apply_from_argv([\"...name@=name\"]).make()\n    assert obj == Main(name=\"foo\", a=A(name=\"foo\"))\n\n\ndef test_blueprint_reference_wildcard_default_no_default():\n    @chz.chz\n    class Defaults:\n        a: int\n\n    @chz.chz\n    class A:\n        defaults: Defaults\n        a: int\n\n    with pytest.raises(\n        MissingBlueprintArg, match=r\"Missing required arguments for parameter\\(s\\): defaults.a\"\n    ):\n        chz.Blueprint(A).apply_from_argv([\"...a@=defaults.a\"]).make()\n\n\ndef test_blueprint_reference_wildcard_default_constructable():\n    @chz.chz\n    class Object:\n        a: int = 1\n\n    @chz.chz\n    class Defaults:\n        obj: Object\n        a: int = 2\n\n    @chz.chz\n    class Main:\n        defaults: Defaults\n        obj: Object\n        a: int = 3\n\n    assert chz.Blueprint(Main).apply_from_argv(\n        [\"...obj@=defaults.obj\", \"defaults.obj.a=4\"]\n    ).make() == Main(\n        defaults=Defaults(obj=Object(a=4), a=2),\n        obj=Object(a=4),\n        a=3,\n    )\n\n    assert chz.Blueprint(Main).apply_from_argv([\"...obj@=defaults.obj\", \"...a=4\"]).make() == Main(\n        defaults=Defaults(obj=Object(a=4), a=4),\n        obj=Object(a=4),\n        a=4,\n    )\n\n\ndef test_blueprint_reference_cycle():\n    @chz.chz\n    class Main:\n        a: int\n        b: int\n\n    with pytest.raises(RecursionError, match=\"Detected cyclic reference: a -> b -> a\"):\n        chz.Blueprint(Main).apply_from_argv([\"a@=b\", \"b@=a\"]).make()\n\n    @chz.chz\n    class Main:\n        a: int\n\n    with pytest.raises(\n        MissingBlueprintArg, match=r\"Missing required arguments for parameter\\(s\\): a\"\n    ):\n        chz.Blueprint(Main).apply_from_argv([\"a@=a\"]).make()\n"
  },
  {
    "path": "tests/test_blueprint_root_polymorphism.py",
    "content": "import re\n\nimport chz\n\n\ndef test_root_polymorphism():\n    @chz.chz\n    class X:\n        a: int\n        b: str = \"str\"\n\n    @chz.chz\n    class Y(X):\n        c: float = 1.0\n\n    def foo(a: int, b: str = \"default\", c: float = 3.0):\n        return Y(a=a, b=b, c=c)\n\n    assert chz.Blueprint(X).apply({\"a\": 0}).make() == X(a=0, b=\"str\")\n    assert chz.Blueprint(X | None).apply({\"a\": 0}).make() == X(a=0, b=\"str\")\n\n    assert chz.Blueprint(X).apply({\"\": Y, \"a\": 0}).make() == Y(a=0, b=\"str\", c=1.0)\n    assert chz.Blueprint(X).apply({\"\": foo, \"a\": 2}).make() == Y(a=2, b=\"default\", c=3.0)\n\n    assert chz.Blueprint(object).apply({\"\": X, \"a\": 1}).make() == X(a=1, b=\"str\")\n    assert chz.Blueprint(object).apply({\"\": X, \"...a\": 1}).make() == X(a=1, b=\"str\")\n    assert chz.Blueprint(object).apply({\"\": foo, \"a\": 1}).make() == Y(a=1, b=\"default\", c=3.0)\n\n    # TODO: make help better if root is object or Any and no arguments are provided\n    assert re.fullmatch(\n        r\"\"\"Entry point: object\n\n  The base class of the class hierarchy.*\n\nArguments:\n  <entrypoint>  object  test_blueprint_root_polymorphism:test_root_polymorphism.<locals>.X                The base class of the class hierarchy.*\n  a             int     1\n  b             str     'str' \\(default\\)\n\"\"\",\n        chz.Blueprint(object).apply({\"\": X, \"a\": 1}).get_help(),\n        flags=re.DOTALL,\n    )\n\n    assert re.fullmatch(\n        r\"\"\"Entry point: object\n\n  The base class of the class hierarchy.*\n\nArguments:\n  <entrypoint>  object  test_blueprint_root_polymorphism:test_root_polymorphism.<locals>.foo              The base class of the class hierarchy.*\n  a             int     1\n  b             str     'default' \\(default\\)\n  c             float   3.0 \\(default\\)\n\"\"\",\n        chz.Blueprint(object).apply({\"\": foo, \"a\": 1}).get_help(),\n        flags=re.DOTALL,\n    )\n"
  },
  {
    "path": "tests/test_blueprint_unit.py",
    "content": "import pytest\n\nfrom chz.blueprint import Blueprint, Castable, beta_argv_arg_to_string, beta_blueprint_to_argv\nfrom chz.blueprint._argmap import ArgumentMap, Layer, join_arg_path\nfrom chz.blueprint._wildcard import _wildcard_key_match, wildcard_key_to_regex\n\n\ndef test_beta_argv_arg_to_string():\n    k = \"a\"\n    v = {\n        \"b\": {\n            \"c\": {\n                \"d\": 1,\n            },\n            \"e\": 2,\n        },\n        \"f\": 3,\n    }\n    assert beta_argv_arg_to_string(k, v) == [\"a.b.c.d=1\", \"a.b.e=2\", \"a.f=3\"]\n\n    assert beta_argv_arg_to_string(\"nums\", [1, 2, 3]) == [\"nums=1,2,3\"]\n    assert beta_argv_arg_to_string(\"flags\", [True, None, False]) == [\"flags=True,None,False\"]\n    assert beta_argv_arg_to_string(\"k\", [\"1,2,3\"]) == [\"k.0=1,2,3\"]\n\n    class C:\n        d: int\n\n    class B:\n        c: C\n        e: int\n\n    class A:\n        b: B\n        f: int\n\n    blueprint = Blueprint(A)\n    blueprint.apply({\"a.b\": {\"c\": {\"d\": 1}, \"e\": 2}, \"a.f\": 3})\n    assert beta_blueprint_to_argv(blueprint) == [\"a.b.c.d=1\", \"a.b.e=2\", \"a.f=3\"]\n\n\ndef test_wildcard_key_to_regex():\n    assert wildcard_key_to_regex(\"a.b.c\").pattern == r\"a\\.b\\.c\"\n    assert wildcard_key_to_regex(\"a...c\").pattern == r\"a\\.(.*\\.)?c\"\n    assert wildcard_key_to_regex(\"...a\").pattern == r\"(.*\\.)?a\"\n    assert wildcard_key_to_regex(\"...a...c\").pattern == r\"(.*\\.)?a\\.(.*\\.)?c\"\n    with pytest.raises(ValueError, match=\"Wildcard not allowed at end of key\"):\n        wildcard_key_to_regex(\"...\")\n    with pytest.raises(ValueError, match=\"Wildcard not allowed at end of key\"):\n        wildcard_key_to_regex(\"a...\")\n\n\ndef test_wildcard_key_match():\n    assert _wildcard_key_match(\"a.b.c\", \"a.b.c\")\n    assert _wildcard_key_match(\"a...c\", \"a.b.c\")\n    assert _wildcard_key_match(\"...c\", \"a.b.c\")\n    assert _wildcard_key_match(\"...a...c\", \"a.b.c\")\n    assert _wildcard_key_match(\"...a.b.c\", \"a.b.c\")\n    assert _wildcard_key_match(\"...a.b...c\", \"a.b.c\")\n    assert _wildcard_key_match(\"...a...b.b...a\", \"a.b.x.b.b.a\")\n    assert _wildcard_key_match(\"...x.y.z\", \"x.y.z\")\n    assert not _wildcard_key_match(\"a.b.d\", \"a.b.c\")\n    assert not _wildcard_key_match(\"...a\", \"a.b.c\")\n    assert not _wildcard_key_match(\"a...b\", \"a.b.c\")\n    assert not _wildcard_key_match(\"...a\", \"xxa\")\n    assert not _wildcard_key_match(\"...a...b.b...a\", \"a.b.x.b.c.a\")\n    assert not _wildcard_key_match(\"...a...y\", \"a...a...z\")\n    assert not _wildcard_key_match(\"...a...b...a\", \"a...b...a...b...b...a.z\")\n\n    assert wildcard_key_to_regex(\"a.b.c\").fullmatch(\"a.b.c\")\n    assert wildcard_key_to_regex(\"a...c\").fullmatch(\"a.b.c\")\n    assert wildcard_key_to_regex(\"...c\").fullmatch(\"a.b.c\")\n    assert wildcard_key_to_regex(\"...a...c\").fullmatch(\"a.b.c\")\n    assert wildcard_key_to_regex(\"...a.b.c\").fullmatch(\"a.b.c\")\n    assert wildcard_key_to_regex(\"...a.b...c\").fullmatch(\"a.b.c\")\n    assert wildcard_key_to_regex(\"...a...b.b...a\").fullmatch(\"a.b.x.b.b.a\")\n    assert wildcard_key_to_regex(\"...x.y.z\").fullmatch(\"x.y.z\")\n    assert not wildcard_key_to_regex(\"a.b.d\").fullmatch(\"a.b.c\")\n    assert not wildcard_key_to_regex(\"...a\").fullmatch(\"a.b.c\")\n    assert not wildcard_key_to_regex(\"a...b\").fullmatch(\"a.b.c\")\n    assert not wildcard_key_to_regex(\"...a\").fullmatch(\"xxa\")\n    assert not wildcard_key_to_regex(\"...a...b.b...a\").fullmatch(\"a.b.x.b.c.a\")\n    assert not wildcard_key_to_regex(\"...a...y\").fullmatch(\"a...a...z\")\n    assert not wildcard_key_to_regex(\"...a...b...a\").fullmatch(\"a...b...a...b...b...a.z\")\n\n\ndef test_join_arg_path():\n    assert join_arg_path(\"parent\", \"child\") == \"parent.child\"\n    assert join_arg_path(\"grand.parent\", \"child\") == \"grand.parent.child\"\n    assert join_arg_path(\"parent\", \"...child\") == \"parent...child\"\n    assert join_arg_path(\"grand...parent\", \"child\") == \"grand...parent.child\"\n    assert join_arg_path(\"\", \"child\") == \"child\"\n    assert join_arg_path(\"\", \"...child\") == \"...child\"\n\n\ndef test_arg_map():\n    layer = Layer({\"a.b.c\": 0, \"a.b.c.one\": 1, \"a.b.c.two\": 2}, None)\n    arg_map = ArgumentMap([layer])\n    arg_map.consolidate()\n\n    assert arg_map.get_kv(\"a.b.c.one\").key == \"a.b.c.one\"\n    assert arg_map.get_kv(\"a.b.c.two\").key == \"a.b.c.two\"\n    assert arg_map.get_kv(\"a.b\") == None\n    assert arg_map.get_kv(\"a.b.c.zero\") == None\n    assert arg_map.get_kv(\"a.b.d\") == None\n\n    assert arg_map.subpaths(\"a.b.c\") == [\"\", \"one\", \"two\"]\n    assert arg_map.subpaths(\"a.b.c\", strict=True) == [\"one\", \"two\"]\n    assert arg_map.subpaths(\"a.b.c.one\") == [\"\"]\n    assert arg_map.subpaths(\"a.b.c.one\", strict=True) == []\n\n    layer = Layer({\"prefix_suffix\": 1}, None)\n    arg_map = ArgumentMap([layer])\n    arg_map.consolidate()\n\n    assert arg_map.subpaths(\"prefix\") == []\n    assert arg_map.subpaths(\"prefix\", strict=True) == []\n\n    layer = Layer({\"\": 1, \"a\": 2}, None)\n    arg_map = ArgumentMap([layer])\n    arg_map.consolidate()\n\n    assert arg_map.subpaths(\"\") == [\"\", \"a\"]\n    assert arg_map.subpaths(\"\", strict=True) == [\"a\"]\n\n\ndef test_arg_map_wildcard():\n    layer_wildcard = Layer({\"a...c.one\": 1, \"a...c.two\": 2}, None)\n    arg_map = ArgumentMap([layer_wildcard])\n    arg_map.consolidate()\n\n    assert arg_map.get_kv(\"a.b.c.one\").key == \"a...c.one\"\n    assert arg_map.get_kv(\"a.b.b.b.b.c.one\").key == \"a...c.one\"\n\n    assert arg_map.subpaths(\"a.b.c\") == [\"one\", \"two\"]\n    assert arg_map.subpaths(\"a.b.c.one\") == [\"\"]\n    assert arg_map.subpaths(\"a.b.c.one\", strict=True) == []\n\n    layer_wildcard = Layer({\"...one\": 1}, None)\n    arg_map = ArgumentMap([layer_wildcard])\n    arg_map.consolidate()\n\n    assert arg_map.subpaths(\"a.b.c.one\") == [\"\"]\n    assert arg_map.subpaths(\"a.b.c.two\") == []\n    assert arg_map.subpaths(\"a.b.c.one\", strict=True) == []\n\n    layer_wildcard = Layer({\"a...one...b...one\": 1}, None)\n    arg_map = ArgumentMap([layer_wildcard])\n    arg_map.consolidate()\n\n    assert arg_map.subpaths(\"a.one.x.one\") == [\"...b...one\"]\n\n    layer_wildcard = Layer({\"...prefix_suffix\": 1}, None)\n    arg_map = ArgumentMap([layer_wildcard])\n    arg_map.consolidate()\n\n    assert arg_map.subpaths(\"something.prefix\") == []\n    assert arg_map.subpaths(\"something.prefix\", strict=True) == []\n\n    layer_wildcard = Layer({\"...a.key.key\": 1, \"...a.key.key...x\": 2}, None)\n    arg_map = ArgumentMap([layer_wildcard])\n    arg_map.consolidate()\n\n    assert arg_map.subpaths(\"a.key\") == [\"key...x\", \"key\"]\n    assert arg_map.subpaths(\"\") == [\"...a.key.key...x\", \"...a.key.key\"]\n\n    layer_wildcard = Layer({\"...c\": 1, \"...b.c\": 2}, None)\n    arg_map = ArgumentMap([layer_wildcard])\n    arg_map.consolidate()\n\n    assert arg_map.get_kv(\"a.b.c\").key == \"...b.c\"\n    assert arg_map.get_kv(\"a.c.c\").key == \"...c\"\n\n    wildcard_layer = Layer({\"...bar.delta\": \"wildcard\"}, \"wild\")\n    qualified_layer = Layer({\"foo.bar.alpha\": \"alpha\", \"foo.bar.delta\": \"qualified\"}, \"qual\")\n    arg_map = ArgumentMap([wildcard_layer, qualified_layer])\n    arg_map.consolidate()\n\n    assert arg_map.get_kv(\"another.bar.delta\").key == \"...bar.delta\"\n    assert arg_map.get_kv(\"foo.bar.delta\").key == \"foo.bar.delta\"\n\n    arg_map = ArgumentMap([qualified_layer, wildcard_layer])\n    arg_map.consolidate()\n\n    assert arg_map.get_kv(\"another.bar.delta\").key == \"...bar.delta\"\n    assert arg_map.get_kv(\"foo.bar.delta\").key == \"...bar.delta\"\n\n\ndef test_layer():\n    l = Layer({\"...a\": 0, \"a\": 1}, None)\n    assert l.get_kv(\"a\") == (\"a\", 1, None)\n    l = Layer({\"a\": 1, \"...a\": 0}, None)\n    assert l.get_kv(\"a\") == (\"a\", 1, None)\n\n    l = Layer({\"...z\": 1, \"...x...y...z\": 2, \"...y...z\": 3}, None)\n    assert l.get_kv(\"x.y.z\") == (\"...x...y...z\", 2, None)\n\n\ndef test_collapse_layers():\n    class Dummy:\n        pass\n\n    from chz.blueprint._argv import _collapse_layers\n\n    b = Blueprint(Dummy)\n    b.apply({\"a.b.c.d\": 3, \"a...d\": 4, \"...d\": 5, \"a...e\": 7, \"...e\": 6, \"a.b.c.d.f\": 9})\n    b.apply({\"...f\": 10})\n    assert set(_collapse_layers(b)) == {\n        (\"a.b.c.d\", 3),\n        (\"a...d\", 4),\n        (\"...d\", 5),\n        (\"a...e\", 7),\n        (\"...e\", 6),\n        (\"...f\", 10),\n    }\n\n    b = Blueprint(Dummy)\n    b.apply({\"a.b.c.d\": 3})\n    b.apply({\"a...d\": 4})\n    b.apply({\"...d\": 5})\n    b.apply({\"a...e\": 7})\n    b.apply({\"...e\": 6})\n    b.apply({\"a.b.c.d.f\": 9})\n    b.apply({\"...f\": 10})\n    assert set(_collapse_layers(b)) == {(\"...d\", 5), (\"...e\", 6), (\"...f\", 10)}\n\n    b = Blueprint(Dummy)\n    b.apply({\"...d\": 5})\n    b.apply({\"a.b.c.d\": 3})\n    b.apply({\"a...d\": 4})\n    b.apply({\"...f\": 10})\n    b.apply({\"a.b.c.d.f\": 9})\n    assert set(_collapse_layers(b)) == {(\"...d\", 5), (\"a...d\", 4), (\"...f\", 10), (\"a.b.c.d.f\", 9)}\n\n    b = Blueprint(Dummy)\n    b.apply({\"...d\": 5, \"a.b.c.d\": 3, \"a...d\": 4, \"...f\": 10, \"a.b.c.d.f\": 9})\n    assert set(_collapse_layers(b)) == {\n        (\"...d\", 5),\n        (\"a.b.c.d\", 3),\n        (\"a...d\", 4),\n        (\"...f\", 10),\n        (\"a.b.c.d.f\", 9),\n    }\n\n\ndef test_collapse_blueprint_to_argv():\n    class Dummy:\n        pass\n\n    b = Blueprint(Dummy)\n    b.apply({\"...d\": 5})\n    b.apply({\"a.b.c.d\": 3})\n    b.apply({\"a...d\": 4})\n    b.apply({\"...f\": 10})\n    b.apply({\"a.b.c.d.f\": 9})\n    b.apply({\"a.b.c.d.f.e\": None})\n    assert beta_blueprint_to_argv(b) == [\n        \"...d=5\",\n        \"a...d=4\",\n        \"...f=10\",\n        \"a.b.c.d.f=9\",\n        \"a.b.c.d.f.e=None\",\n    ]\n\n\ndef test_apply_from_argv():\n    class Dummy:\n        pass\n\n    from chz.blueprint._argv import _collapse_layers\n\n    b = Blueprint(Dummy)\n    b.apply_from_argv([\"...d=5\"])\n\n    assert beta_blueprint_to_argv(b) == [\"...d=5\"]\n    assert _collapse_layers(b)[0][1].value == \"5\"\n\n\ndef test_apply_with_types():\n    class Dummy:\n        pass\n\n    b = Blueprint(Dummy)\n    b.apply({\"a\": 1, \"b\": beta_blueprint_to_argv, \"c\": Blueprint})\n    assert beta_blueprint_to_argv(b) == [\n        \"a=1\",\n        \"b=chz.blueprint._argv:beta_blueprint_to_argv\",\n        \"c=chz.blueprint._blueprint:Blueprint\",\n    ]\n\n\ndef test_castable_eq():\n    assert Castable(\"None\") == Castable(\"None\")\n    assert Castable(\"None\") == None\n\n    assert Castable(\"1\") == Castable(\"1\")\n    assert Castable(\"1\") == 1\n\n    assert Castable(\"1\") != 2\n    assert Castable(\"x\") != 2\n"
  },
  {
    "path": "tests/test_blueprint_variadic.py",
    "content": "import typing\n\nimport pytest\n\nimport chz\nfrom chz.blueprint import (\n    Castable,\n    ConstructionException,\n    ExtraneousBlueprintArg,\n    InvalidBlueprintArg,\n    MissingBlueprintArg,\n)\n\n\ndef test_variadic_list():\n    @chz.chz\n    class X:\n        a: int\n\n    @chz.chz\n    class MainList:\n        xs: list[X]\n\n    assert chz.Blueprint(MainList).apply({\"xs.0.a\": 1}).make() == MainList(xs=[X(a=1)])\n\n    assert chz.Blueprint(MainList).apply(\n        {\"xs.0.a\": 1, \"xs.1.a\": 2, \"xs.2.a\": 3}\n    ).make() == MainList(xs=[X(a=1), X(a=2), X(a=3)])\n\n    with pytest.raises(\n        MissingBlueprintArg, match=r\"Missing required arguments for parameter\\(s\\): xs\"\n    ):\n        chz.Blueprint(MainList).make()\n\n    with pytest.raises(\n        MissingBlueprintArg, match=r\"Missing required arguments for parameter\\(s\\): xs.1.a\"\n    ):\n        chz.Blueprint(MainList).apply({\"xs.0.a\": 1, \"xs.2.a\": 3}).make()\n\n    @chz.chz\n    class MainListDefault:\n        xs: list[X] = chz.field(default_factory=list)\n\n    assert chz.Blueprint(MainListDefault).make() == MainListDefault(xs=[])\n\n\ndef test_variadic_wildcard():\n    @chz.chz\n    class X:\n        a: int\n        b: int\n\n    @chz.chz\n    class MainList:\n        xs: list[X]\n\n    with pytest.raises(ExtraneousBlueprintArg, match=r\"Extraneous argument '\\.\\.\\.a'\"):\n        chz.Blueprint(MainList).apply({\"...a\": 1}).make()\n\n    with pytest.raises(ExtraneousBlueprintArg, match=r\"Extraneous argument '\\.\\.\\.0.a'\"):\n        chz.Blueprint(MainList).apply({\"...0.a\": 1}).make()\n\n    assert chz.Blueprint(MainList).apply({\"xs.0.a\": 0, \"...0.b\": 1}).make() == MainList(\n        xs=[X(a=0, b=1)]\n    )\n\n    assert chz.Blueprint(MainList).apply({\"xs.0.a\": 0, \"...b\": 1}).make() == MainList(\n        xs=[X(a=0, b=1)]\n    )\n\n    with pytest.raises(ExtraneousBlueprintArg, match=r\"Extraneous argument '\\.\\.\\.0.a'\"):\n        chz.Blueprint(MainList).apply({\"...0.a\": 0}).make()\n    with pytest.raises(ExtraneousBlueprintArg, match=r\"Extraneous argument '\\.\\.\\.0'\"):\n        chz.Blueprint(MainList).apply({\"...0\": 0}).make()\n\n    assert chz.Blueprint(MainList).apply({\"...xs.0.a\": 0, \"...xs.0.b\": 0}).make() == MainList(\n        xs=[X(a=0, b=0)]\n    )\n\n    with pytest.raises(ExtraneousBlueprintArg, match=r\"Extraneous argument 'xs\\.\\.\\.a'\"):\n        chz.Blueprint(MainList).apply({\"xs...a\": 5}).make()\n\n\ndef test_variadic_tuple():\n    @chz.chz\n    class X:\n        a: int\n\n    @chz.chz\n    class MainHomoTuple:\n        xs: tuple[X, ...]\n\n    assert chz.Blueprint(MainHomoTuple).apply(\n        {\"xs.0.a\": 1, \"xs.1.a\": 2, \"xs.2.a\": 3}\n    ).make() == MainHomoTuple(xs=(X(a=1), X(a=2), X(a=3)))\n\n    with pytest.raises(\n        MissingBlueprintArg, match=r\"Missing required arguments for parameter\\(s\\): xs\"\n    ):\n        chz.Blueprint(MainHomoTuple).make()\n\n    with pytest.raises(\n        MissingBlueprintArg, match=r\"Missing required arguments for parameter\\(s\\): xs.1.a\"\n    ):\n        chz.Blueprint(MainHomoTuple).apply({\"xs.0.a\": 1, \"xs.2.a\": 3}).make()\n\n    @chz.chz\n    class Y:\n        b: str\n\n    @chz.chz\n    class MainHeteroTuple:\n        xs: tuple[X, Y, X]\n\n    assert chz.Blueprint(MainHeteroTuple).apply(\n        {\"xs.0.a\": 1, \"xs.1.b\": \"str\", \"xs.2.a\": 3}\n    ).make() == MainHeteroTuple(xs=(X(a=1), Y(b=\"str\"), X(a=3)))\n\n    with pytest.raises(\n        TypeError,\n        match=r\"Tuple type tuple\\[.*X.*Y.*X\\] for 'xs' must take 3 items; arguments for index 9 were specified\",\n    ):\n        chz.Blueprint(MainHeteroTuple).apply({\"xs.0.a\": 1, \"xs.9.b\": \"str\"}).make()\n\n    with pytest.raises(ExtraneousBlueprintArg, match=r\"Extraneous argument 'xs.1.a'\"):\n        chz.Blueprint(MainHeteroTuple).apply({\"xs.0.a\": 1, \"xs.1.a\": 2, \"xs.2.a\": 3}).make()\n\n\ndef test_variadic_dict():\n    @chz.chz\n    class X:\n        a: int\n\n    @chz.chz\n    class MainDict:\n        xs: dict[str, X]\n\n    assert chz.Blueprint(MainDict).apply(\n        {\"xs.first.a\": 1, \"xs.second.a\": 2, \"xs.3.a\": 3}\n    ).make() == MainDict(xs={\"first\": X(a=1), \"second\": X(a=2), \"3\": X(a=3)})\n\n\ndef test_variadic_collections_type():\n    @chz.chz\n    class X:\n        a: int\n\n    @chz.chz\n    class Main:\n        seq: typing.Sequence[X]\n        map: typing.Mapping[str, X]\n\n    assert chz.Blueprint(Main).apply(\n        {\"seq.0.a\": 1, \"seq.1.a\": 2, \"map.first.a\": 3, \"map.second.a\": 4}\n    ).make() == Main(seq=(X(a=1), X(a=2)), map={\"first\": X(a=3), \"second\": X(a=4)})\n\n\ndef test_variadic_dict_non_int_or_str_key():\n    @chz.chz\n    class MainDict:\n        xs: dict[float, str]\n\n    with pytest.raises(TypeError, match=\"Variadic dict type must take str or int keys, not float\"):\n        chz.Blueprint(MainDict).apply({\"xs.0\": \"a\", \"xs.1\": \"2\"}).make()\n\n    assert chz.Blueprint(MainDict).apply({\"xs\": {1: \"2\"}}).make() == MainDict(xs={1: \"2\"})\n\n    @chz.chz\n    class MainDict2:\n        xs: dict[int, str] | None = None\n\n    assert chz.Blueprint(MainDict2).make() == MainDict2(xs=None)\n\n\ndef test_variadic_dict_unannotated():\n    @chz.chz\n    class MainDict:\n        xs: dict\n\n    assert chz.Blueprint(MainDict).apply({\"xs.0\": \"a\", \"xs.first\": 123}).make() == MainDict(\n        xs={\"0\": \"a\", \"first\": 123}\n    )\n\n\ndef test_variadic_typed_dict():\n    class Foo(typing.TypedDict):\n        bar: int\n        baz: str\n\n    @chz.chz(typecheck=True)\n    class Main:\n        foo: Foo\n\n    assert chz.Blueprint(Main).apply(\n        {\"foo.bar\": Castable(\"3\"), \"foo.baz\": Castable(\"43\")}\n    ).make() == Main(foo={\"bar\": 3, \"baz\": \"43\"})\n\n    with pytest.raises(\n        ExtraneousBlueprintArg, match=r\"Extraneous argument 'foo.typo' to Blueprint for .*Main\"\n    ):\n        chz.Blueprint(Main).apply({\"foo.bar\": 3, \"foo.typo\": \"baz\"}).make()\n\n    with pytest.raises(TypeError, match=r\"Expected 'foo.bar' to be int, got str\"):\n        chz.Blueprint(Main).apply({\"foo.bar\": \"bar\", \"foo.baz\": \"baz\"}).make()\n\n    with pytest.raises(\n        InvalidBlueprintArg,\n        match=(\n            \"Could not interpret argument 'bar' provided for param 'foo.bar'...\\n\\n\"\n            \"- Failed to interpret it as a value:\\n\"\n            \"Could not cast 'bar' to int\"\n        ),\n    ):\n        chz.Blueprint(Main).apply({\"foo.bar\": Castable(\"bar\"), \"foo.baz\": \"baz\"}).make()\n\n\ndef test_variadic_typed_dict_not_required():\n    class Foo(typing.TypedDict):\n        a: int\n        b: typing.Required[int]\n        c: typing.NotRequired[int]\n\n    class Bar(Foo, total=False):\n        d: int\n        e: typing.Required[int]\n        f: typing.NotRequired[int]\n\n    class Baz(Bar):\n        g: int\n        h: typing.Required[int]\n        i: typing.NotRequired[int]\n\n    @chz.chz\n    class Main:\n        foo: Foo\n        bar: Bar\n        baz: Baz\n\n    assert chz.Blueprint(Main).apply(\n        {\n            \"foo.a\": Castable(\"1\"),\n            \"foo.b\": Castable(\"2\"),\n            \"foo.c\": Castable(\"3\"),\n            \"bar.a\": Castable(\"4\"),\n            \"bar.b\": Castable(\"5\"),\n            \"bar.c\": Castable(\"6\"),\n            \"bar.d\": Castable(\"7\"),\n            \"bar.e\": Castable(\"8\"),\n            \"bar.f\": Castable(\"9\"),\n            \"baz.a\": Castable(\"10\"),\n            \"baz.b\": Castable(\"11\"),\n            \"baz.c\": Castable(\"12\"),\n            \"baz.d\": Castable(\"13\"),\n            \"baz.e\": Castable(\"14\"),\n            \"baz.f\": Castable(\"15\"),\n            \"baz.g\": Castable(\"16\"),\n            \"baz.h\": Castable(\"17\"),\n            \"baz.i\": Castable(\"18\"),\n        }\n    ).make() == Main(\n        foo={\"a\": 1, \"b\": 2, \"c\": 3},\n        bar={\"a\": 4, \"b\": 5, \"c\": 6, \"d\": 7, \"e\": 8, \"f\": 9},\n        baz={\"a\": 10, \"b\": 11, \"c\": 12, \"d\": 13, \"e\": 14, \"f\": 15, \"g\": 16, \"h\": 17, \"i\": 18},\n    )\n\n    # Test that c, d, f, i are not required\n    assert chz.Blueprint(Main).apply(\n        {\n            \"foo.a\": Castable(\"1\"),\n            \"foo.b\": Castable(\"2\"),\n            \"bar.a\": Castable(\"3\"),\n            \"bar.b\": Castable(\"4\"),\n            \"bar.e\": Castable(\"5\"),\n            \"baz.a\": Castable(\"6\"),\n            \"baz.b\": Castable(\"7\"),\n            \"baz.e\": Castable(\"8\"),\n            \"baz.g\": Castable(\"9\"),\n            \"baz.h\": Castable(\"10\"),\n        }\n    ).make() == Main(\n        foo={\"a\": 1, \"b\": 2},\n        bar={\"a\": 3, \"b\": 4, \"e\": 5},\n        baz={\"a\": 6, \"b\": 7, \"e\": 8, \"g\": 9, \"h\": 10},\n    )\n\n    # Test that a, b, e, g, h are required\n    with pytest.raises(\n        MissingBlueprintArg,\n        match=(\n            r\"Missing required arguments for parameter\\(s\\): \"\n            r\"foo.a, foo.b, bar.a, bar.b, bar.e, baz.a, baz.b, baz.e, baz.g, baz.h\"\n        ),\n    ):\n        chz.Blueprint(Main).make()\n\n    print(chz.Blueprint(Main).get_help())\n    assert (\n        chz.Blueprint(Main).get_help()\n        == \"\"\"WARNING: Missing required arguments for parameter(s): foo.a, foo.b, bar.a, bar.b, bar.e, baz.a, baz.b, baz.e, baz.g, baz.h\n\nEntry point: test_blueprint_variadic:test_variadic_typed_dict_not_required.<locals>.Main\n\nArguments:\n  foo    test_blueprint_variadic:test_variadic_typed_dict_not_required.<locals>.Foo        -\n  foo.a  int                                       -\n  foo.b  int                                       -\n  foo.c  int                                       typing.NotRequired (default)\n  bar    test_blueprint_variadic:test_variadic_typed_dict_not_required.<locals>.Bar        -\n  bar.a  int                                       -\n  bar.b  int                                       -\n  bar.c  int                                       typing.NotRequired (default)\n  bar.d  int                                       typing.NotRequired (default)\n  bar.e  int                                       -\n  bar.f  int                                       typing.NotRequired (default)\n  baz    test_blueprint_variadic:test_variadic_typed_dict_not_required.<locals>.Baz        -\n  baz.a  int                                       -\n  baz.b  int                                       -\n  baz.c  int                                       typing.NotRequired (default)\n  baz.d  int                                       typing.NotRequired (default)\n  baz.e  int                                       -\n  baz.f  int                                       typing.NotRequired (default)\n  baz.g  int                                       -\n  baz.h  int                                       -\n  baz.i  int                                       typing.NotRequired (default)\n\"\"\"\n    )\n\n\ndef test_variadic_default():\n    @chz.chz\n    class X:\n        a: int = 0\n\n    @chz.chz\n    class MainList:\n        xs: list[X]\n\n    assert chz.Blueprint(MainList).apply({\"xs.3.a\": 5}).make() == MainList(\n        xs=[X(a=0), X(a=0), X(a=0), X(a=5)]\n    )\n\n\ndef test_variadic_default_wildcard_error():\n    @chz.chz\n    class X:\n        a: int\n\n    @chz.chz\n    class MainList:\n        xs: list[X] = chz.field(default_factory=lambda: [X(a=0)])\n        a: int  # same name as X.a, to prevent unused wildcard error\n\n    with pytest.raises(\n        ConstructionException,\n        match=(\n            r'The parameter \"xs\" is variadic(.|\\n)*'\n            r'However, you also specified the wildcard \"\\.\\.\\.a\" and you may '\n            r'have expected it to modify the value of \"xs\\.\\(variadic\\)\\.a\"'\n        ),\n    ):\n        chz.Blueprint(MainList).apply({\"...a\": 1}).make()\n\n    @chz.chz\n    class MainListOk:\n        xs: list[X] = chz.field(default_factory=list)\n        a: int  # same name as X.a, to prevent unused wildcard error\n\n    assert chz.Blueprint(MainListOk).apply({\"...a\": 1}).make() == MainListOk(xs=[], a=1)\n\n\ndef test_variadic_default_wildcard_error_using_types_from_default():\n    @chz.chz\n    class Clause:\n        def value(self) -> bool:\n            raise NotImplementedError\n\n    @chz.chz\n    class SimpleClause(Clause):\n        val: bool\n\n        def value(self) -> bool:\n            return self.val\n\n    @chz.chz\n    class FalseClause(SimpleClause):\n        val: bool = False\n\n    @chz.chz\n    class AndClause(Clause):\n        clauses: tuple[Clause, ...] = ()\n\n        def value(self) -> bool:\n            return all(clause.value() for clause in self.clauses)\n\n    @chz.chz\n    class MyClause(AndClause):\n        # Need to check both Clause and FalseClause for wildcard matches\n        clauses: tuple[Clause, ...] = (FalseClause(), FalseClause())\n\n    with pytest.raises(\n        ConstructionException,\n        match=(\n            r'The parameter \"clauses.1.clauses\" is variadic(.|\\n)*'\n            r'However, you also specified the wildcard \"\\.\\.\\.val\" and you may '\n            r'have expected it to modify the value of \"clauses.1.clauses\\.\\(variadic\\)\\.val\"'\n        ),\n    ):\n        chz.Blueprint(AndClause).apply_from_argv(\n            [\"clauses.0=SimpleClause\", \"clauses.1=MyClause\", \"...val=True\"]\n        ).make()\n\n\ndef test_polymorphic_variadic_generic():\n    @chz.chz\n    class A:\n        a: int\n\n    @chz.chz\n    class AA(A): ...\n\n    @chz.chz\n    class MainList:\n        xs: list[A]\n\n    assert chz.Blueprint(MainList).apply(\n        {\"xs\": Castable(\"list[AA]\"), \"xs.0.a\": 1}\n    ).make() == MainList(xs=[AA(a=1)])\n\n    @chz.chz\n    class MainTuple:\n        xs: tuple[A, ...]\n\n    assert chz.Blueprint(MainTuple).apply(\n        {\"xs\": Castable(\"tuple[AA, ...]\"), \"xs.0.a\": 1}\n    ).make() == MainTuple(xs=(AA(a=1),))\n\n    @chz.chz\n    class MainListList:\n        xs: list[list[A]]\n\n    # This is gtting a little silly :-)\n    assert chz.Blueprint(MainListList).apply(\n        {\"xs\": Castable(\"list[list[AA]]\"), \"xs.0.0.a\": 1}\n    ).make() == MainListList(xs=[[AA(a=1)]])\n"
  },
  {
    "path": "tests/test_data_model.py",
    "content": "# ruff: noqa: F811\nimport dataclasses\nimport functools\nimport json\nimport re\nimport typing\n\nimport pytest\n\nimport chz\n\n\ndef test_basic():\n    @chz.chz\n    class X:\n        a: int\n\n    @chz.chz()\n    class Y:\n        a: int = 3\n\n    assert X(a=1).a == 1\n    assert Y().a == 3\n\n    assert chz.is_chz(X)\n    assert chz.is_chz(X(a=1))\n    assert not chz.is_chz(1)\n\n\nwith_future_annotation = \"\"\"\nfrom __future__ import annotations\ntry:\n    class _test:\n        _: _test\nexcept NameError:\n    import sys\n    if sys.version_info < (3, 14):\n        raise AssertionError(\"from __future__ import annotations should be imported\") from None\n\"\"\"\n\nwithout_future_annotation = \"\"\"\ntry:\n    class _test:\n        _: _test\nexcept NameError:\n    pass\nelse:\n    import sys\n    if sys.version_info < (3, 14):\n        raise AssertionError(\"from __future__ import annotations should not be imported\")\n\"\"\"\n\nbasic_definition = \"\"\"\n@chz.chz\nclass X:\n    a: int\n    b: int = chz.field()\n    c: str = \"yikes\"\n    d: str = chz.field(default=\"yonks\")\n    e: str = chz.field(default_factory=lambda: \"zeiks\")\n\"\"\"\n\n\ndef _test_construct_helper(X):\n    with pytest.raises(TypeError, match=\"missing 2 required keyword-only arguments: 'a' and 'b'\"):\n        X()\n\n    with pytest.raises(TypeError, match=\"missing 2 required keyword-only arguments: 'a' and 'b'\"):\n        X(1, 2)\n\n    with pytest.raises(TypeError, match=\"missing 2 required keyword-only arguments: 'a' and 'b'\"):\n        X(c=\"okay\")\n\n    x = X(a=1, b=2)\n    assert x.a == 1\n    assert x.b == 2\n    assert x.c == \"yikes\"\n    assert x.d == \"yonks\"\n    assert x.e == \"zeiks\"\n\n    x = X(a=3, b=4, c=\"hijinks\", d=\"iflunks\", e=\"jourks\")\n    assert x.a == 3\n    assert x.b == 4\n    assert x.c == \"hijinks\"\n    assert x.d == \"iflunks\"\n    assert x.e == \"jourks\"\n\n\ndef test_construct_without_future_annotations():\n    prog = without_future_annotation + basic_definition\n    ns = {}\n    exec(compile(prog, \"\", \"exec\", dont_inherit=True), {\"chz\": chz}, ns)\n    X = ns[\"X\"]\n    _test_construct_helper(X)\n\n\ndef test_construct_with_future_annotations():\n    prog = with_future_annotation + basic_definition\n    ns = {}\n    exec(compile(prog, \"\", \"exec\", dont_inherit=True), {\"chz\": chz}, ns)\n    X = ns[\"X\"]\n    _test_construct_helper(X)\n\n\ndef test_inheritance():\n    @chz.chz\n    class X:\n        a: int\n        b: str\n        c: str = chz.field(default=\"yikes\")\n\n    @chz.chz\n    class Y(X):\n        d: int\n        e: str = chz.field(default=\"yonks\")\n\n    value = Y(a=1, b=\"2\", d=3)\n    assert value.a == 1\n    assert value.b == \"2\"\n    assert value.c == \"yikes\"\n    assert value.d == 3\n    assert value.e == \"yonks\"\n\n    value = Y(a=1, b=\"2\", d=3, e=\"4\")\n    assert value.a == 1\n    assert value.b == \"2\"\n    assert value.c == \"yikes\"\n    assert value.d == 3\n    assert value.e == \"4\"\n\n    with pytest.raises(TypeError, match=\"missing 1 required keyword-only argument: 'd'\"):\n        Y(a=1, b=\"2\")  # type: ignore\n\n    with pytest.raises(\n        ValueError,\n        match=\"Cannot override field 'c' with a non-field member; maybe you're missing a type annotation?\",\n    ):\n\n        @chz.chz\n        class Z(X):\n            c = \"asdf\"\n\n    class NonChz(X):\n        pass\n\n    with pytest.raises(TypeError, match=\"NonChz is not decorated with @chz.chz\"):\n        NonChz(a=1, b=\"2\", c=\"3\")\n\n\ndef test_immutability():\n    @chz.chz\n    class X:\n        a: int\n\n    x = X(a=1)\n    with pytest.raises(chz.data_model.FrozenInstanceError):\n        x.a = 2  # type: ignore\n    with pytest.raises(chz.data_model.FrozenInstanceError):\n        x.b = 1  # type: ignore\n\n    @chz.chz\n    class Y:\n        a: int\n\n        @functools.cached_property\n        def b(self):\n            return self.a\n\n        @property\n        def c(self):\n            return self.a\n\n        @c.setter\n        def c(self, value):\n            self.a = value  # type: ignore\n\n        @chz.init_property\n        def d(self):\n            return self.a\n\n    y = Y(a=1)\n\n    assert y.b == 1\n    with pytest.raises(chz.data_model.FrozenInstanceError):\n        y.b = 2  # type: ignore\n\n    assert y.c == 1\n    with pytest.raises(chz.data_model.FrozenInstanceError):\n        y.c = 2  # type: ignore\n    with pytest.raises(chz.data_model.FrozenInstanceError):\n        del y.c  # type: ignore\n\n    assert y.d == 1\n    with pytest.raises(chz.data_model.FrozenInstanceError):\n        y.d = 2  # type: ignore\n\n    # Here's the loophole\n    object.__setattr__(y, \"a\", 2)\n    assert y.a == 2\n    assert y.b == 1\n    assert y.c == 2\n\n\ndef test_no_post_init():\n    with pytest.raises(ValueError, match=\"Cannot define __post_init__\"):\n\n        @chz.chz\n        class X:\n            a: int\n\n            def __post_init__(self):\n                pass\n\n\ndef test_no_annotation():\n    @chz.chz\n    class X:\n        a = 1\n\n    X()\n    with pytest.raises(TypeError, match=r\"__init__\\(\\) got an unexpected keyword argument 'a'\"):\n        X(a=11)\n\n\ndef test_asdict():\n    @chz.chz\n    class Y:\n        x: int\n        y: bool\n\n    @chz.chz\n    class X:\n        a: int\n        b: str\n        c: Y\n        d: dict[str, bool]\n        e: list[float]\n        f: tuple[int, ...]\n\n    x = X(a=1, b=\"2\", c=Y(x=3, y=True), d={\"a\": True}, e=[1.0, 2.0], f=(1, 2))\n\n    assert chz.asdict(x) == {\n        \"a\": 1,\n        \"b\": \"2\",\n        \"c\": {\"x\": 3, \"y\": True},\n        \"d\": {\"a\": True},\n        \"e\": [1.0, 2.0],\n        \"f\": (1, 2),\n    }\n\n\ndef test_asdict_computed_properties():\n    @chz.chz\n    class C:\n        x: float\n\n        @property\n        def doubled(self):\n            return self.x * 2\n\n        @functools.cached_property\n        def tripled(self):\n            return self.x * 3\n\n        @chz.init_property\n        def quadrupled(self):\n            return self.x * 4\n\n    c = C(x=1.0)\n    assert chz.asdict(c) == {\"x\": 1.0}\n\n    # Carefully test cached_property behavior. Cached properties which have\n    # not been accessed are not in the __dict__...\n    assert \"tripled\" not in c.__dict__\n    # ...accessing them adds them to the __dict__...\n    _ = c.tripled\n    assert \"tripled\" in c.__dict__\n\n    # ...but they still don't appear in the asdict output\n    assert chz.asdict(c) == {\"x\": 1.0}\n\n\ndef test_asdict_include_type():\n    @chz.chz\n    class X:\n        a: int\n        b: int\n\n    @chz.chz\n    class Y:\n        a: int\n        b: int\n\n    x = X(a=1, b=2)\n    y = Y(a=1, b=2)\n\n    assert chz.asdict(x) == chz.asdict(y)\n    assert chz.asdict(x, include_type=True) != chz.asdict(y, include_type=True)\n\n\n@chz.chz\nclass Outer:\n    @chz.chz\n    class Config:\n        v: int\n\n\ndef test_asdict_include_type_nested_class():\n    cfg = Outer.Config(v=1)\n    assert chz.asdict(cfg, include_type=True)[\"__chz_type__\"] == \"test_data_model:Outer.Config\"\n\n\ndef test_asdict_exclude():\n    @chz.chz\n    class Inner:\n        x: int\n        b: int\n\n    @chz.chz\n    class OuterLocal:\n        a: int\n        b: int\n        inner: Inner\n\n    obj = OuterLocal(a=1, b=2, inner=Inner(x=3, b=4))\n    assert chz.asdict(obj, exclude={\"b\"}) == {\"a\": 1, \"inner\": {\"x\": 3, \"b\": 4}}\n\n\ndef test_replace():\n    @chz.chz\n    class X:\n        a: int\n        b: int\n\n    x = X(a=1, b=2)\n\n    y = chz.replace(x, a=3)\n    assert y is not x\n    assert x.a == 1\n    assert x.b == 2\n    assert y.a == 3\n    assert y.b == 2\n\n    z = chz.replace(y, a=4, b=5)\n    assert z is not x\n    assert z is not y\n    assert y.a == 3\n    assert y.b == 2\n    assert z.a == 4\n    assert z.b == 5\n\n    @chz.chz\n    class Y:\n        a: int\n        X_e: int\n\n        @functools.cached_property\n        def b(self):\n            return self.a\n\n        @property\n        def c(self):\n            return self.a\n\n        @c.setter\n        def c(self, value):\n            self.a = value  # type: ignore\n\n        @chz.init_property\n        def d(self):\n            return self.a\n\n    y = Y(a=1, e=11)\n    y = chz.replace(y, a=2)\n    y = chz.replace(y, e=12)\n    with pytest.raises(TypeError, match=r\"__init__\\(\\) got an unexpected keyword argument 'b'\"):\n        chz.replace(y, b=1)\n    with pytest.raises(TypeError, match=r\"__init__\\(\\) got an unexpected keyword argument 'c'\"):\n        chz.replace(y, c=1)\n    with pytest.raises(TypeError, match=r\"__init__\\(\\) got an unexpected keyword argument 'd'\"):\n        chz.replace(y, d=1)\n    with pytest.raises(TypeError, match=r\"__init__\\(\\) got an unexpected keyword argument 'X_e'\"):\n        chz.replace(y, X_e=1)\n\n\ndef test_repr():\n    @chz.chz\n    class X:\n        a: int\n        b: int\n\n    assert repr(X(a=1, b=2)) == \"test_repr.<locals>.X(a=1, b=2)\"\n\n    @chz.chz\n    class Y:\n        X_seed1: int\n\n        @chz.init_property\n        def seed1(self):\n            return self.X_seed1 + 10\n\n    assert repr(Y(seed1=1)) == \"test_repr.<locals>.Y(seed1=1)\"\n\n    @chz.chz\n    class Z:\n        a: int = chz.field(repr=False)\n        b: int = chz.field(repr=lambda x: f\"?{x}?\")\n\n    assert repr(Z(a=1, b=2)) == \"test_repr.<locals>.Z(a=..., b=?2?)\"\n\n\ndef test_eq():\n    @chz.chz\n    class X:\n        a: int\n        b: int\n\n    x = X(a=1, b=2)\n    y = X(a=1, b=2)\n    z = X(a=1, b=3)\n    assert x == y\n    assert x != z\n    assert x != 1\n\n\ndef test_hash():\n    @chz.chz\n    class X:\n        a: int\n        b: int\n\n        @chz.init_property\n        def c(self):\n            return self.b + 1\n\n    x = X(a=1, b=2)\n    y = X(a=1, b=2)\n    x2 = chz.replace(x, a=2)\n    z = X(a=1, b=3)\n    assert hash(x) == hash(y)\n    assert hash(x) != hash(z)\n    assert hash(x2) != hash(x)\n\n    @chz.chz\n    class Q:\n        a: list[int] = chz.field(default_factory=lambda: [1, 2, 3])\n\n    q = Q()\n    with pytest.raises(TypeError, match=re.escape(\"Cannot hash chz field: Q.a=[1, 2, 3]\")):\n        hash(q)\n\n    @chz.chz\n    class R:\n        a: tuple[int, ...] = chz.field(default_factory=lambda: (1, 2, 3))\n\n    # Tuples are hashable.\n    hash(R())\n\n    value = 0\n\n    @chz.chz\n    class S:\n        @chz.init_property\n        def a(self):\n            nonlocal value\n            value += 1\n            return value\n\n    # Since init property is not adding a __chz_fields__\n    # the instances of S result in the same hash value\n    # despite not having the same a value\n    s1 = S()\n    s2 = S()\n    assert hash(s1) == hash(s2)\n    assert s1.a != s2.a\n\n    @chz.chz\n    class T:\n        X_a: int\n\n        @chz.init_property\n        def a(self):\n            return [self.X_a]\n\n    with pytest.raises(TypeError):\n        hash(T(a=1))\n\n    @chz.chz\n    class U:\n        X_a: list[int]\n\n        @chz.init_property\n        def a(self):\n            return tuple(self.X_a)\n\n    hash(U(a=[1, 2, 3]))\n\n\ndef test_blueprint_values():\n    @chz.chz\n    class Y:\n        c: int\n\n        @chz.init_property\n        def b(self):\n            return self.c + 1\n\n    @chz.chz\n    class X:\n        b: int\n        c: Y = chz.field(default_factory=lambda: Y(c=1))\n\n        @chz.init_property\n        def a(self):\n            return self.c.b + self.b\n\n    x = X(b=2)\n\n    assert x.a == 4  # (c=1) + 1 + (b=2)\n    assert x.c.b == 2  # (c=1) + 1\n    values = chz.beta_to_blueprint_values(x)\n    assert values == {\"b\": 2, \"c\": Y, \"c.c\": 1}\n    assert chz.Blueprint(X).apply(values).make() == x\n\n    x = X(b=2, c=Y(c=3))\n    assert x.a == 6  # (c=3) + 1 + (b=2)\n    assert x.c.b == 4  # (c=3) + 1\n    values = chz.beta_to_blueprint_values(x)\n    assert values == {\"b\": 2, \"c\": Y, \"c.c\": 3}\n    assert chz.Blueprint(X).apply(values).make() == x\n\n    @chz.chz\n    class Q:\n        a: int\n        b: tuple[int, ...] = chz.field(default_factory=lambda: (1, 2, 3))\n\n    q = Q(a=1)\n    values = chz.beta_to_blueprint_values(q)\n    assert values == {\"a\": 1, \"b\": (1, 2, 3)}\n    assert chz.Blueprint(Q).apply(values).make() == q\n\n    @chz.chz\n    class R:\n        a: int\n        b: list[int] = chz.field(default_factory=lambda: [1, 2, 3])\n\n    r = R(a=1)\n\n    # Ensure that castable + json mostly works\n    values = chz.beta_to_blueprint_values(r)\n    assert (\n        chz.Blueprint(R)\n        .apply(\n            {key: chz.Castable(str(value)) for key, value in json.loads(json.dumps(values)).items()}\n        )\n        .make()\n        == r\n    )\n\n    # This test ensures that we handle the following as expected:\n    # - default_factory\n    # - munged values\n    # - castable values\n    @chz.chz\n    class T:\n        default_factory: str = chz.field(default_factory=lambda: \"?\")\n        default_value: str = chz.field(default=\"!\")\n        munged_value: str = chz.field(default=\"?\", munger=lambda instance, value: value + \"!!\")\n\n    t = T(munged_value=\"Hello\")\n    values = chz.beta_to_blueprint_values(t)\n    assert values == {\"default_factory\": \"?\", \"default_value\": \"!\", \"munged_value\": \"Hello\"}\n    assert chz.Blueprint(T).apply(values).make() == t\n\n    # This test is dedicated to testing that `x_type`/`blueprint_cast` works fine\n    @chz.chz\n    class U:\n        value: int = chz.field(x_type=str, blueprint_cast=int)\n\n    u = U(value=\"7\")\n\n    # The type of the blueprint should be the one we are supposed to pass in the blueprint\n    # Not the one after instantiation\n    values = chz.beta_to_blueprint_values(u)\n    assert values == {\"value\": \"7\"}\n    assert chz.Blueprint(U).apply(values).make() == u\n\n    # This test verifies that derived properties aren't serialized and X_ fields are not exposed\n    @chz.chz\n    class W:\n        value: int\n\n        @chz.init_property\n        def value_2(self) -> int:\n            return self.value * 2\n\n    w = W(value=5)\n    values = chz.beta_to_blueprint_values(w)\n    assert values == {\"value\": 5}\n    assert \"value_2\" not in values\n    assert \"value\" in values\n    assert w.value_2 == 10\n\n\ndef test_blueprint_values_polymorphic():\n    @chz.chz\n    class X:\n        a: int\n\n        @property\n        def name(self) -> str:\n            return \"x\"\n\n    @chz.chz\n    class Y(X):\n        b: int\n\n        @property\n        def name(self) -> str:\n            return \"y\"\n\n    @chz.chz\n    class Z(X):\n        c: int\n\n        @property\n        def name(self) -> str:\n            return \"z\"\n\n    @chz.chz\n    class Y2(Y):\n        d: int\n\n        @property\n        def name(self) -> str:\n            return \"y2\"\n\n    @chz.chz\n    class W:\n        x: X = chz.field(meta_factory=chz.factories.subclass(base_cls=X, default_cls=Y))\n        w: int\n\n    w = W(x=Y(a=1, b=2), w=3)\n    values = chz.beta_to_blueprint_values(w)\n    assert values == {\"x\": Y, \"x.a\": 1, \"x.b\": 2, \"w\": 3}\n    w_new = chz.Blueprint(W).apply(values).make()\n    assert w_new == w\n    assert w_new.x.name == \"y\"\n\n    w = W(x=Z(a=1, c=2), w=3)\n    values = chz.beta_to_blueprint_values(w)\n    assert values == {\"x\": Z, \"x.a\": 1, \"x.c\": 2, \"w\": 3}\n    w_new = chz.Blueprint(W).apply(values).make()\n    assert w_new == w\n    assert w_new.x.name == \"z\"\n\n    w = W(x=Y2(a=1, b=2, d=3), w=4)\n    values = chz.beta_to_blueprint_values(w)\n    assert values == {\"x\": Y2, \"x.a\": 1, \"x.b\": 2, \"x.d\": 3, \"w\": 4}\n    w_new = chz.Blueprint(W).apply(values).make()\n    assert w_new == w\n    assert w_new.x.name == \"y2\"\n\n    @chz.chz\n    class W_Union:\n        x: Y | Z\n        w: int\n\n    wu = W_Union(x=Y(a=1, b=2), w=3)\n    values = chz.beta_to_blueprint_values(wu)\n    assert values == {\"x\": Y, \"x.a\": 1, \"x.b\": 2, \"w\": 3}\n    wu_new = chz.Blueprint(W_Union).apply(values).make()\n    assert wu_new == wu\n    assert wu_new.x.name == \"y\"\n\n    wu = W_Union(x=Z(a=1, c=2), w=3)\n    values = chz.beta_to_blueprint_values(wu)\n    assert values == {\"x\": Z, \"x.a\": 1, \"x.c\": 2, \"w\": 3}\n    wu_new = chz.Blueprint(W_Union).apply(values).make()\n    assert wu_new == wu\n    assert wu_new.x.name == \"z\"\n\n\ndef test_blueprint_values_variadic():\n    @chz.chz\n    class A:\n        a: int\n\n    @chz.chz\n    class B(A):\n        b: int\n\n    @chz.chz\n    class Main:\n        list_a: list[A]\n        dict_a: dict[str, A]\n\n    main = Main(list_a=[A(a=1), B(a=2, b=3)], dict_a={\"a\": A(a=4), \"b\": B(a=5, b=6)})\n    values = chz.beta_to_blueprint_values(main)\n    assert values == {\n        \"list_a.0\": A,\n        \"list_a.0.a\": 1,\n        \"list_a.1\": B,\n        \"list_a.1.a\": 2,\n        \"list_a.1.b\": 3,\n        \"dict_a.a\": A,\n        \"dict_a.a.a\": 4,\n        \"dict_a.b\": B,\n        \"dict_a.b.a\": 5,\n        \"dict_a.b.b\": 6,\n    }\n    assert chz.Blueprint(Main).apply(values).make() == main\n\n    @chz.chz\n    class Main:\n        list_a: list[A | int]\n        dict_a: dict[str, A | int]\n\n    main = Main(list_a=[A(a=1), 2], dict_a={\"a\": A(a=4), \"b\": 5})\n    values = chz.beta_to_blueprint_values(main)\n    assert values == {\n        \"list_a.0\": A,\n        \"list_a.0.a\": 1,\n        \"list_a.1\": 2,\n        \"dict_a.a\": A,\n        \"dict_a.a.a\": 4,\n        \"dict_a.b\": 5,\n    }\n    assert chz.Blueprint(Main).apply(values).make() == main\n\n    main = Main(list_a=[1, 2], dict_a={\"a\": 3, \"b\": 4})\n    values = chz.beta_to_blueprint_values(main)\n    assert values == {\n        \"list_a\": [1, 2],\n        \"dict_a\": {\"a\": 3, \"b\": 4},\n    }\n    assert chz.Blueprint(Main).apply(values).make() == main\n\n\ndef test_blueprint_values_skip_defaults():\n    @chz.chz\n    class Y:\n        c: int\n        d: int = 3\n\n        @chz.init_property\n        def b(self):\n            return self.c + 1\n\n    @chz.chz\n    class X:\n        b: int\n        c: Y = chz.field(default_factory=lambda: Y(c=1))\n        e: int = 4\n        f: int = 5\n\n        @chz.init_property\n        def a(self):\n            return self.c.b + self.b\n\n    x = X(b=2, e=4, f=6)\n    values = chz.beta_to_blueprint_values(x, skip_defaults=True)\n    assert values == {\"b\": 2, \"c\": Y, \"c.c\": 1, \"f\": 6}\n    assert chz.Blueprint(X).apply(values).make() == x\n\n    x = X(b=2, c=Y(c=3, d=4), f=5)\n    values = chz.beta_to_blueprint_values(x, skip_defaults=True)\n    assert values == {\"b\": 2, \"c\": Y, \"c.c\": 3, \"c.d\": 4}\n    assert chz.Blueprint(X).apply(values).make() == x\n\n    # Does not skip default values generated by default_factory\n    @chz.chz\n    class Q:\n        a: int\n        b: tuple[int, ...] = chz.field(default_factory=lambda: (1, 2, 3))\n        c: tuple[int, ...] = (4, 5, 6)\n\n    q = Q(a=1)\n    values = chz.beta_to_blueprint_values(q, skip_defaults=True)\n    assert values == {\"a\": 1, \"b\": (1, 2, 3)}\n    assert chz.Blueprint(Q).apply(values).make() == q\n\n\ndef test_blueprint_values_unspecified_sequence():\n    @chz.chz\n    class Element:\n        v: int\n\n    @chz.chz\n    class TheClass:\n        fixed_tuple: tuple[Element, Element] = chz.field(blueprint_unspecified=lambda: 1 / 0)\n        var_tuple: tuple[Element, ...] = chz.field(blueprint_unspecified=lambda: 1 / 0)\n        fixed_list: list[Element] = chz.field(blueprint_unspecified=lambda: 1 / 0)\n        var_list: list[Element] = chz.field(blueprint_unspecified=lambda: 1 / 0)\n\n    obj1 = TheClass(\n        fixed_tuple=(Element(v=1), Element(v=2)),\n        var_tuple=(Element(v=3), Element(v=4), Element(v=5)),\n        fixed_list=[Element(v=6), Element(v=7)],\n        var_list=[Element(v=8), Element(v=9), Element(v=10)],\n    )\n    vals = chz.beta_to_blueprint_values(obj1)\n    obj2 = chz.Blueprint(TheClass).apply(vals).make()\n    assert obj1 == obj2\n\n\ndef test_duplicate_fields():\n    # There is no way to detect this since __annotations__ is a dictionary\n    @chz.chz\n    class X:\n        a: int\n        a: int  # noqa: PIE794\n\n    X(a=1)\n\n\ndef test_no_type_annotation_on_field():\n    with pytest.raises(TypeError, match=\"'a' has no type annotation\"):\n\n        @chz.chz\n        class X:\n            a = chz.field(default=0)\n\n\ndef test_logical_name():\n    @chz.chz\n    class X:\n        X_seed1: int\n        seed2: int\n\n        @property\n        def seed1(self):\n            return self.X_seed1 + 100\n\n    assert len(X.__chz_fields__) == 2\n    assert X.__chz_fields__[\"seed1\"].logical_name == \"seed1\"\n    assert X.__chz_fields__[\"seed2\"].logical_name == \"seed2\"\n\n    x = X(seed1=1, seed2=2)\n    assert x.seed1 == 101\n    assert x.X_seed1 == 1\n    assert x.seed2 == 2\n    assert x.X_seed2 == 2\n\n\ndef test_init_property():\n    value = 0\n\n    @chz.chz\n    class A:\n        @chz.init_property\n        def a(self):\n            nonlocal value\n            value += 1\n            return value\n\n    @chz.chz\n    class B(A):\n        @chz.init_property\n        def b(self):\n            nonlocal value\n            value += 1\n            return value\n\n    b1 = B()\n    assert b1.a == 1\n    assert b1.b == 2\n\n    value = 10\n\n    b2 = B()\n    assert b2.a == 11\n    assert b2.b == 12\n\n    assert b1.a == 1\n    assert b1.b == 2\n\n    @chz.chz\n    class X:\n        @chz.init_property\n        def a(self):\n            raise RuntimeError\n\n    with pytest.raises(RuntimeError):\n        X()\n\n    @chz.chz\n    class Y(X):\n        pass\n\n    with pytest.raises(RuntimeError):\n        Y()\n\n\ndef test_init_property_top_level():\n    # There isn't really anything special about init_property, you can just use it as a one-liner\n    # if you don't care about static type checking. It's probably better to use a munger though\n    # Note that in this case it is possible for init_property to get called more than once\n\n    @chz.chz\n    class A:\n        a: int\n        b = chz.init_property(lambda self: self.a + 1)\n\n    a = A(a=1)\n    assert a.__dict__ == {\"X_a\": 1, \"a\": 1, \"b\": 2}\n    assert a.b == 2\n\n    with pytest.raises(ValueError, match=\"Field 'b' is clobbered by chz.data_model.init_property\"):\n\n        @chz.chz\n        class B:\n            a: int\n            b: int = chz.init_property(lambda self: self.a + 1)  # with type annotation\n\n\ndef test_default_init_property():\n    @chz.chz\n    class A:\n        attr: int\n\n    a = A(attr=1)\n    assert a.__dict__ == {\"X_attr\": 1, \"attr\": 1}\n    assert a.attr == 1\n    assert a.__dict__ == {\"X_attr\": 1, \"attr\": 1}\n\n\ndef test_init_property_x_field():\n    @chz.chz\n    class A:\n        X_attr: int\n\n        @chz.init_property\n        def attr(self):\n            return self.X_attr + 1\n\n    a = A(attr=1)\n    assert a.X_attr == 1\n    assert a.attr == 2\n    assert a.__dict__ == {\"X_attr\": 1, \"attr\": 2}\n\n\ndef test_conflicting_superclass_no_fields_in_base():\n    @chz.chz\n    class BaseA:\n        def method(self):\n            return 1\n\n        @property\n        def prop(self):\n            return 1\n\n        @chz.init_property\n        def init_prop(self):\n            return 1\n\n    with pytest.raises(\n        ValueError,\n        match=\"Cannot define field 'method' because it conflicts with something defined on a superclass\",\n    ):\n\n        @chz.chz\n        class A1(BaseA):\n            method: int\n\n    with pytest.raises(\n        ValueError,\n        match=\"Cannot define field 'X_method' because it conflicts with something defined on a superclass\",\n    ):\n\n        @chz.chz\n        class A1X(BaseA):\n            X_method: int\n\n    with pytest.raises(\n        ValueError,\n        match=\"Cannot define field 'prop' because it conflicts with something defined on a superclass\",\n    ):\n\n        @chz.chz\n        class A2(BaseA):\n            prop: int\n\n    with pytest.raises(\n        ValueError,\n        match=\"Cannot define field 'X_prop' because it conflicts with something defined on a superclass\",\n    ):\n\n        @chz.chz\n        class A2X(BaseA):\n            X_prop: int\n\n    with pytest.raises(\n        ValueError,\n        match=\"Cannot define field 'init_prop' because it conflicts with something defined on a superclass\",\n    ):\n\n        @chz.chz\n        class A3(BaseA):\n            init_prop: int\n\n        # We could consider allowing this. In which case, you want:\n        # assert A3(init_prop=2).X_init_prop == 2\n        # assert A3(init_prop=2).init_prop == 2\n\n    with pytest.raises(\n        ValueError,\n        match=\"Cannot define field 'X_init_prop' because it conflicts with something defined on a superclass\",\n    ):\n\n        @chz.chz\n        class A3X(BaseA):\n            X_init_prop: int\n\n\ndef test_conflicting_superclass_field_in_base():\n    @chz.chz\n    class BaseB:\n        field: int = 0\n\n    assert BaseB().X_field == 0\n    assert BaseB().field == 0\n\n    @chz.chz\n    class B1(BaseB):\n        X_field: int = 1\n\n    assert B1().X_field == 1\n    assert B1().field == 1\n\n    @chz.chz\n    class B2(BaseB):\n        X_field: int = 1\n\n        @chz.init_property\n        def field(self):\n            return self.X_field + 10\n\n    assert B2().X_field == 1\n    assert B2().field == 11\n\n    @chz.chz\n    class B3(BaseB):\n        @chz.init_property\n        def field(self):\n            return self.X_field + 100\n\n    assert B3().X_field == 0\n    assert B3().field == 100\n\n    @chz.chz\n    class B4(BaseB):\n        field: int = 1\n\n    assert B4().X_field == 1\n    assert B4().field == 1\n\n\ndef test_conflicting_superclass_x_field_in_base():\n    @chz.chz\n    class BaseC:\n        X_field: int = 0\n\n        @chz.init_property\n        def field(self):\n            return self.X_field + 10\n\n    assert BaseC().X_field == 0\n    assert BaseC().field == 10\n\n    @chz.chz\n    class C1(BaseC):\n        X_field: int = 1\n\n    assert C1().X_field == 1\n    assert C1().field == 11\n\n    @chz.chz\n    class C2(BaseC):\n        @chz.init_property\n        def field(self):\n            return self.X_field + 100\n\n    assert C2().X_field == 0\n    assert C2().field == 100\n\n    with pytest.raises(ValueError, match=\"little unsure of what the semantics should be here\"):\n\n        @chz.chz\n        class C3(BaseC):\n            field: int = 1\n\n        # assert C3().X_field == 1\n        # Should this be 11 or 1?\n        # The argument for 11 is that it's exactly the same case as C1\n        # The argument for 1 is that it matches go-to-definition better\n        # I'm in favour of 11, but I'll stick with a custom error for now...\n        # assert C3().field == 11\n\n\ndef test_field_clobbering_in_same_class():\n    with pytest.raises(ValueError, match=\"Field 'a' is clobbered by chz.data_model.init_property\"):\n\n        @chz.chz\n        class X:\n            a: int = 1\n\n            @chz.init_property\n            def a(self):\n                return 1\n\n    with pytest.raises(ValueError, match=\"Field 'a' is clobbered by function\"):\n\n        @chz.chz\n        class Y:\n            a: int = 1\n\n            def a(self):\n                return 1\n\n    @chz.chz\n    class OK1:\n        # lambdas are special cased (since they're more likely to be default values than methods)\n        a: typing.Callable[[], int] = lambda: 1\n\n    @chz.chz\n    class OK2:\n        a: typing.Callable[[], None] = test_field_clobbering_in_same_class\n\n\ndef test_dataclass_errors():\n    with pytest.raises(\n        RuntimeError,\n        match=r\"Something has gone horribly awry; are you using a chz.Field in a dataclass\\?\",\n    ):\n\n        @dataclasses.dataclass\n        class X:\n            a: int = chz.field(default=1)\n\n\ndef test_cloudpickle_main():\n    import cloudpickle  # noqa: F401\n\n    main = \"\"\"\nimport chz\n\nfrom threading import Lock\nunpickleable = Lock()\n\nclass Normal:\n    def __repr__(self):\n        return \"normal\"\n\nassert __name__ == \"__main__\"\n\n@chz.chz\nclass X:\n    one: int\n    norm: Normal = chz.field(default_factory=lambda: Normal())\n\nimport base64\nimport cloudpickle\nprint(base64.b64encode(cloudpickle.dumps(X(one=1))).decode())\n\"\"\"\n    import base64\n    import pickle\n    import subprocess\n    import sys\n\n    pickled = subprocess.check_output([sys.executable, \"-c\", main])\n    try:\n        unpickled = pickle.loads(base64.b64decode(pickled))\n    except pickle.UnpicklingError as e:\n        e.add_note(\"Maybe you forgot to remove a print statement?\")\n        raise\n    assert unpickled.one == 1\n    assert repr(unpickled) == \"X(one=1, norm=normal)\"\n\n\ndef test_protocol():\n    with pytest.raises(TypeError, match=\"chz class cannot itself be a Protocol\"):\n\n        @chz.chz\n        class Disallowed(typing.Protocol):\n            a: int\n\n    class Proto(typing.Protocol):\n        a: int\n\n    @chz.chz\n    class Allowed(Proto):\n        pass\n\n    # Protocol fields do not become chz fields automatically\n    Allowed()\n\n\ndef test_abc():\n    import abc\n\n    @chz.chz\n    class IDontMakeAnyPromisesAboutBehaviourHere(abc.ABC):\n        a: int\n\n    class Abc(abc.ABC):\n        a: int\n\n    @chz.chz\n    class Allowed(Abc):\n        pass\n\n    # ABC fields do not become chz fields automatically\n    Allowed()\n\n\ndef test_pretty_format():\n    from chz.data_model import pretty_format\n\n    @chz.chz\n    class Child:\n        name: str\n        age: int\n\n    @chz.chz\n    class Parent:\n        name: str\n        age: int\n        X_nickname: str | None = None\n        child: Child = chz.field(default_factory=lambda: Child(name=\"bob\", age=1))\n\n        @chz.init_property\n        def nickname(self) -> str:\n            return self.X_nickname or self.name\n\n    obj = Parent(name=\"alice\", age=30)\n    expected = f\"\"\"{Parent.__qualname__}(\n    age=30,\n    name='alice',\n    # Fields where pre-init value matches default:\n    child={Child.__qualname__}(\n        age=1,\n        name='bob',\n    ),\n    nickname=None  # 'alice' (after init),\n)\"\"\"\n    assert pretty_format(obj, colored=False) == expected\n\n    @chz.chz\n    class Collection:\n        children: list[Child]\n        named_children: dict[str, Child]\n\n    obj = Collection(\n        children=[Child(name=\"alice\", age=1)],\n        named_children={\"bob\": Child(name=\"bob\", age=2)},\n    )\n    assert (\n        pretty_format(obj, colored=False)\n        == \"\"\"test_pretty_format.<locals>.Collection(\n    children=[\n        test_pretty_format.<locals>.Child(\n            age=1,\n            name='alice',\n        ),\n    ],\n    named_children={\n        'bob': test_pretty_format.<locals>.Child(\n            age=2,\n            name='bob',\n        ),\n    },\n)\"\"\"\n    )\n\n\ndef test_metadata():\n    @chz.chz\n    class X:\n        a: int = chz.field(metadata={\"foo\": \"bar\"})\n\n    assert X.__chz_fields__[\"a\"].metadata == {\"foo\": \"bar\"}\n\n\ndef test_traverse():\n    @chz.chz\n    class A:\n        a_value: int = 15\n\n    @chz.chz\n    class B:\n        a: A = chz.field(default_factory=A)\n        b_value: str = \"hi\"\n\n    @chz.chz\n    class C:\n        ba: tuple[A | B, ...] = (A(), B())\n        c_value: tuple[str, ...] = (\"hello\", \"world\")\n\n    assert list(chz.traverse(C())) == [\n        (\"\", C(ba=(A(a_value=15), B(a=A(a_value=15), b_value=\"hi\")), c_value=(\"hello\", \"world\"))),\n        (\"ba\", (A(a_value=15), B(a=A(a_value=15), b_value=\"hi\"))),\n        (\"ba.0\", A(a_value=15)),\n        (\"ba.0.a_value\", 15),\n        (\"ba.1\", B(a=A(a_value=15), b_value=\"hi\")),\n        (\"ba.1.a\", A(a_value=15)),\n        (\"ba.1.a\", A(a_value=15)),\n        (\"ba.1.a.a_value\", 15),\n        (\"ba.1.b_value\", \"hi\"),\n        (\"c_value\", (\"hello\", \"world\")),\n        (\"c_value.0\", \"hello\"),\n        (\"c_value.1\", \"world\"),\n    ]\n\n\ndef test_int_dict_keys():\n    @chz.chz\n    class A:\n        int_keyed: dict[int, str]\n        str_keyed: dict[str, str]\n\n    a = chz.Blueprint(A).make_from_argv(\n        [\n            \"int_keyed.1=one\",\n            \"int_keyed.2=two\",\n            \"str_keyed.a=ay\",\n            \"str_keyed.b=bee\",\n        ]\n    )\n    assert a.int_keyed == {1: \"one\", 2: \"two\"}\n    assert a.str_keyed == {\"a\": \"ay\", \"b\": \"bee\"}\n"
  },
  {
    "path": "tests/test_factories.py",
    "content": "\"\"\"\n\nWatch out for some of the extra parentheses in these tests.\n\n\"\"\"\n\nimport typing\n\nimport pytest\n\nfrom chz.factories import MetaFromString, standard\n\n\nclass A: ...\n\n\nclass B(A): ...\n\n\nB_alias = B\n\n\nclass C(B): ...\n\n\nclass X: ...\n\n\ndef foo():\n    return A()\n\n\nbar = 0\n\n\na = A()\n\n\ndef test_standard_subclass():\n    f = standard(annotation=A)\n\n    assert f.unspecified_factory() is A\n\n    assert f.from_string(\"A\") is A\n    assert f.from_string(\"B\") is B\n    assert f.from_string(\"C\") is C\n\n    with pytest.raises(MetaFromString, match=\"No subclass of test_factories:A named 'X'\"):\n        f.from_string(\"X\")\n    with pytest.raises(\n        MetaFromString,\n        match=\"Expected test_factories:X from 'test_factories:X' to be a subtype of test_factories:A\",\n    ):\n        f.from_string(f\"{__name__}:X\")\n\n    assert f.from_string(f\"{__name__}:A\") is A\n    assert f.from_string(f\"{__name__}:B\") is B\n    assert f.from_string(f\"{__name__}:C\") is C\n\n    assert f.from_string(f\"{__name__}.A\") is A\n    assert f.from_string(f\"{__name__}.B\") is B\n    assert f.from_string(f\"{__name__}.C\") is C\n\n\ndef test_standard_subclass_unspecified():\n    f = standard(annotation=A, unspecified=B)\n\n    assert f.unspecified_factory() is B\n\n    assert f.from_string(\"A\") is A\n    assert f.from_string(\"B\") is B\n    assert f.from_string(\"C\") is C\n\n    with pytest.raises(MetaFromString, match=\"No subclass of test_factories:A named 'X'\"):\n        f.from_string(\"X\")\n\n    assert f.from_string(f\"{__name__}:A\") is A\n    assert f.from_string(f\"{__name__}:B\") is B\n    assert f.from_string(f\"{__name__}:C\") is C\n\n\ndef test_standard_subclass_module():\n    f = standard(annotation=A)\n    with pytest.raises(MetaFromString, match=\"No subclass of test_factories:A named 'a'\"):\n        f.from_string(\"a\")\n    with pytest.raises(MetaFromString, match=\"No subclass of test_factories:A named 'foo'\"):\n        f.from_string(\"foo\")\n    with pytest.raises(MetaFromString, match=\"No subclass of test_factories:A named 'bar'\"):\n        f.from_string(\"bar\")\n    with pytest.raises(MetaFromString, match=\"No subclass of test_factories:A named 'X'\"):\n        f.from_string(\"X\")\n    assert f.from_string(f\"{__name__}:a\")() is a\n    assert f.from_string(f\"{__name__}:foo\") is foo\n    with pytest.raises(MetaFromString, match=\"Expected 0 from 'test_factories:bar' to be callable\"):\n        f.from_string(f\"{__name__}:bar\")\n\n    f = standard(annotation=A, default_module=__name__)\n    assert f.from_string(\"a\")() is a\n    assert f.from_string(\"foo\") is foo\n    with pytest.raises(MetaFromString, match=\"No subclass of test_factories:A named 'bar'\"):\n        assert f.from_string(\"bar\") is foo\n    with pytest.raises(MetaFromString, match=\"No subclass of test_factories:A named 'X'\"):\n        f.from_string(\"X\")\n    assert f.from_string(f\"{__name__}:a\")() is a\n    assert f.from_string(f\"{__name__}:foo\") is foo\n    with pytest.raises(MetaFromString, match=\"Expected 0 from 'test_factories:bar' to be callable\"):\n        f.from_string(f\"{__name__}:bar\")\n    with pytest.raises(\n        MetaFromString,\n        match=\"Expected test_factories:X from 'test_factories:X' to be a subtype of test_factories:A\",\n    ):\n        f.from_string(f\"{__name__}:X\")\n\n\ndef test_standard_subclass_object_any():\n    import collections.abc\n\n    for any_object in (object, typing.Any):\n        f = standard(annotation=any_object)\n        with pytest.raises(MetaFromString, match=\"Could not find 'a', try a fully qualified name\"):\n            f.from_string(\"a\")\n        with pytest.raises(\n            MetaFromString, match=\"Could not find 'foo', try a fully qualified name\"\n        ):\n            f.from_string(\"foo\")\n        assert f.from_string(f\"{__name__}:a\")() is a\n        assert f.from_string(f\"{__name__}:foo\") is foo\n        assert f.from_string(f\"{__name__}:bar\")() is bar\n\n        f = standard(annotation=any_object, default_module=__name__)\n\n        assert f.from_string(\"a\")() is a\n        assert f.from_string(\"foo\") is foo\n        assert f.from_string(\"bar\")() is bar\n\n        assert f.from_string(\"collections.abc.MutableSequence\") is collections.abc.MutableSequence\n\n        f = standard(annotation=any_object, unspecified=type[object])\n        assert f.unspecified_factory() != type[object]\n        assert f.unspecified_factory()() is object\n\n        f = standard(annotation=any_object, unspecified=type)\n        assert f.unspecified_factory() is type\n\n\ndef test_standard_type_subclass():\n    f = standard(annotation=type[A])\n\n    assert f.unspecified_factory()() is A\n\n    assert f.from_string(\"A\")() is A\n    assert f.from_string(\"B\")() is B\n    assert f.from_string(\"C\")() is C\n\n    with pytest.raises(MetaFromString, match=\"No subclass of test_factories:A named 'X'\"):\n        f.from_string(\"X\")\n\n    assert f.from_string(f\"{__name__}:A\")() is A\n    assert f.from_string(f\"{__name__}:B\")() is B\n    assert f.from_string(f\"{__name__}:C\")() is C\n\n\ndef test_standard_type_subclass_unspecified():\n    f = standard(annotation=type[A], unspecified=type[B])\n\n    assert f.unspecified_factory()() is B\n\n    assert f.from_string(\"A\")() is A\n    assert f.from_string(\"B\")() is B\n    assert f.from_string(\"C\")() is C\n\n    with pytest.raises(MetaFromString, match=\"No subclass of test_factories:A named 'X'\"):\n        f.from_string(\"X\")\n\n    assert f.from_string(f\"{__name__}:A\")() is A\n    assert f.from_string(f\"{__name__}:B\")() is B\n    assert f.from_string(f\"{__name__}:C\")() is C\n\n\ndef test_standard_type_subclass_module():\n    f = standard(annotation=type[A])\n    with pytest.raises(MetaFromString, match=\"No subclass of test_factories:A named 'B_alias'\"):\n        f.from_string(\"B_alias\")\n    assert f.from_string(f\"{__name__}:B_alias\")() is B\n\n    f = standard(annotation=type[A], default_module=__name__)\n    assert f.from_string(\"B_alias\")() is B\n    assert f.from_string(f\"{__name__}:B_alias\")() is B\n\n\ndef test_standard_union():\n    f = standard(annotation=A | X)\n\n    assert f.unspecified_factory() is None\n\n    assert f.from_string(\"A\") is A\n    assert f.from_string(\"B\") is B\n    assert f.from_string(\"C\") is C\n    assert f.from_string(\"X\") is X\n\n    with pytest.raises(MetaFromString, match=\"Could not produce a union instance from 'object'\"):\n        f.from_string(\"object\")\n\n    assert f.from_string(f\"{__name__}:A\") is A\n    assert f.from_string(f\"{__name__}:B\") is B\n    assert f.from_string(f\"{__name__}:C\") is C\n    assert f.from_string(f\"{__name__}:X\") is X\n\n\ndef test_standard_union_unspecified():\n    f = standard(annotation=A | X, unspecified=B)\n\n    assert f.unspecified_factory() is B\n\n    assert f.from_string(\"A\") is A\n    assert f.from_string(\"B\") is B\n    assert f.from_string(\"C\") is C\n    assert f.from_string(\"X\") is X\n\n    with pytest.raises(MetaFromString, match=\"Could not produce a union instance from 'object'\"):\n        f.from_string(\"object\")\n\n    assert f.from_string(f\"{__name__}:A\") is A\n    assert f.from_string(f\"{__name__}:B\") is B\n    assert f.from_string(f\"{__name__}:C\") is C\n    assert f.from_string(f\"{__name__}:X\") is X\n\n\ndef test_standard_union_optional():\n    f = standard(annotation=A | None)\n\n    assert f.unspecified_factory() is A\n\n    assert f.from_string(\"A\") is A\n    assert f.from_string(\"None\")() is None\n\n    with pytest.raises(MetaFromString, match=\"Could not produce a union instance from 'object'\"):\n        f.from_string(\"object\")\n\n    f = standard(annotation=int | None)\n\n    assert f.perform_cast(\"123\") == 123\n    assert f.perform_cast(\"None\") is None\n\n\ndef test_standard_union_module():\n    f = standard(annotation=A | X)\n    with pytest.raises(MetaFromString, match=\"Could not produce a union instance from 'a'\"):\n        f.from_string(\"a\")\n    with pytest.raises(MetaFromString, match=\"Could not produce a union instance from 'foo'\"):\n        f.from_string(\"foo\")\n    assert f.from_string(f\"{__name__}:a\")() is a\n    assert f.from_string(f\"{__name__}:foo\") is foo\n\n    f = standard(annotation=A, default_module=__name__)\n    assert f.from_string(\"a\")() is a\n    assert f.from_string(\"foo\") is foo\n    assert f.from_string(f\"{__name__}:a\")() is a\n    assert f.from_string(f\"{__name__}:foo\") is foo\n\n\ndef test_standard_union_type():\n    f = standard(annotation=type[A] | type[X])\n    assert f.unspecified_factory() == None\n\n    f = standard(annotation=type[A | X])\n    assert f.unspecified_factory() == None\n\n    f = standard(annotation=type[A] | type[X], unspecified=type[B])\n    assert f.unspecified_factory() != type[B]\n    assert f.unspecified_factory()() is B\n\n    f = standard(annotation=type[A | X], unspecified=type[B])\n    assert f.unspecified_factory() != type[B]\n    assert f.unspecified_factory()() is B\n\n\ndef test_standard_type_generic():\n    f = standard(annotation=type[list[int]])\n    assert f.unspecified_factory() is not list\n    assert f.unspecified_factory() != list[int]\n    assert f.unspecified_factory()() == list[int]\n\n\ndef test_standard_lambda():\n    f = standard(annotation=int)\n    assert f.from_string(\"lambda: 123\")() == 123\n    assert f.from_string(\"lambda x, y: x + y\")(1, 2) == 3\n\n\ndef test_standard_none():\n    f = standard(annotation=None)\n    assert f.unspecified_factory()() is None\n    assert f.from_string(\"None\")() is None\n\n\ndef test_standard_special_forms():\n    f = standard(annotation=typing.Literal[\"foo\", \"bar\"])\n    assert f.unspecified_factory() is None\n\n    with pytest.raises(\n        MetaFromString, match=r\"Could not produce a Literal\\['foo', 'bar'\\] instance from 'foo'\"\n    ):\n        f.from_string(\"foo\")\n\n\ndef test_standard_subclass_duplicate():\n    class Parent: ...\n\n    ca = type(\"Child\", (Parent,), {})  # noqa: F841\n    cb = type(\"Child\", (Parent,), {})  # noqa: F841\n\n    f = standard(annotation=Parent)\n    with pytest.raises(\n        MetaFromString, match=r\"Multiple subclasses of .*Parent named 'Child': .*Child, .*Child\"\n    ):\n        f.from_string(\"Child\")\n"
  },
  {
    "path": "tests/test_munge.py",
    "content": "from typing import Any, Callable, TypedDict, TypeVar\n\nimport pytest\n\nimport chz\nfrom chz.mungers import attr_if_none, if_none\n\n\ndef test_munger():\n    @chz.chz\n    class A:\n        a: int = chz.field(munger=lambda s, v: s.b)\n        b: int = chz.field(munger=lambda s, v: s.c + 10)\n        c: int = chz.field(munger=lambda s, v: s.X_a + 100)\n\n    x = A(a=1, b=47, c=94)\n    assert x.X_a == 1\n    assert x.a == 111\n    assert x.X_b == 47\n    assert x.b == 111\n    assert x.X_c == 94\n    assert x.c == 101\n\n\ndef test_munger_call_count():\n    count = 0\n\n    def munger(s, v):\n        nonlocal count\n        count += 1\n        return v * 2\n\n    @chz.chz\n    class A:\n        a: int = chz.field(munger=munger)\n\n    a = A(a=18)\n\n    assert a.a == 36\n    assert count == 1\n    assert a.a == 36\n    assert count == 1\n    assert a.a == 36\n    assert count == 1\n\n\ndef test_munger_conflict():\n    with pytest.raises(\n        ValueError, match=\"Cannot define 'a' in class when the associated field has a munger\"\n    ):\n\n        @chz.chz\n        class A:\n            X_a: int = chz.field(munger=lambda s, v: s.X_a)\n\n            @chz.init_property\n            def a(self):\n                return 1\n\n\ndef test_munge_recursive():\n    @chz.chz\n    class A:\n        # Since we pass the value, there is little need to do this\n        # And if there is need, it's still better to do s.X_a\n        # We could make this a different kind of error other than RecursionError, but seems fine\n        a: int = chz.field(munger=lambda s, v: s.a)\n\n    with pytest.raises(RecursionError):\n        A(a=1)\n\n\ndef test_munger_combinators():\n    @chz.chz\n    class A:\n        a: int = chz.field(munger=if_none(lambda self: self.c))\n        b: int = chz.field(munger=attr_if_none(\"a\"))\n        c: int = 42\n\n    a = A(a=None, b=None)\n    assert a.a == 42\n    assert a.b == 42\n\n    a = A(a=1, b=None)\n    assert a.a == 1\n    assert a.b == 1\n\n    a = A(a=None, b=2)\n    assert a.a == 42\n    assert a.b == 2\n\n    a = A(a=1, b=2)\n    assert a.a == 1\n    assert a.b == 2\n\n\ndef test_munger_x_type():\n    @chz.chz(typecheck=True)\n    class A:\n        a: int = chz.field(munger=lambda s, v: int(v + \"0\") + 1, x_type=str)\n\n    a = A(a=\"123\")\n    assert a.X_a == \"123\"\n    assert a.a == 1231\n\n    a = chz.Blueprint(A).apply({\"a\": chz.blueprint.Castable(\"456\")}).make()\n    assert a.X_a == \"456\"\n    assert a.a == 4561\n\n    @chz.chz(typecheck=True)\n    class B:\n        b: int = chz.field(munger=lambda s, v: v, x_type=str)\n\n    with pytest.raises(TypeError, match=\"Expected X_b to be str, got int\"):\n        B(b=0)\n\n    B(b=\"0\")  # TODO: this could raise\n\n    @chz.chz(typecheck=True)\n    class C:\n        X_c: int\n\n        @chz.init_property\n        def c(self) -> str:\n            return str(self.X_c)\n\n    assert C(c=0).c == \"0\"\n\n\ndef test_munger_freeze_dict():\n    class MyDict(TypedDict):\n        a: int\n        b: int\n\n    @chz.chz\n    class A:\n        d: dict[str, int] = chz.field(munger=chz.mungers.freeze_dict())\n        d2: MyDict = chz.field(munger=chz.mungers.freeze_dict())\n\n    x = A(d={\"a\": 1, \"b\": 2}, d2=MyDict(a=1, b=2))\n    hash(x)\n\n\ndef test_converter():\n    @chz.chz\n    class A:\n        a: int = chz.field(converter=if_none(lambda self: self.c))\n        c: int = 42\n\n    a = A(a=None)\n    assert a.a == 42\n    b = A(a=3)\n    assert b.a == 3\n\n\ndef test_converter_and_munger():\n    with pytest.raises(ValueError, match=\"Cannot specify both converter and munger\"):\n\n        @chz.chz\n        class A:\n            a: int = chz.field(\n                converter=if_none(lambda self: self.c), munger=if_none(lambda self: self.c)\n            )\n            c: int = 42\n\n\ndef test_converter_fn():\n    @chz.chz\n    class A:\n        a: int = chz.field(converter=lambda v, **kwargs: v or 10)\n        c: int = 42\n\n    a = A(a=None)\n    assert a.a == 10\n\n\n_T = TypeVar(\"_T\")\n\n\ndef if_none_fn(default: _T) -> Callable[[_T | None], _T]:\n    def convert(value: _T | None, *, chzself: Any = None) -> _T:\n        return value or default\n\n    return convert\n\n\ndef test_converter_fn_typed():\n    @chz.chz\n    class A:\n        a: int = chz.field(converter=if_none_fn(10))\n        c: int = 42\n\n    a = A(a=None)\n    assert a.a == 10\n\n\ndef test_converter_freeze_dict():\n    from frozendict import frozendict\n\n    class MyDict(TypedDict):\n        a: int\n        b: int\n\n    @chz.chz\n    class A:\n        d: frozendict[str, int] = chz.field(converter=chz.mungers.freeze_dict())\n        d2: MyDict = chz.field(converter=chz.mungers.freeze_dict())  # type: ignore\n\n    x = A(d={\"a\": 1, \"b\": 2}, d2=MyDict(a=1, b=2))\n    hash(x)\n"
  },
  {
    "path": "tests/test_tiepin.py",
    "content": "# ruff: noqa: UP006\n# ruff: noqa: UP007\n# ruff: noqa: UP045\nimport collections.abc\nimport enum\nimport fractions\nimport pathlib\nimport re\nimport sys\nimport typing\n\nimport pytest\nimport typing_extensions\n\nfrom chz.tiepin import (\n    CastError,\n    _simplistic_try_cast,\n    _simplistic_type_of_value,\n    approx_type_hash,\n    is_subtype,\n    is_subtype_instance,\n    type_repr,\n)\n\n\ndef test_type_repr():\n    assert type_repr(int) == \"int\"\n    assert type_repr(list[int]) == \"list[int]\"\n\n    class X: ...\n\n    assert type_repr(X) == \"test_tiepin:test_type_repr.<locals>.X\"\n\n    assert type_repr(typing.Literal[\"asdf\"]) == \"Literal['asdf']\"\n    assert type_repr(typing.Union[int, str]) in (\"Union[int, str]\", \"int | str\")\n    assert (\n        type_repr(typing.Callable[[int], X])\n        == \"Callable[[int], test_tiepin.test_type_repr.<locals>.X]\"\n    )\n\n    assert type_repr(typing.Sequence[str]) == \"Sequence[str]\"\n    assert type_repr(collections.abc.Sequence[str]) == \"Sequence[str]\"\n\n\ndef test_is_subtype_instance_basic():\n    assert is_subtype_instance(None, None)\n    assert is_subtype_instance(1, int)\n    assert is_subtype_instance(True, bool)\n\n    assert not is_subtype_instance(\"str\", int)\n    assert not is_subtype_instance(1, str)\n\n    assert not is_subtype_instance(int, int)\n    assert not is_subtype_instance(int, float)\n\n    class A: ...\n\n    class B(A): ...\n\n    assert is_subtype_instance(A(), A)\n    assert is_subtype_instance(B(), B)\n    assert is_subtype_instance(B(), A)\n    assert not is_subtype_instance(A(), B)\n\n    assert not is_subtype_instance(A, A)\n    assert not is_subtype_instance(B, A)\n    assert not is_subtype_instance(A, B)\n    assert not is_subtype_instance(B, B)\n\n\ndef test_is_subtype_instance_user_defined_generic():\n    T = typing.TypeVar(\"T\")\n\n    class X(typing.Generic[T]): ...\n\n    assert is_subtype_instance(X(), X)\n    assert is_subtype_instance(X(), X[int])\n    assert is_subtype_instance(X(), X[str])\n\n    # Note that we do use __orig_class__ where possible\n    assert is_subtype_instance(X[int](), X[int])\n    assert not is_subtype_instance(X[str](), X[int])\n\n\ndef test_is_subtype_instance_user_defined_generic_abc():\n    T = typing.TypeVar(\"T\")\n\n    class X(typing.Generic[T]):\n        def unrelated(self) -> T: ...\n\n        def __iter__(self):\n            return iter([1, 2, 3])\n\n    assert is_subtype_instance(X(), X)\n    assert is_subtype_instance(X(), X[int])\n    assert is_subtype_instance(X(), X[str])\n\n    assert is_subtype_instance(X(), collections.abc.Iterable)\n    assert is_subtype_instance(X(), collections.abc.Iterable[int])\n    assert not is_subtype_instance(X(), collections.abc.Iterable[str])\n\n    class Y(typing.Generic[T]):\n        def __call__(self, x: int) -> str: ...\n\n    assert is_subtype_instance(Y(), Y)\n    assert is_subtype_instance(Y(), Y[int])\n    assert is_subtype_instance(Y(), Y[str])\n    assert is_subtype_instance(Y(), Y[bytes])\n\n    assert is_subtype_instance(Y(), typing.Callable)\n    assert is_subtype_instance(Y(), typing.Callable[[int], str])\n    assert not is_subtype_instance(Y(), typing.Callable[[int], int])\n    assert not is_subtype_instance(Y(), typing.Callable[[str], int])\n    assert not is_subtype_instance(Y(), typing.Callable[[str], str])\n\n\ndef test_is_subtype_instance_duck_type():\n    assert is_subtype_instance(1, int)\n    assert is_subtype_instance(1, float)\n    assert is_subtype_instance(1, complex)\n\n    assert not is_subtype_instance(1.0, int)\n    assert is_subtype_instance(1.0, float)\n    assert is_subtype_instance(1.0, complex)\n\n    assert not is_subtype_instance(1 + 1j, int)\n    assert not is_subtype_instance(1 + 1j, float)\n    assert is_subtype_instance(1 + 1j, complex)\n\n    assert is_subtype_instance(bytearray(), bytes)\n    assert is_subtype_instance(memoryview(b\"\"), bytes)\n    assert not is_subtype_instance(bytes(), bytearray)\n\n\ndef test_is_subtype_instance_any():\n    assert is_subtype_instance(1, typing.Any)\n    assert is_subtype_instance(\"str\", typing.Any)\n    assert is_subtype_instance([], typing.Any)\n    assert is_subtype_instance(int, typing.Any)\n\n    if sys.version_info >= (3, 11):\n\n        class Mock(typing.Any): ...\n\n        assert is_subtype_instance(Mock(), typing.Any)\n        assert is_subtype_instance(Mock(), int)\n        assert is_subtype_instance(Mock(), str)\n        assert not is_subtype_instance(1, Mock)\n\n\ndef test_is_subtype_instance_list_abc():\n    T = typing.TypeVar(\"T\")\n\n    seq: typing.Any\n    for seq in (\n        list,\n        typing.List,\n        collections.abc.Sequence,\n        collections.abc.Iterable,\n        typing.Iterable,\n        collections.abc.Collection,\n        list[T],\n        typing.List[T],\n        collections.abc.Sequence[T],\n        collections.abc.Iterable[T],\n        typing.Iterable[T],\n        collections.abc.Collection[T],\n    ):\n        assert is_subtype_instance([], seq)\n        assert is_subtype_instance([], seq[int])\n        assert is_subtype_instance([], seq[object])\n        assert is_subtype_instance([], seq[typing.Any])\n\n        assert is_subtype_instance([1, 2], seq)\n        assert is_subtype_instance([1, 2], seq[int])\n        assert is_subtype_instance([1, 2], seq[object])\n        assert is_subtype_instance([1, 2], seq[typing.Any])\n        assert not is_subtype_instance([1, 2], seq[str])\n\n        assert is_subtype_instance([1, 2, \"3\"], seq)\n        assert is_subtype_instance([1, 2, \"3\"], seq[object])\n        assert is_subtype_instance([1, 2, \"3\"], seq[typing.Any])\n        assert not is_subtype_instance([1, 2, \"3\"], seq[int])\n\n\ndef test_is_subtype_instance_iterable():\n    assert is_subtype_instance([], typing.Iterable)\n    assert is_subtype_instance([1, 2, 3], typing.Iterable[int])\n    assert not is_subtype_instance([1, 2, \"3\"], typing.Iterable[int])\n\n    assert is_subtype_instance((), typing.Iterable)\n    assert is_subtype_instance((1, 2, 3), typing.Iterable[int])\n    assert not is_subtype_instance((1, 2, \"3\"), typing.Iterable[int])\n\n    assert is_subtype_instance({}, typing.Iterable)\n    assert is_subtype_instance({1: 2, 3: 4}, typing.Iterable[int])\n    assert not is_subtype_instance({1: 2, \"3\": 4}, typing.Iterable[int])\n\n    assert is_subtype_instance(set(), typing.Iterable)\n    assert is_subtype_instance({1, 2, 3}, typing.Iterable[int])\n    assert not is_subtype_instance({1, 2, \"3\"}, typing.Iterable[int])\n\n    assert is_subtype_instance(frozenset(), typing.Iterable)\n    assert is_subtype_instance(frozenset({1, 2, 3}), typing.Iterable[int])\n    assert not is_subtype_instance(frozenset({1, 2, \"3\"}), typing.Iterable[int])\n\n    assert is_subtype_instance(\"\", typing.Iterable)\n    assert is_subtype_instance(\"123\", typing.Iterable[str])\n    assert not is_subtype_instance(\"123\", typing.Iterable[int])\n\n    assert is_subtype_instance(b\"\", typing.Iterable)\n    assert is_subtype_instance(b\"123\", typing.Iterable[int])\n    assert not is_subtype_instance(b\"123\", typing.Iterable[str])\n\n    assert is_subtype_instance(bytearray(), typing.Iterable)\n    assert is_subtype_instance(bytearray(b\"123\"), typing.Iterable[int])\n    assert not is_subtype_instance(bytearray(b\"123\"), typing.Iterable[str])\n\n    assert is_subtype_instance(memoryview(b\"\"), typing.Iterable)\n    assert is_subtype_instance(memoryview(b\"123\"), typing.Iterable[int])\n    assert not is_subtype_instance(memoryview(b\"123\"), typing.Iterable[str])\n\n    assert is_subtype_instance(range(0), typing.Iterable)\n    assert is_subtype_instance(range(3), typing.Iterable[int])\n    assert not is_subtype_instance(range(3), typing.Iterable[str])\n\n    assert is_subtype_instance(map(int, [1, 2, 3]), typing.Iterable)\n    assert is_subtype_instance(map(int, [1, 2, 3]), typing.Iterable[int])\n    assert not is_subtype_instance(map(str, [1, 2, 3]), typing.Iterable[int])\n\n\ndef test_is_subtype_instance_tuple():\n    assert is_subtype_instance((1, 2), tuple)\n    assert is_subtype_instance((1, 2), tuple[int, int])\n    assert is_subtype_instance((1, 2), tuple[int, ...])\n    assert is_subtype_instance((1, 2), typing.Tuple)\n    assert is_subtype_instance((1, 2), typing.Tuple[int, int])\n    assert is_subtype_instance((1, 2), typing.Tuple[int, ...])\n\n    assert not is_subtype_instance((1, 2), tuple[int, int, int])\n    assert not is_subtype_instance((1, 2), tuple[int, str])\n    assert not is_subtype_instance((1, 2), typing.Tuple[int, int, int])\n    assert not is_subtype_instance((1, 2), typing.Tuple[int, str])\n\n    assert is_subtype_instance((1, \"str\", \"bytes\"), tuple)\n    assert is_subtype_instance((1, \"str\", \"bytes\"), tuple[int, str, str])\n    assert is_subtype_instance((1, \"str\", \"bytes\"), typing.Tuple)\n    assert is_subtype_instance((1, \"str\", \"bytes\"), typing.Tuple[int, str, str])\n\n    assert not is_subtype_instance((1, \"str\", \"bytes\"), tuple[int, ...])\n\n\ndef test_is_subtype_instance_mapping():\n    K = typing.TypeVar(\"K\")\n    V = typing.TypeVar(\"V\")\n\n    map: typing.Any\n    for map in (\n        dict,\n        typing.Dict,\n        collections.abc.MutableMapping,\n        collections.abc.Mapping,\n        dict[K, V],\n        typing.Dict[K, V],\n        collections.abc.MutableMapping[K, V],\n        collections.abc.Mapping[K, V],\n    ):\n        assert is_subtype_instance({}, map)\n        assert is_subtype_instance({}, map[str, int])\n\n        assert is_subtype_instance({\"a\": 1}, map)\n        assert is_subtype_instance({\"a\": 1}, map[str, int])\n        assert is_subtype_instance({\"a\": 1}, map[str, typing.Any])\n        assert is_subtype_instance({\"a\": 1}, map[typing.Any, typing.Any])\n\n        assert not is_subtype_instance({\"a\": 1}, map[int, int])\n        assert not is_subtype_instance({\"a\": 1}, map[int, typing.Any])\n\n\ndef test_is_subtype_instance_typed_dict():\n    for t_TypedDict in (typing.TypedDict, typing_extensions.TypedDict):\n\n        class A(t_TypedDict):\n            a: int\n            b: str\n\n        class B(A):\n            c: bytes\n\n        assert not is_subtype_instance({}, A)\n        assert not is_subtype_instance({\"a\": 1}, A)\n\n        assert is_subtype_instance({\"a\": 1, \"b\": \"str\"}, A)\n        assert not is_subtype_instance({\"a\": 1, \"b\": \"str\"}, B)\n\n        assert is_subtype_instance({\"a\": 1, \"b\": \"str\", \"c\": b\"bytes\"}, A)\n        assert is_subtype_instance({\"a\": 1, \"b\": \"str\", \"c\": b\"bytes\"}, B)\n\n        assert not is_subtype_instance({\"a\": 1, \"b\": 1}, A)\n\n        assert not is_subtype_instance({\"c\": b\"bytes\"}, A)\n        assert not is_subtype_instance({\"c\": b\"bytes\"}, B)\n\n\ndef test_is_subtype_instance_typed_dict_required():\n    for t_TypedDict in (typing.TypedDict, typing_extensions.TypedDict):\n\n        class Foo(t_TypedDict):\n            a: int\n            b: typing.Required[int]\n            c: typing.NotRequired[int]\n\n        class Bar(Foo, total=False):\n            d: int\n            e: typing.Required[int]\n            f: typing.NotRequired[int]\n\n        class Baz(Bar):\n            g: int\n            h: typing.Required[int]\n            i: typing.NotRequired[int]\n\n        class Qux(t_TypedDict, total=False):\n            j: int\n\n        assert not is_subtype_instance({}, Foo)\n        assert is_subtype_instance({\"a\": 1, \"b\": 1}, Foo)\n        assert not is_subtype_instance({\"a\": 1, \"b\": \"str\"}, Foo)\n        assert is_subtype_instance({\"a\": 1, \"b\": 1, \"c\": 1}, Foo)\n        assert not is_subtype_instance({\"a\": 1, \"b\": 1, \"c\": \"str\"}, Foo)\n        assert is_subtype_instance({\"a\": 1, \"b\": 1, \"c\": 1, \"xyz\": object()}, Foo)\n\n        assert not is_subtype_instance({}, Bar)\n        assert not is_subtype_instance({\"a\": 1, \"b\": 1}, Bar)\n        assert is_subtype_instance({\"a\": 1, \"b\": 1, \"e\": 1}, Bar)\n        assert is_subtype_instance({\"a\": 1, \"b\": 1, \"c\": 1, \"d\": 1, \"e\": 1, \"f\": 1}, Bar)\n        assert not is_subtype_instance({\"a\": 1, \"b\": 1, \"c\": 1, \"d\": 1, \"e\": 1, \"f\": \"str\"}, Bar)\n\n        assert not is_subtype_instance({\"a\": 1, \"b\": 1, \"e\": 1}, Baz)\n        assert is_subtype_instance({\"a\": 1, \"b\": 1, \"e\": 1, \"g\": 1, \"h\": 1}, Baz)\n\n        assert is_subtype_instance({}, Qux)\n        assert is_subtype_instance({\"j\": 1}, Qux)\n        assert not is_subtype_instance({\"j\": \"str\"}, Qux)\n\n\ndef test_is_subtype_instance_named_tuple():\n    class A(typing.NamedTuple):\n        a: int\n        b: str\n\n    class B(A): ...\n\n    assert is_subtype_instance(A(a=1, b=\"str\"), A)\n    assert is_subtype_instance(A(a=1, b=\"str\"), tuple[int, str])\n    assert not is_subtype_instance(A(a=1, b=\"str\"), tuple[int, int])\n    assert not is_subtype_instance(A(a=1, b=\"str\"), B)\n\n\ndef test_is_subtype_instance_type_var():\n    T = typing.TypeVar(\"T\")\n    assert is_subtype_instance(1, T)\n    assert is_subtype_instance(\"str\", T)\n\n    assert is_subtype_instance([1, 2], list[T])\n    assert is_subtype_instance((1,), tuple[T])\n\n    assert is_subtype_instance(\"a\", typing.AnyStr)\n    assert not is_subtype_instance(1, typing.AnyStr)\n\n    assert is_subtype_instance([\"a\"], list[typing.AnyStr])\n    assert is_subtype_instance([\"a\"], typing.List[typing.AnyStr])\n    assert not is_subtype_instance([1], list[typing.AnyStr])\n\n    assert is_subtype_instance([\"this is\", b\"not quite right\"], list[typing.AnyStr])\n\n    C = typing.TypeVar(\"C\", list[int], list[str])\n    assert is_subtype_instance([], C)\n    assert not is_subtype_instance([b\"bytes\"], C)\n\n    B = typing.TypeVar(\"B\", bound=int)\n\n    assert is_subtype_instance(1, B)\n    assert is_subtype_instance(False, B)\n    assert not is_subtype_instance(\"str\", B)\n\n\ndef test_is_subtype_instance_union():\n    assert is_subtype_instance(1, typing.Union[int, str])\n    assert is_subtype_instance(\"str\", typing.Union[int, str])\n    assert not is_subtype_instance(b\"bytes\", typing.Union[int, str])\n\n    assert is_subtype_instance([1], typing.List[typing.Union[int, str]])\n    assert is_subtype_instance([\"str\", 1], typing.List[typing.Union[int, str]])\n    assert is_subtype_instance([1], list[typing.Union[int, str]])\n    assert is_subtype_instance([\"str\", 1], list[typing.Union[int, str]])\n\n    if sys.version_info >= (3, 10):\n        assert is_subtype_instance(1, int | str)\n        assert is_subtype_instance(\"str\", int | str)\n        assert not is_subtype_instance(b\"bytes\", int | str)\n\n        assert is_subtype_instance([1], typing.List[int | str])\n        assert is_subtype_instance([\"str\", 1], typing.List[int | str])\n        assert is_subtype_instance([1], list[int | str])\n        assert is_subtype_instance([\"str\", 1], list[int | str])\n\n\ndef test_is_subtype_instance_callable() -> None:\n    # lambda / untyped subtyping\n    assert is_subtype_instance(lambda: None, typing.Callable)\n    assert is_subtype_instance(lambda x: x, typing.Callable)\n\n    assert is_subtype_instance(lambda: None, typing.Callable[[], None])\n    assert is_subtype_instance(lambda x: x, typing.Callable[[int], int])\n\n    assert is_subtype_instance(lambda: None, typing.Callable[..., None])\n    assert is_subtype_instance(lambda x: x, typing.Callable[..., int])\n\n    assert not is_subtype_instance(lambda: None, typing.Callable[[int], int])\n    assert not is_subtype_instance(lambda x: x, typing.Callable[[], None])\n\n    assert not is_subtype_instance(1, typing.Callable[[int], str])\n\n    # typed functions subtyping\n    def foo(x: int, y: str, z: bytes = ...) -> None: ...\n\n    assert is_subtype_instance(foo, typing.Callable[[int, str], None])\n    assert is_subtype_instance(foo, typing.Callable[[int, str, bytes], None])\n    assert not is_subtype_instance(foo, typing.Callable[[str, int, bytes], None])\n    assert not is_subtype_instance(foo, typing.Callable[[int, str, bytes], str])\n\n    def bar(x: object) -> bool: ...\n\n    assert is_subtype_instance(bar, typing.Callable[[int], int])\n    assert is_subtype_instance(bar, typing.Callable[[str], bool])\n    assert is_subtype_instance(bar, typing.Callable[..., bool])\n    assert not is_subtype_instance(bar, typing.Callable[..., str])\n\n    def baz(x, y, z): ...\n\n    assert is_subtype_instance(baz, typing.Callable[[int, str, bytes], int])\n    assert is_subtype_instance(baz, typing.Callable[[str, bytes, int], bytes])\n\n    # type subtyping\n    class A:\n        def __init__(self, x: int) -> None: ...\n\n    class B(A): ...\n\n    assert is_subtype_instance(A, typing.Callable[[int], A])\n    assert is_subtype_instance(B, typing.Callable[[int], A])\n    assert not is_subtype_instance(A, typing.Callable[[int], B])\n\n    # callable instance subtyping\n    class Call:\n        def __call__(self, x: int) -> None: ...\n\n    assert is_subtype_instance(Call(), typing.Callable[[int], None])\n    assert is_subtype_instance(Call(), typing.Callable[..., None])\n    assert not is_subtype_instance(Call(), typing.Callable[[str], None])\n    assert not is_subtype_instance(Call(), typing.Callable[[int, int], None])\n\n    # function with arguments of generic type\n    def takes_dict(x: dict[int, str]) -> int: ...\n\n    assert is_subtype_instance(takes_dict, typing.Callable[[dict[int, str]], int])\n    assert is_subtype_instance(takes_dict, typing.Callable[[dict[int, typing.Any]], int])\n    assert is_subtype_instance(takes_dict, typing.Callable[[dict[typing.Any, str]], int])\n    assert is_subtype_instance(takes_dict, typing.Callable[[dict[typing.Any, typing.Any]], int])\n    assert not is_subtype_instance(takes_dict, typing.Callable[[dict[object, object]], int])\n    assert not is_subtype_instance(takes_dict, typing.Callable[[dict[int, str]], None])\n    assert not is_subtype_instance(takes_dict, typing.Callable[[dict[str, int]], int])\n\n    # more contravariance tests\n    class C(B): ...\n\n    def takes_b(x: B) -> None: ...\n\n    assert is_subtype_instance(takes_b, typing.Callable[[C], None])\n    assert is_subtype_instance(takes_b, typing.Callable[[B], None])\n    assert not is_subtype_instance(takes_b, typing.Callable[[A], None])\n\n    # varargs\n    def varargs(*args: int) -> None: ...\n\n    assert is_subtype_instance(varargs, typing.Callable[[int], None])\n    assert is_subtype_instance(varargs, typing.Callable[[int, int], None])\n    assert not is_subtype_instance(varargs, typing.Callable[[str], None])\n\n    # varkwargs\n    def varkwargs(**kwargs: int) -> None: ...\n\n    assert is_subtype_instance(varkwargs, typing.Callable[[], None])\n    assert not is_subtype_instance(varkwargs, typing.Callable[[int], None])\n    assert not is_subtype_instance(varkwargs, typing.Callable[[int, int], None])\n\n    # param spec\n    P = typing.ParamSpec(\"P\")\n    assert not is_subtype_instance(lambda: None, typing.Callable[[P], None])\n    assert not is_subtype_instance(lambda: foo, typing.Callable[[P], None])\n    assert not is_subtype_instance(lambda: bar, typing.Callable[[P], None])\n    assert not is_subtype_instance(lambda: A, typing.Callable[[P], None])\n\n\ndef test_is_subtype_instance_callable_protocol():\n    class A: ...\n\n    class B(A): ...\n\n    class C(B): ...\n\n    class P1(typing.Protocol):\n        def __call__(self, x: B) -> None: ...\n\n    def p1(x: B) -> None: ...\n    def p2(x: A) -> None: ...\n    def p3(x: B = ...) -> None: ...\n    def p4(x: B, y: int = ...) -> None: ...\n\n    assert is_subtype_instance(p1, P1)\n    assert is_subtype_instance(p2, P1)\n    assert is_subtype_instance(p3, P1)\n    assert is_subtype_instance(p4, P1)\n\n    def p5(x: B, /) -> None: ...\n    def p6(y: B, /) -> None: ...\n    def p7(x: C) -> None: ...\n    def p8(y: B) -> None: ...\n    def p9(x: B, y: int) -> None: ...\n\n    assert not is_subtype_instance(p5, P1)\n    assert not is_subtype_instance(p6, P1)\n    assert not is_subtype_instance(p7, P1)\n    assert not is_subtype_instance(p8, P1)\n    assert not is_subtype_instance(p9, P1)\n\n    class P2(typing.Protocol):\n        def __call__(self, x: B, /) -> None: ...\n\n    assert is_subtype_instance(p1, P2)\n    assert is_subtype_instance(p2, P2)\n    assert is_subtype_instance(p3, P2)\n    assert is_subtype_instance(p4, P2)\n    assert is_subtype_instance(p5, P2)\n    assert is_subtype_instance(p6, P2)\n\n    class P3(typing.Protocol):\n        def __call__(self, x: B, y: int = ...) -> None: ...\n\n    assert not is_subtype_instance(p1, P3)\n    assert not is_subtype_instance(p9, P3)\n\n    def p10(*args: int | A) -> None: ...\n    def p11(*args: int | C) -> None: ...\n\n    assert is_subtype_instance(p10, P3)\n    assert not is_subtype_instance(p11, P3)\n\n    class P4(typing.Protocol):\n        def __call__(self, x: B, *, y: int) -> None: ...\n\n    assert not is_subtype_instance(p1, P4)\n    assert is_subtype_instance(p4, P4)\n\n    def p12(x: B, *, y: int = ...) -> None: ...\n    def p13(x: B, *, y: int) -> None: ...\n    def p14(x: B, *, y: B = ...) -> None: ...\n    def p15(**kwargs: int | B) -> None: ...\n    def p16(x: B, **kwargs: int) -> None: ...\n    def p17(x: B, *, y: int, z: int = ...) -> None: ...\n    def p18(x: B, y: B) -> None: ...\n\n    assert is_subtype_instance(p4, P4)\n    assert is_subtype_instance(p9, P4)\n    assert is_subtype_instance(p12, P4)\n    assert is_subtype_instance(p13, P4)\n    assert not is_subtype_instance(p14, P4)\n    assert not is_subtype_instance(p15, P4)\n    assert not is_subtype_instance(p16, P4)\n    assert is_subtype_instance(p17, P4)\n    assert not is_subtype_instance(p18, P4)\n\n    class P5(typing.Protocol):\n        def __call__(self, x: B, *, y: int = ...) -> None: ...\n\n    assert is_subtype_instance(p4, P5)\n    assert not is_subtype_instance(p9, P5)\n    assert is_subtype_instance(p12, P5)\n    assert not is_subtype_instance(p13, P5)\n    assert not is_subtype_instance(p14, P5)\n    assert not is_subtype_instance(p15, P5)\n    assert not is_subtype_instance(p16, P5)\n    assert not is_subtype_instance(p17, P5)\n    assert not is_subtype_instance(p18, P5)\n\n    class P7(typing.Protocol):\n        def __call__(self, x: B, **kwargs: B) -> None: ...\n\n    assert not is_subtype_instance(p1, P7)\n    assert not is_subtype_instance(p4, P7)\n    assert not is_subtype_instance(p12, P7)\n\n    def p19(x: B, **kwargs: A) -> None: ...\n    def p20(x: B, **kwargs: C) -> None: ...\n    def p21(x: B, y: B, **kwargs: B) -> None: ...\n    def p22(x: B, *, y: B, **kwargs: B) -> None: ...\n    def p23(x: B, *, y: int, **kwargs: B) -> None: ...\n\n    assert is_subtype_instance(p19, P7)\n    assert not is_subtype_instance(p20, P7)\n    assert not is_subtype_instance(p21, P7)\n    assert is_subtype_instance(p22, P7)\n    assert not is_subtype_instance(p23, P7)\n\n    class P8(typing.Protocol):\n        def __call__(self) -> B: ...\n\n    def p22() -> A: ...\n    def p23() -> C: ...\n\n    assert not is_subtype_instance(p22, P8)\n    assert is_subtype_instance(p23, P8)\n\n\ndef test_is_subtype_instance_protocol_chz_callable():\n    class P(typing.Protocol):\n        def __call__(self, a: int) -> int: ...\n\n    import chz\n\n    @chz.chz\n    class Bad:\n        def __call__(self) -> int: ...\n\n    @chz.chz\n    class Good:\n        def __call__(self, a: int) -> int: ...\n\n    assert not is_subtype_instance(Bad(), P)\n    assert not is_subtype_instance(Bad, P)\n    assert is_subtype_instance(Good(), P)\n    assert not is_subtype_instance(Good, P)\n\n\ndef test_is_subtype_instance_protocol_attr():\n    class A: ...\n\n    class B(A): ...\n\n    class C(B): ...\n\n    class FooProto(typing.Protocol):\n        x: B\n\n        def foo(self) -> int: ...\n\n    class Foo:\n        def __init__(self, x) -> None:\n            self.x = x\n\n        def foo(self) -> int:\n            return 1\n\n    assert not is_subtype_instance(Foo(A()), FooProto)\n    assert is_subtype_instance(Foo(B()), FooProto)\n    assert is_subtype_instance(Foo(C()), FooProto)\n\n    b = Foo(B())\n    b.foo = 1\n    assert not is_subtype_instance(b, FooProto)\n\n    assert not is_subtype_instance(object(), FooProto)\n\n\ndef test_is_subtype_instance_runtime_protocol():\n    @typing.runtime_checkable\n    class FooProto(typing.Protocol):\n        def foo(self) -> int: ...\n\n    class Foo:\n        def foo(self) -> int:\n            return 1\n\n    assert is_subtype_instance(Foo(), FooProto)\n    assert not is_subtype_instance(object(), FooProto)\n\n\ndef test_is_subtype_instance_literal():\n    assert is_subtype_instance(1, typing.Literal[1])\n    assert is_subtype_instance(\"str\", typing.Literal[\"str\"])\n\n    assert is_subtype_instance(1, typing.Literal[1, 2])\n    assert is_subtype_instance(\"str\", typing.Literal[1, \"str\"])\n\n    assert not is_subtype_instance(1, typing.Literal[2])\n    assert not is_subtype_instance(\"str\", typing.Literal[1, \"bytes\"])\n\n    assert is_subtype_instance(None, typing.Literal[None])\n    assert is_subtype_instance(None, typing.Literal[1, \"bytes\", None])\n\n\ndef test_is_subtype_instance_type():\n    assert is_subtype_instance(int, type)\n    assert is_subtype_instance(str, type)\n    assert is_subtype_instance(type, type)\n    assert is_subtype_instance(int, typing.Type)\n    assert is_subtype_instance(str, typing.Type)\n    assert is_subtype_instance(type, typing.Type)\n\n    assert is_subtype_instance(int, type[int])\n    assert is_subtype_instance(str, type[str])\n    assert is_subtype_instance(type, type[type])\n    assert is_subtype_instance(int, typing.Type[int])\n    assert is_subtype_instance(str, typing.Type[str])\n    assert is_subtype_instance(type, typing.Type[type])\n\n    assert not is_subtype_instance(int, type[str])\n    assert not is_subtype_instance(str, type[int])\n    assert not is_subtype_instance(type, type[int])\n    assert not is_subtype_instance(int, typing.Type[str])\n    assert not is_subtype_instance(str, typing.Type[int])\n    assert not is_subtype_instance(type, typing.Type[int])\n\n\ndef test_is_subtype_instance_enum():\n    class Color(enum.Enum):\n        RED = 1\n        GREEN = 2\n\n    assert is_subtype_instance(Color.RED, enum.Enum)\n    assert is_subtype_instance(Color.RED, Color)\n\n    assert not is_subtype_instance(1, Color)\n    assert not is_subtype_instance(\"RED\", Color)\n\n    assert is_subtype_instance(Color.RED, typing.Literal[Color.RED])\n    assert is_subtype_instance(Color.RED, typing.Literal[Color.RED, Color.GREEN])\n    assert not is_subtype_instance(Color.RED, typing.Literal[Color.GREEN])\n\n\ndef test_is_subtype_instance_new_type():\n    N = typing.NewType(\"N\", int)\n\n    assert is_subtype_instance(1, N)\n    assert not is_subtype_instance(\"1\", N)\n\n\ndef test_is_subtype_instance_literal_string():\n    assert is_subtype_instance(\"str\", typing.LiteralString)\n    assert not is_subtype_instance(1, typing.LiteralString)\n\n\ndef test_is_subtype_instance_explicit_protocol_lsp_violation():\n    class P(typing.Protocol):\n        def makes_int(self) -> int: ...\n\n    class Implicit:\n        def makes_int(self) -> str: ...\n\n    class Explicit(P):\n        def makes_int(self) -> str: ...\n\n    assert not is_subtype_instance(Implicit(), P)\n    assert is_subtype_instance(Explicit(), P)\n\n\ndef test_is_subtype_instance_pydantic() -> None:\n    import pydantic\n\n    T = typing.TypeVar(\"T\")\n\n    class Thing(pydantic.BaseModel, typing.Generic[T]):\n        type: str = \"thing\"\n        x: T\n\n    assert is_subtype_instance(Thing[int](x=5), Thing)\n\n\ndef test_is_subtype_instance_pydantic_utils() -> None:\n    import pydantic\n    import pydantic_core\n\n    try:\n        from pydantic_utils import get_polymorphic_generic_model_schema\n    except ImportError:\n        pytest.skip(\"pydantic_utils not installed\")\n\n    T = typing.TypeVar(\"T\")\n\n    class Foo(pydantic.BaseModel, typing.Generic[T]):\n        type: str = \"foo\"\n        x: T\n\n        @classmethod\n        def __get_pydantic_core_schema__(\n            cls,\n            source: typing.Type[pydantic.BaseModel],  # noqa: UP006\n            handler: pydantic.GetCoreSchemaHandler,\n        ) -> pydantic_core.core_schema.CoreSchema:\n            return get_polymorphic_generic_model_schema(\n                cls,\n                __class__,\n                source,\n                handler,  # type:ignore[name-defined]\n            )\n\n    class Bar(Foo[T], typing.Generic[T]):\n        type: str = \"bar\"\n        y: T\n\n    assert is_subtype_instance(Foo[int](x=5), Foo)\n    assert Foo[typing.Any].model_validate(Foo[int](x=5))\n    assert is_subtype_instance(Foo[int](x=5), Foo[typing.Any])\n    assert is_subtype_instance(Bar[int](x=5, y=2), Foo[int])\n    # This is currently broken in pydantic_utils.\n    # assert not is_subtype_instance(Bar[str](x=\"a\", y=\"b\"), Foo[int])\n    assert is_subtype_instance(Bar(x=5, y=2), Foo[int])\n\n\ndef test_is_subtype():\n    assert is_subtype(int, int)\n    assert not is_subtype(int, str)\n\n    assert is_subtype(list, list)\n    assert is_subtype(list[int], list)\n    assert is_subtype(list, list[int])\n    assert is_subtype(list[int], list[typing.Any])\n    assert is_subtype(list[typing.Any], list[int])\n    assert not is_subtype(list[int], list[str])\n\n    class A: ...\n\n    class B(A): ...\n\n    assert is_subtype(tuple, tuple)\n    assert is_subtype(tuple[int], tuple[int, ...])\n    assert is_subtype(tuple[int, int], tuple[int, ...])\n    assert not is_subtype(tuple[int, ...], tuple[int, int])\n    assert not is_subtype(tuple[int, str], tuple[int, ...])\n\n    assert is_subtype(tuple[B, B], tuple[A, B])\n    assert not is_subtype(tuple[B, A], tuple[B, B])\n    assert not is_subtype(tuple[B, B], tuple[A, B, object])\n\n    assert is_subtype(dict, dict)\n    assert is_subtype(dict[typing.Any, B], dict[str, A])\n    assert not is_subtype(dict[A, A], dict[A, B])\n\n    assert is_subtype(int, int | str)\n    assert is_subtype(str, int | str)\n    assert not is_subtype(bytes, int | str)\n\n    assert is_subtype(int | str, str | int)\n    assert is_subtype(int | str, int | bytes | str)\n    assert not is_subtype(int | str | bytes, int | str)\n\n    assert is_subtype(int, typing.Union[int, str])\n    assert is_subtype(str, typing.Union[int, str])\n    assert not is_subtype(bytes, typing.Union[int, str])\n\n    assert is_subtype(typing.Union[int, str], typing.Union[str, int])\n    assert is_subtype(typing.Union[int, str], typing.Union[int, bytes, str])\n    assert not is_subtype(typing.Union[int, str, bytes], typing.Union[int, str])\n\n    assert is_subtype(None, None)\n    assert is_subtype(None, int | None)\n    assert is_subtype(None, typing.Optional[int])\n\n    assert is_subtype(typing.Literal[1, 2], typing.Literal[3, 2, 1])\n    assert not is_subtype(typing.Literal[1, 2, 4], typing.Literal[3, 2, 1])\n    assert is_subtype(typing.Literal[1, 2], str | int)\n    assert is_subtype(typing.Literal[1, 2], str | typing.Literal[1, 2, 3])\n\n    assert is_subtype(typing.Callable[[int], str], typing.Callable[[int], str])\n    assert not is_subtype(typing.Callable[[int], str], typing.Callable[[int, int], str])\n    assert is_subtype(typing.Callable[[int], str], typing.Callable[[int], str | None])\n    assert not is_subtype(typing.Callable[[int], str | None], typing.Callable[[int], str])\n    assert is_subtype(typing.Callable[[int | None], None], typing.Callable[[int], None])\n    assert not is_subtype(typing.Callable[[int], None], typing.Callable[[int | None], None])\n    assert not is_subtype(typing.Callable[[int], None], typing.Callable[[str], None])\n\n\ndef test_is_subtype_protocol():\n    class P1(typing.Protocol):\n        def foo(self) -> int: ...\n\n    class P2(typing.Protocol):\n        def foo(self) -> int: ...\n        def bar(self) -> int: ...\n\n    class Good:\n        def foo(self) -> int: ...\n\n    class Bad:\n        def bar(self) -> int: ...\n\n    assert not is_subtype(P1, P2)\n    assert is_subtype(P2, P1)\n\n    assert is_subtype(Good, P1)\n    assert not is_subtype(Bad, P1)\n    assert not is_subtype(Good, P2)\n\n    def a() -> P1: ...\n\n    assert is_subtype_instance(a, typing.Callable[..., P1])\n    assert not is_subtype_instance(a, typing.Callable[..., P2])\n\n\ndef test_is_subtype_typed_dict():\n    class A(typing.TypedDict):\n        a: int\n        b: str\n\n    assert is_subtype(A, typing.Mapping[str, typing.Any])\n    assert not is_subtype(typing.Mapping[str, typing.Any], A)\n    assert is_subtype(A, dict[str, int | str])\n    assert is_subtype(A, dict[str, int])\n\n    class B(A):\n        c: bytes\n\n    assert is_subtype(B, A)\n    assert not is_subtype(A, B)\n\n    class B_alt(typing.TypedDict):\n        a: int\n        b: str\n        c: bytes\n\n    assert is_subtype(B_alt, B)\n    assert is_subtype(B, B_alt)\n\n    assert is_subtype(B_alt, A)\n    assert not is_subtype(A, B_alt)\n\n    class A_not_total(typing.TypedDict, total=False):\n        a: int\n        b: str\n\n    assert is_subtype(A, A_not_total)\n    assert not is_subtype(A_not_total, A)\n\n\ndef test_is_subtype_typevar() -> None:\n    T_int = typing.TypeVar(\"T_int\", bound=int)\n    assert is_subtype(T_int, int)\n    assert is_subtype(T_int, object)\n    assert not is_subtype(T_int, str)\n\n    assert is_subtype(int, T_int)\n    assert not is_subtype(str, T_int)\n\n    T = typing.TypeVar(\"T\")\n    assert is_subtype(T, object)\n    assert not is_subtype(T, str)\n\n    assert is_subtype(T, T)\n    assert is_subtype(object, T)\n    assert is_subtype(str, T)\n\n    T_constrained = typing.TypeVar(\"T_constrained\", int, str)\n    assert is_subtype(T_constrained, int | str)\n    assert is_subtype(T_constrained, object)\n    assert not is_subtype(T_constrained, bytes)\n\n    assert is_subtype(T_constrained, T_constrained)\n    assert is_subtype(int, T_constrained)\n    assert is_subtype(str, T_constrained)\n    assert not is_subtype(object, T_constrained)\n\n\ndef test_no_return():\n    assert is_subtype_instance(typing.NoReturn, int)\n    assert is_subtype_instance(typing.NoReturn, str)\n\n    def foo() -> typing.NoReturn: ...\n\n    assert is_subtype_instance(foo, typing.Callable[[], None])\n    assert is_subtype_instance(foo, typing.Callable[[], str])\n\n    if sys.version_info >= (3, 11):\n        assert is_subtype_instance(typing.Never, int)\n        assert is_subtype_instance(typing.Never, str)\n\n        def foo() -> typing.Never: ...\n\n        assert is_subtype_instance(foo, typing.Callable[[], None])\n        assert is_subtype_instance(foo, typing.Callable[[], str])\n\n\ndef test_try_cast_object_any():\n    for obj_any in (object, typing.Any, typing_extensions.Any):\n        assert _simplistic_try_cast(\"1\", obj_any) == 1\n        assert _simplistic_try_cast(\"1a\", obj_any) == \"1a\"\n        assert _simplistic_try_cast(\"1j\", obj_any) == 1j\n        assert _simplistic_try_cast(\"{1: ('2', [3])}\", obj_any) == {1: (\"2\", [3])}\n        assert _simplistic_try_cast(\"null\", obj_any) is None\n        assert _simplistic_try_cast(\"none\", obj_any) is None\n        assert _simplistic_try_cast(\"true\", obj_any) is True\n        assert _simplistic_try_cast(\"false\", obj_any) is False\n\n\ndef test_try_cast_tuple():\n    assert _simplistic_try_cast(\"1\", tuple) == (\"1\",)\n    assert _simplistic_try_cast(\"1,2\", tuple) == (\"1\", \"2\")\n    assert _simplistic_try_cast(\"1,2\", tuple[str, str]) == (\"1\", \"2\")\n    assert _simplistic_try_cast(\"1,2\", tuple[typing.Any, ...]) == (1, 2)\n    assert _simplistic_try_cast(\"1a,2\", tuple[typing.Any, ...]) == (\"1a\", 2)\n    assert _simplistic_try_cast(\"1,2\", tuple[int, ...]) == (1, 2)\n    assert _simplistic_try_cast(\"\", tuple) == ()\n\n    # can't distinguish untyped tuple from zero length tuple\n    assert _simplistic_try_cast(\"1\", tuple[()]) == (\"1\",)\n    assert _simplistic_try_cast(\"1,2\", tuple[()]) == (\"1\", \"2\")\n\n\ndef test_try_cast_list():\n    assert _simplistic_try_cast(\"1\", list) == [1]\n    assert _simplistic_try_cast(\"1,str\", list) == [1, \"str\"]\n    assert _simplistic_try_cast(\"1,2\", list[int]) == [1, 2]\n    assert _simplistic_try_cast(\"1,2\", list[str]) == [\"1\", \"2\"]\n\n    assert _simplistic_try_cast(\"[1]\", list) == [1]\n    assert _simplistic_try_cast(\"[1,'str']\", list) == [1, \"str\"]\n    assert _simplistic_try_cast(\"[1,2]\", list[int]) == [1, 2]\n\n    with pytest.raises(CastError, match=r\"Could not cast '\\[1,2\\]' to list\\[str\\]\"):\n        _simplistic_try_cast(\"[1,2]\", list[str])\n    with pytest.raises(CastError, match=r\"Could not cast '\\[1,str\\]' to list\"):\n        _simplistic_try_cast(\"[1,str]\", list)\n\n\ndef test_try_cast_sequence_iterable():\n    for origin in {\n        collections.abc.Sequence,\n        collections.abc.Iterable,\n        typing.Sequence,\n        typing.Iterable,\n    }:\n        assert _simplistic_try_cast(\"1\", origin) == (1,)\n        assert _simplistic_try_cast(\"1,str\", origin) == (1, \"str\")\n        assert _simplistic_try_cast(\"1,2\", origin[int]) == (1, 2)\n        assert _simplistic_try_cast(\"1,2\", origin[str]) == (\"1\", \"2\")\n\n        assert _simplistic_try_cast(\"[1]\", origin) == [1]\n        assert _simplistic_try_cast(\"[1,'str']\", origin) == [1, \"str\"]\n        assert _simplistic_try_cast(\"[1,2]\", origin[int]) == [1, 2]\n\n        assert _simplistic_try_cast(\"(1,)\", origin) == (1,)\n        assert _simplistic_try_cast(\"(1,'str')\", origin) == (1, \"str\")\n        assert _simplistic_try_cast(\"(1,2)\", origin[int]) == (1, 2)\n\n        with pytest.raises(CastError, match=r\"Could not cast '\\(1\\)' to \\w+\"):\n            _simplistic_try_cast(\"(1)\", origin)\n        with pytest.raises(CastError, match=r\"Could not cast '\\[1,2\\]' to \\w+\\[str\\]\"):\n            _simplistic_try_cast(\"[1,2]\", origin[str])\n        with pytest.raises(CastError, match=r\"Could not cast '\\[1,str\\]' to \\w+\"):\n            _simplistic_try_cast(\"[1,str]\", origin)\n\n\ndef test_try_cast_dict():\n    assert _simplistic_try_cast(\"{1: 2}\", dict) == {1: 2}\n    assert _simplistic_try_cast(\"{1: 2}\", dict[int, int]) == {1: 2}\n    assert _simplistic_try_cast(\"{1: '2'}\", dict[int, str]) == {1: \"2\"}\n    assert _simplistic_try_cast(\"{1: '2'}\", dict[int, typing.Any]) == {1: \"2\"}\n\n    with pytest.raises(CastError, match=r\"\"\"Could not cast \"\\{1: '2'\\}\" to dict\\[int, int\\]\"\"\"):\n        _simplistic_try_cast(\"{1: '2'}\", dict[int, int])\n    with pytest.raises(CastError, match=r\"\"\"Could not cast \"\\{1: '2'\\}\" to dict\\[str, str\\]\"\"\"):\n        _simplistic_try_cast(\"{1: '2'}\", dict[str, str])\n    with pytest.raises(CastError, match=r\"Could not cast '\\{str: str\\}' to dict\"):\n        _simplistic_try_cast(\"{str: str}\", dict)\n\n\ndef test_try_cast_callable():\n    assert (\n        _simplistic_try_cast(\n            \"chz.tiepin:_simplistic_try_cast\", typing.Callable[[str, typing.Any], typing.Any]\n        )\n        is _simplistic_try_cast\n    )\n\n    assert (\n        _simplistic_try_cast(\"chz.tiepin:_simplistic_try_cast\", typing.Callable[..., typing.Any])\n        is _simplistic_try_cast\n    )\n\n    with pytest.raises(\n        CastError,\n        match=r\"Could not cast 'chz.tiepin:_simplistic_try_cast' to Callable\\[\\[int\\], int\\]\",\n    ):\n        _simplistic_try_cast(\"chz.tiepin:_simplistic_try_cast\", typing.Callable[[int], int])\n\n    with pytest.raises(\n        CastError,\n        match=r\"Could not cast 'does_not_exist:penguin' to Callable\\[\\[int\\], int\\]\\. Could not import module.*ModuleNotFoundError\",\n    ):\n        _simplistic_try_cast(\"does_not_exist:penguin\", typing.Callable[[int], int])\n\n\ndef test_try_cast_tuple_unpack():\n    # fmt: off\n    assert _simplistic_try_cast(\"1,2,3\", tuple[int, *tuple[str, int]]) == (1, \"2\", 3)\n    assert _simplistic_try_cast(\"1,2,3\", tuple[int, typing.Unpack[tuple[str, int]]]) == (1, \"2\", 3)\n\n    assert _simplistic_try_cast(\"1,2,3\", tuple[int, *tuple[str, ...]]) == (1, \"2\", \"3\")\n    assert _simplistic_try_cast(\"1,2,3\", tuple[int, typing.Unpack[tuple[str, ...]]]) == (1, \"2\", \"3\")\n\n    assert _simplistic_try_cast(\"1,2,3,4\", tuple[int, *tuple[str, ...]]) == (1, \"2\", \"3\", \"4\")\n    assert _simplistic_try_cast(\"1,2,3,4\", tuple[int, typing.Unpack[tuple[str, ...]]]) == (1, \"2\", \"3\", \"4\")\n\n    assert _simplistic_try_cast(\"1,2,3,4\", tuple[int, *tuple[str, ...], int, int]) == (1, \"2\", 3, 4)\n    assert _simplistic_try_cast(\"1,2,3,4\", tuple[int, typing.Unpack[tuple[str, ...]], int, int]) == (1, \"2\", 3, 4)\n\n    assert _simplistic_try_cast(\"1,2,3,4\", tuple[int, *tuple[str, *tuple[int, int]]]) == (1, \"2\", 3, 4)\n    assert _simplistic_try_cast(\"1,2,3,4\", tuple[int, typing.Unpack[tuple[str, typing.Unpack[tuple[int, int]]]]]) == (1, \"2\", 3, 4)\n    # fmt: on\n\n    with pytest.raises(\n        CastError,\n        match=re.escape(\n            \"Could not cast '1,2' to tuple[int, *tuple[str, int]] because of length mismatch\"\n        ),\n    ):\n        _simplistic_try_cast(\"1,2\", tuple[int, *tuple[str, int]])\n\n\ndef test_try_cast_union_overlap():\n    assert _simplistic_try_cast(\"1\", str | int) == 1\n    assert _simplistic_try_cast(\"1\", int | str) == 1\n\n    assert _simplistic_try_cast(\"None\", str | None) == None\n    assert _simplistic_try_cast(\"None\", None | str) == None\n\n    assert _simplistic_try_cast(\"all\", tuple[str, ...] | typing.Literal[\"all\"]) == \"all\"\n    assert _simplistic_try_cast(\"all\", typing.Literal[\"all\"] | tuple[str, ...]) == \"all\"\n\n    assert _simplistic_try_cast(\"None\", None | typing.Literal[\"None\"]) == \"None\"\n    assert _simplistic_try_cast(\"None\", typing.Literal[\"None\"] | None) == \"None\"\n\n    assert _simplistic_try_cast(\"None\", None | tuple[str, ...]) == None\n    assert _simplistic_try_cast(\"None\", tuple[str, ...] | None) == None\n\n    assert _simplistic_try_cast(\"\", tuple[str, ...] | str) == ()\n    assert _simplistic_try_cast(\"\", tuple[str, ...] | typing.Literal[\"\"]) == \"\"\n\n\ndef test_try_cast_enum():\n    class Color(enum.Enum):\n        RED = 1\n        GREEN = 2\n\n    assert _simplistic_try_cast(\"RED\", Color) == Color.RED\n    assert _simplistic_try_cast(\"GREEN\", Color) == Color.GREEN\n    assert _simplistic_try_cast(\"1\", Color) == Color.RED\n    assert _simplistic_try_cast(\"2\", Color) == Color.GREEN\n\n    with pytest.raises(CastError, match=\"Could not cast 'BLUE' to .*Color\"):\n        _simplistic_try_cast(\"BLUE\", Color)\n    with pytest.raises(CastError, match=\"Could not cast '3' to .*Color\"):\n        _simplistic_try_cast(\"3\", Color)\n\n\ndef test_try_cast_fractions():\n    assert _simplistic_try_cast(\"1/2\", fractions.Fraction) == fractions.Fraction(1, 2)\n    assert _simplistic_try_cast(\"1\", fractions.Fraction) == fractions.Fraction(1)\n    assert _simplistic_try_cast(\"1.5\", fractions.Fraction) == fractions.Fraction(3, 2)\n\n\ndef test_try_cast_pathlib():\n    assert _simplistic_try_cast(\"foo\", pathlib.Path) == pathlib.Path(\"foo\")\n\n\ndef test_try_cast_typevar():\n    assert _simplistic_try_cast(\"foo\", typing.TypeVar(\"T\")) == \"foo\"\n    assert _simplistic_try_cast(\"foo\", typing.TypeVar(\"T\", int, str)) == \"foo\"\n    assert _simplistic_try_cast(\"5\", typing.TypeVar(\"T\", bound=int)) == 5\n    assert _simplistic_try_cast(\"5\", typing.TypeVar(\"T\", bound=str | int)) == 5\n\n    with pytest.raises(CastError, match=\"Could not cast 'foo' to ~T\"):\n        assert _simplistic_try_cast(\"foo\", typing.TypeVar(\"T\", int, float)) == \"foo\"\n\n    with pytest.raises(CastError, match=\"Could not cast 'five' to int\"):\n        assert _simplistic_try_cast(\"five\", typing.TypeVar(\"T\", bound=int)) == 5\n\n\ndef test_approx_type_hash():\n    import builtins\n    from typing import Callable, Literal, TypeVar, Union\n\n    _T = TypeVar(\"_T\")\n\n    assert approx_type_hash(int)[:8] == \"46f8ab7c\"\n\n    assert approx_type_hash(str)[:8] == \"3442496b\"\n    assert approx_type_hash(\"str\")[:8] == \"3442496b\"\n    assert approx_type_hash(builtins.str)[:8] == \"3442496b\"\n\n    class float: ...\n\n    assert approx_type_hash(builtins.float)[:8] == \"685e8036\"\n    assert approx_type_hash(float)[:8] == \"685e8036\"  # can't tell the difference...\n    assert approx_type_hash(\"float\")[:8] == \"685e8036\"\n\n    assert approx_type_hash(list[int])[:8] == \"e4c2cba0\"\n    assert approx_type_hash(\"list[int]\")[:8] == \"e4c2cba0\"\n    assert approx_type_hash(list[\"int\"])[:8] == \"e4c2cba0\"\n\n    assert approx_type_hash(list[_T])[:8] == \"c6eb1529\"\n\n    assert approx_type_hash(Union[int, str])[:8] == \"c1729268\"\n    assert approx_type_hash(Union[str, int])[:8] == \"d811461d\"\n    assert approx_type_hash(Union[str, \"int\"])[:8] == \"d811461d\"\n\n    assert approx_type_hash(Callable[[int], str])[:8] == \"0dc453ef\"\n    assert approx_type_hash(Literal[1, \"asdf\", False])[:8] == \"ee5b7e0f\"\n\n\ndef test_simplistic_type_of_value():\n    tov = _simplistic_type_of_value\n\n    assert tov(1) is int\n    assert tov(\"foo\") is str\n\n    assert tov([1, 2, 3]) == list[int]\n    assert tov([1, 2, 3.0]) == list[int | float]\n    assert tov([1, 2, \"3\"]) == list[int | str]\n\n    assert tov((1, 2, 3)) == tuple[int, int, int]\n    assert tov((1, 2, 3.0)) == tuple[int, int, float]\n    assert tov((1, \"2\", 3.0)) == tuple[int, str, float]\n    assert tov(tuple(i for i in range(12))) == tuple[int, ...]\n\n    assert tov([(1, 2), (3, 4)]) == list[tuple[int, int]]\n    assert tov([(1, 2), (3, 4, 5)]) == list[tuple[int, int] | tuple[int, int, int]]\n\n    assert tov({1: \"a\", \"b\": 2}) == dict[int | str, str | int]\n\n    assert tov(int) == type[int]\n\n    class A: ...\n\n    class B(A): ...\n\n    class C(A): ...\n\n    assert tov([A(), B()]) == list[A]\n    assert tov([B(), A()]) == list[A]\n    assert tov([B(), C()]) == list[B | C]\n"
  },
  {
    "path": "tests/test_todo.py",
    "content": "import pytest\n\nimport chz\n\n# TODO: test inheritance, setattr, repr\n\n\ndef test_version():\n    @chz.chz(version=\"b4d37d6e\")\n    class X1:\n        a: int\n\n    @chz.chz(version=\"b4d37d6e-3\")\n    class X2:\n        a: int\n\n    with pytest.raises(ValueError, match=\"Version 'b4d37d6e' does not match '3902ee27'\"):\n\n        @chz.chz(version=\"b4d37d6e\")\n        class X3:\n            a: int\n            b: int\n"
  },
  {
    "path": "tests/test_validate.py",
    "content": "import math\nimport re\nfrom typing import Generic, TypeVar\n\nimport pytest\n\nimport chz\n\nT = TypeVar(\"T\")\n\n\ndef test_validate_readme():\n    @chz.chz\n    class Fraction:\n        numerator: int = chz.field(validator=chz.validators.typecheck)\n        denominator: int = chz.field(validator=[chz.validators.typecheck, chz.validators.gt(0)])\n\n        @chz.validate\n        def _check_reduced(self):\n            if math.gcd(self.numerator, self.denominator) > 1:\n                raise ValueError(\"Fraction is not reduced\")\n\n    Fraction(numerator=1, denominator=2)\n    Fraction(numerator=2, denominator=1)\n    with pytest.raises(ValueError, match=r\"Fraction is not reduced\"):\n        Fraction(numerator=2, denominator=4)\n\n\ndef test_validate():\n    @chz.chz\n    class X:\n        attr: int = chz.field(validator=chz.validators.instancecheck)\n\n    X(attr=1)\n    with pytest.raises(TypeError, match=\"Expected X_attr to be int, got str\"):\n        X(attr=\"1\")  # type: ignore\n\n    @chz.chz\n    class Y:\n        attr: int = chz.field(validator=chz.validators.instance_of(int))\n\n        @chz.validate\n        def _attr_validator(self):\n            if self.attr < 0:\n                raise ValueError(\"attr must be non-negative\")\n\n    Y(attr=1)\n    with pytest.raises(TypeError, match=\"Expected X_attr to be int, got str\"):\n        Y(attr=\"1\")  # type: ignore\n    with pytest.raises(ValueError, match=\"attr must be non-negative\"):\n        Y(attr=-1)\n\n    @chz.chz\n    class Z:\n        attr: int | str = chz.field(validator=chz.validators.typecheck)\n\n    Z(attr=1)\n    Z(attr=\"asdf\")\n    with pytest.raises(TypeError, match=r\"int \\| str, got bytes\"):\n        Z(attr=b\"fdsa\")  # type: ignore\n\n\ndef test_validate_replace():\n    @chz.chz\n    class X:\n        attr: int = chz.field(validator=chz.validators.typecheck)\n\n    x = X(attr=1)\n    x = chz.replace(x, attr=2)\n    with pytest.raises(TypeError, match=\"Expected X_attr to be int, got str\"):\n        chz.replace(x, attr=\"3\")\n\n\ndef test_for_all_fields():\n    @chz.chz\n    class X:\n        a: str\n        b: int\n\n        @chz.validate\n        def typecheck_all_fields(self):\n            chz.validators.for_all_fields(chz.validators.typecheck)(self)\n\n    X(a=\"asdf\", b=1)\n    with pytest.raises(TypeError, match=\"Expected X_a to be str, got int\"):\n        X(a=1, b=1)\n    with pytest.raises(TypeError, match=\"Expected X_b to be int, got str\"):\n        X(a=\"asdf\", b=\"asdf\")\n    with pytest.raises(TypeError, match=\"Expected X_a to be str, got int\"):\n        X(a=1, b=\"asdf\")\n\n\ndef test_validate_inheritance_field_level():\n    @chz.chz\n    class X:\n        a: str = chz.field(validator=chz.validators.typecheck)\n\n    @chz.chz\n    class Y(X):\n        b: int\n\n    with pytest.raises(TypeError, match=\"Expected X_a to be str, got int\"):\n        Y(a=1, b=1)\n\n    @chz.chz\n    class A:\n        x: X = chz.field(validator=chz.validators.typecheck)\n\n    @chz.chz\n    class B(A):\n        x: Y\n\n    A(x=X(a=\"asdf\"))\n    A(x=Y(a=\"asdf\", b=1))\n    B(x=Y(a=\"asdf\", b=1))\n    # But note that if you clobber an attribute, the field-level validator also gets clobbered\n    B(x=X(a=\"asdf\"))\n\n\ndef test_validate_init_property():\n    @chz.chz\n    class A1:\n        X_attr: str = chz.field(validator=chz.validators.instancecheck)\n\n        @chz.init_property\n        def attr(self) -> str:\n            return str(self.X_attr)\n\n    A1(attr=\"attr\")\n    with pytest.raises(TypeError, match=\"Expected X_attr to be str, got int\"):\n        A1(attr=1)\n\n    @chz.chz\n    class A2:\n        X_attr: int = chz.field(validator=chz.validators.instancecheck)\n\n        @chz.init_property\n        def attr(self) -> str:  # changes type\n            return str(self.X_attr)\n\n    A2(attr=1)\n\n    with pytest.raises(TypeError, match=\"Expected X_attr to be int, got str\"):\n        A2(attr=\"attr\")\n\n\ndef test_validate_init_property_order():\n    @chz.chz\n    class A:\n        value: int = chz.field(validator=chz.validators.gt(0))\n\n        @chz.init_property\n        def reciprocal(self):\n            return 1 / self.value\n\n    with pytest.raises(ValueError, match=\"Expected X_value to be greater than 0, got 0\"):\n        A(value=0)\n\n\ndef test_validate_munger():\n    # See comments in __chz_validate__\n\n    @chz.chz\n    class A:\n        a: int = chz.field(munger=lambda s, v: 100, validator=chz.validators.gt(10))\n\n    with pytest.raises(ValueError, match=\"Expected X_a to be greater than 10, got 1\"):\n        A(a=1)\n\n    @chz.chz\n    class A:\n        a: int = chz.field(munger=lambda s, v: 100, validator=chz.validators.lt(10))\n\n    with pytest.raises(ValueError, match=\"Expected a to be less than 10, got 100\"):\n        A(a=1)\n\n\ndef test_validate_ge_le() -> None:\n    @chz.chz\n    class A:\n        value: int = chz.field(validator=chz.validators.ge(0))\n\n    A(value=0)\n    with pytest.raises(ValueError, match=\"Expected X_value to be greater or equal to 0, got -1\"):\n        A(value=-1)\n\n    @chz.chz\n    class B:\n        value: int = chz.field(validator=chz.validators.le(0))\n\n    B(value=0)\n    with pytest.raises(ValueError, match=\"Expected X_value to be less or equal to 0, got 1\"):\n        B(value=1)\n\n\ndef test_validate_inheritance_class_level():\n    @chz.chz\n    class X:\n        a: str\n\n        @chz.validate\n        def check_a_is_banana(self):\n            if self.a != \"banana\":\n                raise ValueError(\"Banana only\")\n\n    @chz.chz\n    class Y(X):\n        b: int\n\n    with pytest.raises(ValueError, match=\"Banana only\"):\n        Y(a=\"nana\", b=1)\n\n    @chz.chz\n    class Z(Y):\n        c: bytes\n\n        @chz.validate\n        def check_c_is_not_empty(self):\n            if not self.c:\n                raise ValueError(\"c must not be empty\")\n\n        @chz.validate\n        def check_b_is_positive(self):\n            if self.b < 0:\n                raise ValueError(\"b must be positive\")\n\n    X(a=\"banana\")\n    Y(a=\"banana\", b=1)\n    Y(a=\"banana\", b=-1)\n\n    with pytest.raises(ValueError, match=\"Banana only\"):\n        Z(a=\"nana\", b=1, c=b\"asdf\")\n    with pytest.raises(ValueError, match=\"b must be positive\"):\n        Z(a=\"banana\", b=-1, c=b\"asdf\")\n\n    Z(a=\"banana\", b=1, c=b\"asdf\")\n\n    assert len(X.__chz_validators__) == 1\n    assert len(Y.__chz_validators__) == 1\n    assert len(Z.__chz_validators__) == 3\n\n\ndef test_validate_decorator_option():\n    @chz.chz(typecheck=True)\n    class X:\n        a: str\n\n    X(a=\"asdf\")\n    with pytest.raises(TypeError, match=\"Expected X_a to be str, got int\"):\n        X(a=1)\n\n    @chz.chz\n    class Y(X):\n        b: int\n\n    Y(a=\"asdf\", b=1)\n    with pytest.raises(TypeError, match=\"Expected X_a to be str, got int\"):\n        Y(a=1, b=1)\n    with pytest.raises(TypeError, match=\"Expected X_b to be int, got str\"):\n        Y(a=\"asdf\", b=\"asdf\")\n\n    @chz.chz(typecheck=True)\n    class Z(X):\n        c: bytes\n\n    assert len(Z.__chz_validators__) == 1\n\n    with pytest.raises(ValueError, match=\"Cannot disable typecheck; all validators are inherited\"):\n\n        @chz.chz(typecheck=False)\n        class A(X):\n            pass\n\n\ndef test_validate_mixins():\n    results = set()\n\n    @chz.chz\n    class M1:\n        @chz.validate\n        def v1(self):\n            results.add(\"v1\")\n\n    class M2NonChz:\n        @chz.validate\n        def v2(self):\n            results.add(\"v2\")\n\n    @chz.chz\n    class M3:\n        @chz.validate\n        def v3(self):\n            results.add(\"v3\")\n\n    @chz.chz\n    class Main(M1, M2NonChz, M3):\n        @chz.validate\n        def v4(self):\n            results.add(\"v4\")\n\n    Main()\n    assert results == {\"v1\", \"v2\", \"v3\", \"v4\"}\n\n\ndef test_validate_valid_regex():\n    @chz.chz\n    class A:\n        attr: str = chz.field(validator=chz.validators.valid_regex)\n\n    A(attr=\".*\")\n    with pytest.raises(\n        ValueError, match=\"Invalid regex in X_attr: nothing to repeat at position 0\"\n    ):\n        A(attr=\"*\")\n\n\ndef test_validate_literal():\n    from typing import Literal\n\n    @chz.chz(typecheck=True)\n    class A:\n        attr: Literal[\"a\", \"b\"]\n\n    A(attr=\"a\")\n    A(attr=\"b\")\n    with pytest.raises(TypeError, match=r\"Expected X_attr to be Literal\\['a', 'b'\\], got 'c'\"):\n        A(attr=\"c\")\n\n\ndef test_validate_const_default():\n    @chz.chz\n    class Image:\n        encoding: str\n\n    @chz.chz\n    class PNG(Image):\n        encoding: str = chz.field(default=\"png\", validator=chz.validators.const_default)\n\n    PNG()\n    PNG(encoding=\"png\")\n    with pytest.raises(\n        ValueError, match=\"Expected X_encoding to match the default 'png', got 'jpg'\"\n    ):\n        PNG(encoding=\"jpg\")\n\n\ndef test_validate_field_consistency():\n    @chz.chz\n    class D:\n        const: int\n\n    @chz.chz\n    class C:\n        map: dict[str, D]\n        seq: list[D]\n\n    @chz.chz\n    class B:\n        const: int\n        c: C\n\n    @chz.chz\n    class A:\n        const: int\n        b: B\n\n        @chz.validate\n        def field_consistency(self):\n            chz.validators.check_field_consistency_in_tree(self, {\"const\"})\n\n    with pytest.raises(\n        ValueError,\n        match=re.escape(\n            \"\"\"\\\nField 'const' has inconsistent values in object tree:\n1 at const\n2 at b.const\n3 at b.c.map.a.const\n4 at b.c.seq.0.const, b.c.seq.1.const, b.c.seq.2.const, ... (1 more)\"\"\"\n        ),\n    ):\n        A(\n            const=1,\n            b=B(\n                const=2,\n                c=C(map={\"a\": D(const=3)}, seq=[D(const=4), D(const=4), D(const=4), D(const=4)]),\n            ),\n        )\n\n    @chz.chz\n    class F:\n        const: int\n\n    @chz.chz\n    class E:\n        seq: list[F]\n\n    @chz.chz\n    class D:\n        const: int\n        e: E\n\n        @chz.validate\n        def field_consistency(self):\n            chz.validators.check_field_consistency_in_tree(self, {\"const\"}, regex_root=r\"e\\.seq\")\n\n    # This should not raise an error because the check is only done on the `e.seq` field\n    assert D(const=1, e=E(seq=[F(const=3), F(const=3)])).e.seq[0].const == 3\n\n    with pytest.raises(\n        ValueError,\n        match=re.escape(\n            \"\"\"\\\nField 'const' has inconsistent values in object tree:\n3 at e.seq.0.const\n4 at e.seq.1.const\"\"\"\n        ),\n    ):\n        D(const=1, e=E(seq=[F(const=3), F(const=4)]))\n\n\ndef test_is_override_catches_non_overriding() -> None:\n    @chz.chz\n    class HasBase:\n        x_different_name: int = 0\n\n    @chz.chz\n    class MyHasBase(HasBase):\n        x: int = chz.field(default=1, validator=chz.validators.is_override)\n\n    with pytest.raises(\n        ValueError,\n        match=\"Field x does not exist in any parent classes of test_validate:.*MyHasBase\",\n    ):\n        MyHasBase()\n\n\ndef test_is_override_catches_bad_types() -> None:\n    @chz.chz\n    class Base:\n        x: int = 1\n        my_tuple: tuple[int, str] = (0, \"good\")\n        my_homogenous_tuple: tuple[int, ...] = (0, 1)\n        my_dict: dict[int, str] = chz.field(default_factory=lambda: {0: \"hi\"})\n\n    Base()  # OK\n\n    @chz.chz\n    class GoodOverride(Base):\n        x: int = chz.field(default=5, validator=chz.validators.is_override)\n        my_tuple: tuple[int, str] = chz.field(\n            default=(1, \"hi\"), validator=chz.validators.is_override\n        )\n        my_homogenous_tuple: tuple[int, ...] = chz.field(\n            default=(1, 2), validator=chz.validators.is_override\n        )\n        my_dict: dict[int, str] = chz.field(\n            default_factory=lambda: {0: \"hi\"}, validator=chz.validators.is_override\n        )\n\n    result = GoodOverride()\n    assert result.x == 5\n    assert result.my_tuple == (1, \"hi\")\n    assert result.my_homogenous_tuple == (1, 2)\n    assert result.my_dict == {0: \"hi\"}\n\n    @chz.chz\n    class BadInt(Base):\n        x: int = chz.field(default=\"oops\", validator=chz.validators.is_override)\n\n    @chz.chz\n    class BadTuple(Base):\n        my_tuple: tuple[int, str] = chz.field(default=(1, 0), validator=chz.validators.is_override)\n\n    @chz.chz\n    class BadHomogenousTuple(Base):\n        my_homogenous_tuple: tuple[int, ...] = chz.field(\n            default=(1, \"oops\"), validator=chz.validators.is_override\n        )\n\n    @chz.chz\n    class BadDict(Base):\n        my_dict: dict[int, str] = chz.field(\n            default_factory=lambda: {\"oops\": \"foo\"}, validator=chz.validators.is_override\n        )\n\n    for cls in [BadInt, BadTuple, BadHomogenousTuple, BadDict]:\n        with pytest.raises(\n            ValueError,\n            match=r\"test_validate:.*Bad.+\\.X_.+' must be an instance of .+? to match the type on the original definition in test_validate:.*Base\",\n        ):\n            cls()\n\n\ndef test_is_override_mixin_catches_bad_types() -> None:\n    @chz.chz\n    class Base:\n        x: int = 1\n        my_tuple: tuple[int, str] = (0, \"good\")\n        my_homogenous_tuple: tuple[int, ...] = (0, 1)\n        my_dict: dict[int, str] = chz.field(default_factory=lambda: {0: \"hi\"})\n\n    Base()  # OK\n\n    @chz.chz\n    class GoodOverride(Base, chz.validators.IsOverrideMixin):\n        x: int = chz.field(default=5)\n        my_tuple: tuple[int, str] = chz.field(default=(1, \"hi\"))\n        my_homogenous_tuple: tuple[int, ...] = chz.field(default=(1, 2))\n        my_dict: dict[int, str] = chz.field(default_factory=lambda: {0: \"hi\"})\n\n    result = GoodOverride()\n    assert result.x == 5\n    assert result.my_tuple == (1, \"hi\")\n    assert result.my_homogenous_tuple == (1, 2)\n    assert result.my_dict == {0: \"hi\"}\n\n    @chz.chz\n    class BadInt(Base, chz.validators.IsOverrideMixin):\n        x: int = chz.field(default=\"oops\")\n\n    @chz.chz\n    class BadTuple(Base, chz.validators.IsOverrideMixin):\n        my_tuple: tuple[int, str] = chz.field(default=(1, 0))\n\n    @chz.chz\n    class BadHomogenousTuple(Base, chz.validators.IsOverrideMixin):\n        my_homogenous_tuple: tuple[int, ...] = chz.field(default=(1, \"oops\"))\n\n    @chz.chz\n    class BadDict(Base, chz.validators.IsOverrideMixin):\n        my_dict: dict[int, str] = chz.field(default_factory=lambda: {\"oops\": \"foo\"})\n\n    for cls in [BadInt, BadTuple, BadHomogenousTuple, BadDict]:\n        with pytest.raises(\n            ValueError,\n            match=r\"test_validate:.*Bad.+\\.X_.+' must be an instance of .+? to match the type on the original definition in test_validate:.*Base\",\n        ):\n            cls()\n\n\ndef test_is_override_catches_bad_generic_default_factory() -> None:\n    class Box(Generic[T]):\n        def __init__(self, value: T):\n            self.value = value\n\n    @chz.chz\n    class Atom(Generic[T]):\n        box: Box[str]\n\n    # Check that normal overriding words\n    @chz.chz\n    class MyGoodAtom(Atom, Generic[T]):\n        box: Box[str] = chz.field(\n            default_factory=lambda: Box[str](\"hi\"), validator=chz.validators.is_override\n        )\n\n    assert MyGoodAtom().box.value == \"hi\"\n\n    @chz.chz\n    class MyBadAtom(Atom, Generic[T]):\n        box: Box[str] = chz.field(\n            default_factory=lambda: Box[int](5), validator=chz.validators.is_override\n        )\n\n    with pytest.raises(\n        ValueError,\n        match=r\"test_validate:.*MyBadAtom.X_box' must be an instance of .*Box\\[str\\] to match the type on the original definition in test_validate:.*\\.Atom\",\n    ):\n        MyBadAtom()\n\n\ndef test_is_override_works_with_default_factory() -> None:\n    class Base:\n        def __init__(self) -> None:\n            self.value = 1\n\n    @chz.chz\n    class HasBases:\n        bases: tuple[Base, ...]\n\n    def my_bad_factory() -> tuple[Base, ...]:\n        return Base(), \"oop\", Base()  # type: ignore\n\n    @chz.chz\n    class MyBadHasBases(HasBases):\n        bases: tuple[Base, ...] = chz.field(\n            default_factory=my_bad_factory, validator=chz.validators.is_override\n        )\n\n    with pytest.raises(\n        ValueError,\n        match=r\".*MyBadHasBases.X_bases' must be an instance of tuple\\[.*Base, \\.\\.\\.\\] to match the type on the original definition in .*\\.HasBases\",\n    ):\n        MyBadHasBases()\n\n\ndef test_is_override_mixin_catches_bad_types_in_subclasses() -> None:\n    @chz.chz\n    class Atom:\n        x: int = 1\n\n    @chz.chz\n    class MyBadAtom(Atom, chz.validators.IsOverrideMixin):\n        x: int = chz.field(default=\"foo\")\n\n    @chz.chz\n    class Container:\n        atom: Atom\n\n    @chz.chz\n    class MyBadContainer(Container):\n        atom: Atom = chz.field(default_factory=MyBadAtom, blueprint_unspecified=MyBadAtom)\n\n    with pytest.raises(\n        ValueError,\n        match=\".*MyBadAtom.X_x' must be an instance of int to match the type on the original definition in .*Atom\",\n    ):\n        MyBadContainer()\n    with pytest.raises(\n        ValueError,\n        match=\".*MyBadAtom.X_x' must be an instance of int to match the type on the original definition in .*Atom\",\n    ):\n        chz.Blueprint(MyBadContainer).make()\n\n\ndef test_is_override_mixin_works_on_field_default() -> None:\n    @chz.chz\n    class Base:\n        x: int = 1\n\n    @chz.chz\n    class BaseSub(Base, chz.validators.IsOverrideMixin):\n        x: int = \"foo\"  # type: ignore  # that's the point of this test!\n\n    with pytest.raises(\n        ValueError,\n        match=\".*BaseSub.X_x' must be an instance of int to match the type on the original definition in .*Base\",\n    ):\n        BaseSub()\n\n    @chz.chz\n    class BadIntermediate(Base):\n        x: str = \"sneaky intermediate class trying to mess things up\"  # type: ignore\n\n    @chz.chz\n    class BadFinal(BadIntermediate, chz.validators.IsOverrideMixin):\n        pass\n\n    with pytest.raises(\n        ValueError,\n        match=\".*BadFinal.X_x' must be an instance of int to match the type on the original definition in .*Base\",\n    ):\n        BadFinal()\n\n    @chz.chz\n    class BadFinalThatMatchesIntermediate(BadIntermediate, chz.validators.IsOverrideMixin):\n        x: str = \"strings are bad here because it doesn't match the Base definition!\"  # type: ignore\n\n    with pytest.raises(\n        ValueError,\n        match=\".*BadFinalThatMatchesIntermediate.X_x' must be an instance of int to match the type on the original definition in .*Base\",\n    ):\n        BadFinalThatMatchesIntermediate()\n\n\ndef test_is_override_mixin_works_with_x_fields() -> None:\n    @chz.chz\n    class Base:\n        X_value: str = \"foo\"\n\n        @chz.init_property\n        def value(self) -> tuple[str, ...]:\n            return tuple(self.X_value.split(\",\"))\n\n    @chz.chz\n    class SomeOverride(Base, chz.validators.IsOverrideMixin):\n        X_value: str = chz.field(default=\"a,b\")\n\n    instance = chz.Blueprint(SomeOverride).make()\n    assert instance.value == (\"a\", \"b\")\n\n    @chz.chz\n    class BadOverride(Base, chz.validators.IsOverrideMixin):\n        X_value: tuple[str, ...] = chz.field(\n            default=(\"look at me eagerly create\", \"a tuple of strings\")\n        )  # type: ignore\n\n    @chz.chz\n    class BadOverride2(Base, chz.validators.IsOverrideMixin):\n        X_value: str = chz.field(default=(\"type signature is good\", \"but default is bad\"))\n\n    with pytest.raises(\n        ValueError,\n        match=r\".*BadOverride.X_value' must be an instance of str to match the type on the original definition in .*Base\",\n    ):\n        BadOverride()\n    with pytest.raises(\n        ValueError,\n        match=r\".*BadOverride2.X_value' must be an instance of str to match the type on the original definition in .*Base\",\n    ):\n        BadOverride2()\n"
  }
]