worktoy 1.0.1.dev0__tar.gz → 1.0.1.dev1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/PKG-INFO +2 -2
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/README.md +1 -1
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/pyproject.toml +1 -1
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/__init__.py +1 -1
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/core/sentinels/_args.py +25 -1
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/desc/_attri_box.py +46 -5
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/desc/_base_descriptor.py +12 -6
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/desc/_fix_box.py +1 -1
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/dispatch/_dispatcher.py +97 -33
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/dispatch/_flex_call.py +44 -12
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/dispatch/_overload.py +11 -3
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/dispatch/_type_sig.py +24 -7
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/ezdata/__init__.py +5 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/ezdata/_ez_data.py +11 -6
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/ezdata/_ez_field.py +48 -2
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/ezdata/_ez_hook.py +171 -49
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/ezdata/_ez_space.py +31 -26
- worktoy-1.0.1.dev1/src/worktoy/ezdata/_ez_store.py +67 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/keenum/_kee_box.py +32 -10
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/keenum/_kee_flags.py +3 -1
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/keenum/_kee_flags_meta.py +9 -2
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/keenum/_kee_flags_space.py +9 -1
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/keenum/_kee_meta.py +23 -4
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/keenum/_kee_meta_meta.py +0 -1
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/mcls/_abstract_namespace.py +1 -2
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/mcls/_base_meta.py +2 -2
- worktoy-1.0.1.dev1/src/worktoy/mcls/_base_space.py +466 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/mcls/space_hooks/_flex_call_hook.py +11 -2
- worktoy-1.0.1.dev1/src/worktoy/mcls/space_hooks/_load_space_hook.py +125 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/mcls/space_hooks/_reserved_names.py +4 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/mcls/space_hooks/_reserved_namespace_hook.py +4 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/utilities/_type_cast.py +9 -3
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/waitaminute/ezdata/__init__.py +4 -0
- worktoy-1.0.1.dev1/src/worktoy/waitaminute/ezdata/_class_field_error.py +53 -0
- worktoy-1.0.1.dev1/src/worktoy/waitaminute/ezdata/_reserved_method_error.py +50 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/waitaminute/keenum/__init__.py +2 -0
- worktoy-1.0.1.dev1/src/worktoy/waitaminute/keenum/_kee_flag_name_error.py +44 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/work_test/_base_test.py +15 -18
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/work_test/_sub_test.py +23 -1
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/work_test/samplers/_lorem_sampler.py +7 -5
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy.egg-info/PKG-INFO +2 -2
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy.egg-info/SOURCES.txt +4 -0
- worktoy-1.0.1.dev0/src/worktoy/mcls/_base_space.py +0 -346
- worktoy-1.0.1.dev0/src/worktoy/mcls/space_hooks/_load_space_hook.py +0 -121
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/LICENSE +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/setup.cfg +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/core/__init__.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/core/_instance_owner.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/core/_meta_type.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/core/_object.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/core/sentinels/__init__.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/core/sentinels/_deleted.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/core/sentinels/_desc.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/core/sentinels/_meta_call.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/core/sentinels/_owner.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/core/sentinels/_sentinel.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/core/sentinels/_this.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/desc/__init__.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/desc/_alias.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/desc/_fast_box.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/desc/_field.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/desc/_symbolic_name.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/dispatch/__init__.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/dispatch/_call_me_maybe.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/dispatch/_permuter.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/dispatch/_permuter_method.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/ezdata/_ez_meta.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/ezdata/_fields.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/keenum/__init__.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/keenum/_access_num.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/keenum/_kee_flag.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/keenum/_kee_flags_hook.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/keenum/_kee_member.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/keenum/_kee_num.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/keenum/_kee_space.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/keenum/_kee_space_hook.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/lorem_ipsum/__init__.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/lorem_ipsum/_base_generator.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/lorem_ipsum/_clause.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/lorem_ipsum/_gaussian_lengths.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/lorem_ipsum/_paragraph.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/lorem_ipsum/_sentence.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/lorem_ipsum/_stochastic_variable.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/lorem_ipsum/_stochastic_word.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/lorem_ipsum/_words.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/mcls/__init__.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/mcls/_abstract_metaclass.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/mcls/_base_object.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/mcls/space_hooks/__init__.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/mcls/space_hooks/_abstract_space_hook.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/mcls/space_hooks/_name_hook.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/mcls/space_hooks/_space_desc.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/py.typed +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/utilities/__init__.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/utilities/_args_count.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/utilities/_bipartite_matching.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/utilities/_class_body_template.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/utilities/_directory.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/utilities/_exception_info.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/utilities/_join_words.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/utilities/_maybe.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/utilities/_quick_desc.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/utilities/_replace_flex.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/utilities/_resolve_mro.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/utilities/_slice_len.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/utilities/_string_list.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/utilities/_text_fmt.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/utilities/_unpack.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/utilities/_valid_slice.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/utilities/_word_wrap.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/utilities/combinatorics/__init__.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/utilities/combinatorics/_arrangement.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/utilities/combinatorics/_arrangements.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/utilities/combinatorics/_index_permutations.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/waitaminute/__init__.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/waitaminute/_missing_variable.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/waitaminute/_subclass_exception.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/waitaminute/_type_exception.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/waitaminute/_unpack_exception.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/waitaminute/_variable_not_none.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/waitaminute/control_flow/__init__.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/waitaminute/control_flow/_control_class_error.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/waitaminute/control_flow/_control_flow.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/waitaminute/control_flow/_control_space.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/waitaminute/control_flow/_meta_flow.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/waitaminute/control_flow/_skip_set.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/waitaminute/desc/__init__.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/waitaminute/desc/_access_error.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/waitaminute/desc/_descriptor_exception.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/waitaminute/desc/_phantom_box_error.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/waitaminute/desc/_protected_error.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/waitaminute/desc/_read_only_error.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/waitaminute/desc/_without_exception.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/waitaminute/desc/_write_once_error.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/waitaminute/dispatch/__init__.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/waitaminute/dispatch/_dispatch_exception.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/waitaminute/dispatch/_duplicate_signature.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/waitaminute/dispatch/_type_cast_exception.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/waitaminute/ezdata/_duplicate_error.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/waitaminute/ezdata/_extra_positional_exception.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/waitaminute/ezdata/_incomplete_field_exception.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/waitaminute/ezdata/_kw_only_exception.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/waitaminute/ezdata/_reserved_field_error.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/waitaminute/keenum/_kee_box_exception.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/waitaminute/keenum/_kee_box_type_error.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/waitaminute/keenum/_kee_box_value_error.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/waitaminute/keenum/_kee_case_exception.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/waitaminute/keenum/_kee_duplicate.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/waitaminute/keenum/_kee_flag_duplicate.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/waitaminute/keenum/_kee_name_conflict.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/waitaminute/keenum/_kee_resolve_error.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/waitaminute/keenum/_kee_type_exception.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/waitaminute/keenum/_kee_write_once_error.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/waitaminute/meta/__init__.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/waitaminute/meta/_del_exception.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/waitaminute/meta/_duplicate_hook.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/waitaminute/meta/_hook_exception.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/waitaminute/meta/_illegal_instantiation.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/waitaminute/meta/_questionable_syntax.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/waitaminute/meta/_reserved_name.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/waitaminute/meta/_unbound_class_hook.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/work_test/__init__.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/work_test/_complex_mixin.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/work_test/_complex_test.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/work_test/samplers/__init__.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/work_test/samplers/_base_sampler.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/work_test/samplers/_float_sampler.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/work_test/samplers/_gaussian_sampler.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/work_test/samplers/_int_sampler.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/work_test/samplers/_symbolic_sampler.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/work_test/samplers/_word_sampler.py +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy.egg-info/dependency_links.txt +0 -0
- {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: worktoy
|
|
3
|
-
Version: 1.0.1.
|
|
3
|
+
Version: 1.0.1.dev1
|
|
4
4
|
Summary: Declarative structure for Python that enforces types at runtime and enables true function overloading
|
|
5
5
|
Author-email: Asger Jon Vistisen <asgerjon2@gmail.com>
|
|
6
6
|
License: Apache-2.0
|
|
@@ -32,7 +32,7 @@ Dynamic: license-file
|
|
|
32
32
|
[](https://pypi.org/project/worktoy/)
|
|
33
33
|
[](https://opensource.org/licenses/Apache-2.0)
|
|
34
34
|
|
|
35
|
-
# worktoy v1.0.1-
|
|
35
|
+
# worktoy v1.0.1-dev1 (development build)
|
|
36
36
|
|
|
37
37
|
**worktoy** provides utilities for Python development focused on
|
|
38
38
|
reducing boilerplate while improving type safety and readability. Each
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
[](https://pypi.org/project/worktoy/)
|
|
5
5
|
[](https://opensource.org/licenses/Apache-2.0)
|
|
6
6
|
|
|
7
|
-
# worktoy v1.0.1-
|
|
7
|
+
# worktoy v1.0.1-dev1 (development build)
|
|
8
8
|
|
|
9
9
|
**worktoy** provides utilities for Python development focused on
|
|
10
10
|
reducing boilerplate while improving type safety and readability. Each
|
|
@@ -11,7 +11,7 @@ from typing import TYPE_CHECKING
|
|
|
11
11
|
from . import SentinelMeta
|
|
12
12
|
|
|
13
13
|
if TYPE_CHECKING: # pragma: no cover
|
|
14
|
-
from typing import Self, Iterator, Optional
|
|
14
|
+
from typing import Any, Self, Iterator, Optional
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
class _MetaARGS(SentinelMeta):
|
|
@@ -38,6 +38,11 @@ class ARGS(metaclass=_MetaARGS):
|
|
|
38
38
|
"""
|
|
39
39
|
ARGS uses a Sentinel metaclass and provides a placeholder for a starred
|
|
40
40
|
argument in an overload signature.
|
|
41
|
+
|
|
42
|
+
Each subscript builds a new instance, so two signatures written the same
|
|
43
|
+
way hold two distinct instances. Instances therefore compare and hash by
|
|
44
|
+
their inner type, which lets a variadic signature in a subclass be
|
|
45
|
+
recognized as equal to the one it overrides.
|
|
41
46
|
"""
|
|
42
47
|
|
|
43
48
|
def __iter__(self) -> Iterator[Self]:
|
|
@@ -46,6 +51,25 @@ class ARGS(metaclass=_MetaARGS):
|
|
|
46
51
|
"""
|
|
47
52
|
yield from (self,)
|
|
48
53
|
|
|
54
|
+
def __eq__(self, other: Any) -> bool:
|
|
55
|
+
if not isinstance(other, ARGS):
|
|
56
|
+
return NotImplemented
|
|
57
|
+
return True if self.__inner_type__ is other.__inner_type__ else False
|
|
58
|
+
|
|
59
|
+
def __hash__(self) -> int:
|
|
60
|
+
return hash((type(self).__name__, self.__inner_type__))
|
|
61
|
+
|
|
62
|
+
def __str__(self) -> str:
|
|
63
|
+
"""
|
|
64
|
+
The rendering follows the subscript that created the instance, for
|
|
65
|
+
example 'ARGS[int]'.
|
|
66
|
+
"""
|
|
67
|
+
innerType = self.__inner_type__
|
|
68
|
+
innerName = getattr(innerType, '__name__', None) or str(innerType)
|
|
69
|
+
return '%s[%s]' % (type(self).__name__, innerName)
|
|
70
|
+
|
|
71
|
+
__repr__ = __str__
|
|
72
|
+
|
|
49
73
|
@classmethod
|
|
50
74
|
def __class_getitem__(cls, type_: type) -> Self:
|
|
51
75
|
"""
|
|
@@ -148,7 +148,11 @@ class AttriBox(BaseDescriptor[T]):
|
|
|
148
148
|
--------------------
|
|
149
149
|
Reading the field returns the stored 'T' instance, building the
|
|
150
150
|
deferred default on first read. Reading a deleted field raises
|
|
151
|
-
'MissingVariable'.
|
|
151
|
+
'MissingVariable'. The field always holds an instance of 'T', though
|
|
152
|
+
not necessarily of exactly 'T': a 'bool' assigned to an
|
|
153
|
+
'AttriBox[int]' stays a 'bool'. A field type whose constructor returns
|
|
154
|
+
something other than an instance of it is refused with
|
|
155
|
+
'TypeException'. Assigning a value:
|
|
152
156
|
|
|
153
157
|
- a value already of type 'T' is stored unchanged;
|
|
154
158
|
- otherwise a lossless 'typeCast(T, value)' is tried with no
|
|
@@ -192,6 +196,20 @@ class AttriBox(BaseDescriptor[T]):
|
|
|
192
196
|
constructor taking an argument raises 'TypeException' here, chained
|
|
193
197
|
from the 'TypeError' that 'object.__init__' raises, rather than
|
|
194
198
|
silently handing back the owner.
|
|
199
|
+
|
|
200
|
+
Each instance stores the value of a field under the private name that
|
|
201
|
+
'Object.getPrivateName' derives from the field name, followed by the
|
|
202
|
+
lower-case name of the box class and '_field_object__', so an
|
|
203
|
+
'AttriBox' named 'fooBar' stores at
|
|
204
|
+
'__foo_bar__attribox_field_object__'. The double underscore inside
|
|
205
|
+
keeps the storage apart from dunders Python uses and from private
|
|
206
|
+
class attributes such as '__foo_bar__'. Names that differ only in how
|
|
207
|
+
their words are joined or capitalised derive the same private name,
|
|
208
|
+
though: 'fooBar' and 'foo_bar' both store at
|
|
209
|
+
'__foo_bar__attribox_field_object__', as do 'X' and 'x' at
|
|
210
|
+
'__x__attribox_field_object__'. Declaring two such fields on one
|
|
211
|
+
class, the same name once in each spelling, leaves them sharing one
|
|
212
|
+
storage, and the behaviour is then undefined.
|
|
195
213
|
"""
|
|
196
214
|
|
|
197
215
|
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
|
@@ -218,6 +236,23 @@ class AttriBox(BaseDescriptor[T]):
|
|
|
218
236
|
# DOMAIN SPECIFIC # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
|
219
237
|
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
|
220
238
|
|
|
239
|
+
def _getStorageName(self) -> str:
|
|
240
|
+
"""
|
|
241
|
+
The '_getStorageName' method returns the name under which each
|
|
242
|
+
instance stores the value of this field: the private name from
|
|
243
|
+
'getPrivateName', followed by the lower-case name of the box class
|
|
244
|
+
and '_field_object__'. An 'AttriBox' named 'fooBar' stores at
|
|
245
|
+
'__foo_bar__attribox_field_object__' and a 'FixBox' of that name at
|
|
246
|
+
'__foo_bar__fixbox_field_object__', so anyone who meets the name
|
|
247
|
+
can tell what put it there. The double underscore inside sets the
|
|
248
|
+
name apart from every name written in the '__snake_case__' style, so
|
|
249
|
+
the stored value never lands on a dunder Python uses, on the state
|
|
250
|
+
'Object' keeps, or on a private class attribute of the owner, such
|
|
251
|
+
as the '__foo_bar__' of the 'Field' pattern.
|
|
252
|
+
"""
|
|
253
|
+
boxName = type(self).__name__.lower()
|
|
254
|
+
return '%s%s_field_object__' % (self.getPrivateName(), boxName)
|
|
255
|
+
|
|
221
256
|
def _resolve(self, *args, **kwargs) -> T:
|
|
222
257
|
"""
|
|
223
258
|
The '_resolve' method builds a new instance of the field type from
|
|
@@ -343,7 +378,8 @@ class AttriBox(BaseDescriptor[T]):
|
|
|
343
378
|
------
|
|
344
379
|
TypeException
|
|
345
380
|
If neither the cast nor the field-type constructor accepts the
|
|
346
|
-
arguments
|
|
381
|
+
arguments, or if the constructor returns something that is not
|
|
382
|
+
an instance of the field type.
|
|
347
383
|
"""
|
|
348
384
|
fieldType = self.getFieldType()
|
|
349
385
|
fieldObject = None
|
|
@@ -371,6 +407,11 @@ class AttriBox(BaseDescriptor[T]):
|
|
|
371
407
|
name = 'value'
|
|
372
408
|
badValue = args[0] if args else None
|
|
373
409
|
raise TypeException(name, badValue, fieldType) from exception
|
|
410
|
+
if not isinstance(fieldObject, fieldType):
|
|
411
|
+
# A constructor may return anything at all. Storing what it
|
|
412
|
+
# returned would break the rule that the field always holds an
|
|
413
|
+
# instance of its field type.
|
|
414
|
+
raise TypeException(self.getFieldName(), fieldObject, fieldType)
|
|
374
415
|
try:
|
|
375
416
|
setattr(fieldObject, '__field_name__', self.getFieldName())
|
|
376
417
|
setattr(fieldObject, '__field_owner__', self.getFieldOwner())
|
|
@@ -386,7 +427,7 @@ class AttriBox(BaseDescriptor[T]):
|
|
|
386
427
|
field-type instance on the first read and caching it under the
|
|
387
428
|
private name.
|
|
388
429
|
"""
|
|
389
|
-
pvtName = self.
|
|
430
|
+
pvtName = self._getStorageName()
|
|
390
431
|
try:
|
|
391
432
|
value = getattr(instance, pvtName)
|
|
392
433
|
except AttributeError as attributeError:
|
|
@@ -409,7 +450,7 @@ class AttriBox(BaseDescriptor[T]):
|
|
|
409
450
|
contract.
|
|
410
451
|
"""
|
|
411
452
|
fieldType = self.getFieldType()
|
|
412
|
-
pvtName = self.
|
|
453
|
+
pvtName = self._getStorageName()
|
|
413
454
|
if isinstance(value, fieldType) or kwargs.get('_root', False):
|
|
414
455
|
return setattr(instance, pvtName, value)
|
|
415
456
|
# Catch recursion
|
|
@@ -444,7 +485,7 @@ class AttriBox(BaseDescriptor[T]):
|
|
|
444
485
|
instance by storing the 'DELETED' sentinel under the private
|
|
445
486
|
attribute name, which a later read translates into 'MissingVariable'.
|
|
446
487
|
"""
|
|
447
|
-
pvtName = self.
|
|
488
|
+
pvtName = self._getStorageName()
|
|
448
489
|
setattr(instance, pvtName, DELETED)
|
|
449
490
|
|
|
450
491
|
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
|
@@ -30,6 +30,12 @@ class BaseDescriptor(Object, Generic[T]):
|
|
|
30
30
|
original. Registering the same method name twice records it twice and
|
|
31
31
|
fires it twice.
|
|
32
32
|
|
|
33
|
+
A callback may declare fewer parameters than the descriptor passes it,
|
|
34
|
+
and the positional arguments it does not declare are dropped. Each
|
|
35
|
+
decorator returns the callback wrapped by 'flexCall' for this, so the
|
|
36
|
+
wrapping happens once as the class body runs, and an override that was
|
|
37
|
+
never decorated is wrapped as it is called.
|
|
38
|
+
|
|
33
39
|
The following example illustrates an exhaustively decorated descriptor
|
|
34
40
|
in a class body:
|
|
35
41
|
|
|
@@ -155,32 +161,32 @@ class BaseDescriptor(Object, Generic[T]):
|
|
|
155
161
|
def preGet(self, callback: Func) -> Func:
|
|
156
162
|
existing = self._getPreGetKeys()
|
|
157
163
|
self.__pre_get_keys__ = (*existing, callback.__name__)
|
|
158
|
-
return callback
|
|
164
|
+
return flexCall(callback)
|
|
159
165
|
|
|
160
166
|
def onGet(self, callback: Func) -> Func:
|
|
161
167
|
existing = self._getOnGetKeys()
|
|
162
168
|
self.__on_get_keys__ = (*existing, callback.__name__)
|
|
163
|
-
return callback
|
|
169
|
+
return flexCall(callback)
|
|
164
170
|
|
|
165
171
|
def preSet(self, callback: Func) -> Func:
|
|
166
172
|
existing = self._getPreSetKeys()
|
|
167
173
|
self.__pre_set_keys__ = (*existing, callback.__name__)
|
|
168
|
-
return callback
|
|
174
|
+
return flexCall(callback)
|
|
169
175
|
|
|
170
176
|
def onSet(self, callback: Func) -> Func:
|
|
171
177
|
existing = self._getOnSetKeys()
|
|
172
178
|
self.__on_set_keys__ = (*existing, callback.__name__)
|
|
173
|
-
return callback
|
|
179
|
+
return flexCall(callback)
|
|
174
180
|
|
|
175
181
|
def preDelete(self, callback: Func) -> Func:
|
|
176
182
|
existing = self._getPreDeleteKeys()
|
|
177
183
|
self.__pre_delete_keys__ = (*existing, callback.__name__)
|
|
178
|
-
return callback
|
|
184
|
+
return flexCall(callback)
|
|
179
185
|
|
|
180
186
|
def onDelete(self, callback: Func) -> Func:
|
|
181
187
|
existing = self._getOnDeleteKeys()
|
|
182
188
|
self.__on_delete_keys__ = (*existing, callback.__name__)
|
|
183
|
-
return callback
|
|
189
|
+
return flexCall(callback)
|
|
184
190
|
|
|
185
191
|
def setName(self, callback: Func) -> Func:
|
|
186
192
|
existing = self._getSetNameKeys()
|
|
@@ -42,7 +42,7 @@ class FixBox(AttriBox[T]):
|
|
|
42
42
|
'AttributeError' rather than 'is None', so an explicit assignment of
|
|
43
43
|
'None' still counts as the one write.
|
|
44
44
|
"""
|
|
45
|
-
pvtName = self.
|
|
45
|
+
pvtName = self._getStorageName()
|
|
46
46
|
try:
|
|
47
47
|
oldValue = object.__getattribute__(instance, pvtName)
|
|
48
48
|
except AttributeError:
|
|
@@ -79,6 +79,17 @@ class Dispatcher(Object):
|
|
|
79
79
|
signature you want to match first; any later, broader signature
|
|
80
80
|
becomes a fallback for what the earlier ones did not catch.
|
|
81
81
|
|
|
82
|
+
When 'BaseSpace' builds a 'Dispatcher' for a class, it registers the
|
|
83
|
+
class body's own signatures first, followed by those inherited from
|
|
84
|
+
the classes in its method resolution order, nearest first, each with
|
|
85
|
+
its distance from the class. FASTEST and FAST try them in that order,
|
|
86
|
+
so an override wins every type match it competes in, including a
|
|
87
|
+
'THIS' signature that a further subclass matches only through
|
|
88
|
+
'isinstance'. SLOW tries the greatest distance first instead: a
|
|
89
|
+
signature added in a subclass then only receives the calls none of
|
|
90
|
+
the inherited signatures could take through a cast, and never
|
|
91
|
+
redirects a call its parent already handled.
|
|
92
|
+
|
|
82
93
|
Performance contract
|
|
83
94
|
--------------------
|
|
84
95
|
FASTEST is roughly one or two orders of magnitude faster than FAST
|
|
@@ -100,7 +111,9 @@ class Dispatcher(Object):
|
|
|
100
111
|
|
|
101
112
|
# Private Variables
|
|
102
113
|
__sig_funcs__ = None
|
|
114
|
+
__sig_distances__ = None
|
|
103
115
|
__variadic_funcs__ = None
|
|
116
|
+
__variadic_distances__ = None
|
|
104
117
|
__fallback_func__ = None
|
|
105
118
|
__field_name__ = None
|
|
106
119
|
__field_owner__ = None
|
|
@@ -120,6 +133,45 @@ class Dispatcher(Object):
|
|
|
120
133
|
def _getVariadicFuncs(self) -> SigFuncList:
|
|
121
134
|
return maybe(self.__variadic_funcs__, [])
|
|
122
135
|
|
|
136
|
+
def _getSigDistances(self) -> list[int]:
|
|
137
|
+
return maybe(self.__sig_distances__, [])
|
|
138
|
+
|
|
139
|
+
def _getVariadicDistances(self) -> list[int]:
|
|
140
|
+
return maybe(self.__variadic_distances__, [])
|
|
141
|
+
|
|
142
|
+
@staticmethod
|
|
143
|
+
def _getCastOrder(
|
|
144
|
+
sigFuncs: SigFuncList,
|
|
145
|
+
distances: list[int],
|
|
146
|
+
) -> SigFuncList:
|
|
147
|
+
"""
|
|
148
|
+
The '_getCastOrder' method returns the order in which the cast passes
|
|
149
|
+
try the registrations: the greatest distance first, and registration
|
|
150
|
+
order among equal distances. 'distances' lists the distance of each
|
|
151
|
+
pair in 'sigFuncs', position by position. A signature inherited from
|
|
152
|
+
a parent is therefore cast to before a signature its subclass added,
|
|
153
|
+
so adding a signature in a subclass never redirects a call the parent
|
|
154
|
+
already handled through a cast.
|
|
155
|
+
|
|
156
|
+
Parameters
|
|
157
|
+
----------
|
|
158
|
+
sigFuncs : SigFuncList
|
|
159
|
+
Spells out to 'list[tuple[TypeSig, Method]]'. The registrations
|
|
160
|
+
in registration order.
|
|
161
|
+
distances : list[int]
|
|
162
|
+
The distance of each registration, in the same order.
|
|
163
|
+
|
|
164
|
+
Returns
|
|
165
|
+
-------
|
|
166
|
+
SigFuncList
|
|
167
|
+
The same registrations in the order the cast passes try them.
|
|
168
|
+
"""
|
|
169
|
+
# Indexing rather than zipping raises 'IndexError' should the two
|
|
170
|
+
# lists ever fall out of step, where 'zip' would silently drop the
|
|
171
|
+
# unmatched registrations from the cast passes.
|
|
172
|
+
positions = sorted(range(len(sigFuncs)), key=lambda i: -distances[i])
|
|
173
|
+
return [sigFuncs[i] for i in positions]
|
|
174
|
+
|
|
123
175
|
def _getFallbackFunction(self) -> Optional[Method]:
|
|
124
176
|
return self.__fallback_func__
|
|
125
177
|
|
|
@@ -169,12 +221,15 @@ class Dispatcher(Object):
|
|
|
169
221
|
raise MissingVariable(self, '__field_owner__', type)
|
|
170
222
|
return fieldOwner
|
|
171
223
|
|
|
172
|
-
def _getCachedKey(self, ) -> str:
|
|
173
|
-
return '__bound_dispatch_%s__' % self._getFieldName()
|
|
174
|
-
|
|
175
224
|
def _createCachedFunction(self) -> None:
|
|
176
225
|
sigFuncMap = self._getSigFuncMap()
|
|
177
226
|
variadicFuncs = self._getVariadicFuncs()
|
|
227
|
+
castFuncs = self._getCastOrder(
|
|
228
|
+
self._getSigFuncList(), self._getSigDistances(),
|
|
229
|
+
)
|
|
230
|
+
castVariadics = self._getCastOrder(
|
|
231
|
+
variadicFuncs, self._getVariadicDistances(),
|
|
232
|
+
)
|
|
178
233
|
fallback = self._getFallbackFunction()
|
|
179
234
|
finalizer = self._getFinalizerFunction()
|
|
180
235
|
dispatcher = self
|
|
@@ -216,7 +271,7 @@ class Dispatcher(Object):
|
|
|
216
271
|
if matched:
|
|
217
272
|
return func(instance, *args, **kwargs)
|
|
218
273
|
# SLOW
|
|
219
|
-
for sig, func in
|
|
274
|
+
for sig, func in castFuncs:
|
|
220
275
|
castArgs = []
|
|
221
276
|
if not sig.__allow_flex__:
|
|
222
277
|
continue
|
|
@@ -235,7 +290,7 @@ class Dispatcher(Object):
|
|
|
235
290
|
else:
|
|
236
291
|
return func(instance, *castArgs, **kwargs)
|
|
237
292
|
# SLOW (variadic)
|
|
238
|
-
for sig, func in
|
|
293
|
+
for sig, func in castVariadics:
|
|
239
294
|
if not sig.__allow_flex__:
|
|
240
295
|
continue
|
|
241
296
|
rawTypes = sig.getRawTypes()
|
|
@@ -299,7 +354,12 @@ class Dispatcher(Object):
|
|
|
299
354
|
# SETTERS # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
|
300
355
|
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
|
301
356
|
|
|
302
|
-
def addSigFunc(
|
|
357
|
+
def addSigFunc(
|
|
358
|
+
self,
|
|
359
|
+
sig: TypeSig,
|
|
360
|
+
func: Method,
|
|
361
|
+
distance: int = 0,
|
|
362
|
+
) -> Method:
|
|
303
363
|
"""
|
|
304
364
|
The 'addSigFunc' method adds a signature-function pair to the
|
|
305
365
|
internal signature-function map.
|
|
@@ -311,6 +371,12 @@ class Dispatcher(Object):
|
|
|
311
371
|
func : Method
|
|
312
372
|
The function to register. 'Method' expands to
|
|
313
373
|
'Callable[[Any, ...], Any]'.
|
|
374
|
+
distance : int, optional
|
|
375
|
+
How far from the owning class the registration comes: 0 for the
|
|
376
|
+
owning class itself, 1 for the nearest class it inherits the
|
|
377
|
+
registration from, and so on. The exact-type and isinstance
|
|
378
|
+
passes ignore it and try registrations in registration order.
|
|
379
|
+
The cast passes try the greatest distance first. Defaults to 0.
|
|
314
380
|
|
|
315
381
|
Returns
|
|
316
382
|
-------
|
|
@@ -328,10 +394,16 @@ class Dispatcher(Object):
|
|
|
328
394
|
if existingSig == sig:
|
|
329
395
|
raise DuplicateSignature(sig, existingFunc, func)
|
|
330
396
|
self.__sig_funcs__ = [*existing, (sig, func,)]
|
|
397
|
+
self.__sig_distances__ = [*self._getSigDistances(), distance]
|
|
331
398
|
self.__compiled_func__ = None
|
|
332
399
|
return func
|
|
333
400
|
|
|
334
|
-
def addVariadicSigFunc(
|
|
401
|
+
def addVariadicSigFunc(
|
|
402
|
+
self,
|
|
403
|
+
sig: TypeSig,
|
|
404
|
+
func: Method,
|
|
405
|
+
distance: int = 0,
|
|
406
|
+
) -> Method:
|
|
335
407
|
"""
|
|
336
408
|
The 'addVariadicSigFunc' method registers a '(variadicSig, func)'
|
|
337
409
|
pair on this dispatcher. Unlike 'addSigFunc', it does not reject
|
|
@@ -350,6 +422,9 @@ class Dispatcher(Object):
|
|
|
350
422
|
func : Method
|
|
351
423
|
The function to register. 'Method' expands to
|
|
352
424
|
'Callable[[Any, ...], Any]'.
|
|
425
|
+
distance : int, optional
|
|
426
|
+
How far from the owning class the registration comes, with the
|
|
427
|
+
same meaning as in 'addSigFunc'. Defaults to 0.
|
|
353
428
|
|
|
354
429
|
Returns
|
|
355
430
|
-------
|
|
@@ -359,6 +434,7 @@ class Dispatcher(Object):
|
|
|
359
434
|
"""
|
|
360
435
|
existing = self._getVariadicFuncs()
|
|
361
436
|
self.__variadic_funcs__ = [*existing, (sig, func,)]
|
|
437
|
+
self.__variadic_distances__ = [*self._getVariadicDistances(), distance]
|
|
362
438
|
self.__compiled_func__ = None
|
|
363
439
|
return func
|
|
364
440
|
|
|
@@ -434,7 +510,7 @@ class Dispatcher(Object):
|
|
|
434
510
|
# Python API # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
|
435
511
|
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
|
436
512
|
|
|
437
|
-
def __get__(self, instance: Any, owner: type
|
|
513
|
+
def __get__(self, instance: Any, owner: type) -> Callable:
|
|
438
514
|
"""
|
|
439
515
|
The '__get__' method is the descriptor protocol entry point. It
|
|
440
516
|
always returns a callable suitable for dispatching against the
|
|
@@ -447,11 +523,14 @@ class Dispatcher(Object):
|
|
|
447
523
|
argument. The compiled function is built lazily by
|
|
448
524
|
'_getCachedFunction' and cached on the 'Dispatcher' itself.
|
|
449
525
|
|
|
450
|
-
Instance-level access ('obj.attr') returns a bound
|
|
451
|
-
wrapping that compiled function
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
526
|
+
Instance-level access ('obj.attr') returns a fresh bound
|
|
527
|
+
'MethodType' wrapping that compiled function on every access, just
|
|
528
|
+
as Python does for a plain method. Nothing is stored on the
|
|
529
|
+
instance. A bound method stored there would be found again by
|
|
530
|
+
whichever 'Dispatcher' of the same name reads the instance next,
|
|
531
|
+
such as the parent's 'Dispatcher' reached through 'super'. It would
|
|
532
|
+
also be carried along by 'copy.copy', still bound to the original,
|
|
533
|
+
and it would keep the instance alive through a reference cycle.
|
|
455
534
|
|
|
456
535
|
The live 'Dispatcher' is no longer reachable through normal
|
|
457
536
|
attribute access on the owning class. Reach it through
|
|
@@ -474,24 +553,7 @@ class Dispatcher(Object):
|
|
|
474
553
|
"""
|
|
475
554
|
if instance is None:
|
|
476
555
|
return self._getCachedFunction()
|
|
477
|
-
|
|
478
|
-
try:
|
|
479
|
-
boundCache = getattr(instance, key)
|
|
480
|
-
except AttributeError as attributeError:
|
|
481
|
-
if kwargs.get('_recursion', False):
|
|
482
|
-
raise RecursionError from attributeError
|
|
483
|
-
unboundCache = self._getCachedFunction()
|
|
484
|
-
boundCache = MethodType(unboundCache, instance)
|
|
485
|
-
try:
|
|
486
|
-
setattr(instance, key, boundCache)
|
|
487
|
-
except AttributeError:
|
|
488
|
-
# An instance refusing attribute creation, such as a frozen
|
|
489
|
-
# enumeration member, cannot host the per-instance cache. The
|
|
490
|
-
# bound method is then rebuilt on every access instead.
|
|
491
|
-
return boundCache
|
|
492
|
-
return self.__get__(instance, owner, _recursion=True)
|
|
493
|
-
else:
|
|
494
|
-
return boundCache
|
|
556
|
+
return MethodType(self._getCachedFunction(), instance)
|
|
495
557
|
|
|
496
558
|
def __set__(self, instance: Any, value: Any, **kwargs) -> Never:
|
|
497
559
|
"""
|
|
@@ -544,8 +606,8 @@ class Dispatcher(Object):
|
|
|
544
606
|
def clone(self, ) -> Self:
|
|
545
607
|
"""
|
|
546
608
|
The 'clone' method builds a copy of this 'Dispatcher' carrying the
|
|
547
|
-
same registered signature/function pairs (including variadic ones)
|
|
548
|
-
the same fallback, and the same finalizer.
|
|
609
|
+
same registered signature/function pairs (including variadic ones)
|
|
610
|
+
with their distances, the same fallback, and the same finalizer.
|
|
549
611
|
|
|
550
612
|
Returns
|
|
551
613
|
-------
|
|
@@ -556,9 +618,11 @@ class Dispatcher(Object):
|
|
|
556
618
|
"""
|
|
557
619
|
newLoad = type(self)()
|
|
558
620
|
newLoad.__sig_funcs__ = self._getSigFuncList()
|
|
621
|
+
newLoad.__sig_distances__ = self._getSigDistances()
|
|
559
622
|
variadicFuncs = self._getVariadicFuncs()
|
|
560
623
|
if variadicFuncs:
|
|
561
624
|
newLoad.__variadic_funcs__ = [*variadicFuncs, ]
|
|
625
|
+
newLoad.__variadic_distances__ = [*self._getVariadicDistances(), ]
|
|
562
626
|
fallback = self._getFallbackFunction()
|
|
563
627
|
if fallback is not None:
|
|
564
628
|
newLoad.__fallback_func__ = fallback
|
|
@@ -41,18 +41,47 @@ def isFlex(func: FunctionType) -> bool:
|
|
|
41
41
|
return True
|
|
42
42
|
|
|
43
43
|
|
|
44
|
+
def _missingNames(
|
|
45
|
+
requiredNames: tuple[str, ...],
|
|
46
|
+
received: int,
|
|
47
|
+
kwargs: dict,
|
|
48
|
+
) -> tuple[str, ...]:
|
|
49
|
+
"""
|
|
50
|
+
The '_missingNames' function returns the names of the required
|
|
51
|
+
positional parameters that a call leaves without a value.
|
|
52
|
+
'requiredNames' lists those parameters in order, and the first
|
|
53
|
+
'received' of them took their values by position. Each later one is
|
|
54
|
+
missing unless 'kwargs' names it. A positional-only parameter named in
|
|
55
|
+
'kwargs' counts as supplied here, and the wrapped function then refuses
|
|
56
|
+
it with its own 'TypeError', which says what went wrong.
|
|
57
|
+
"""
|
|
58
|
+
out = []
|
|
59
|
+
for name in requiredNames[received:]:
|
|
60
|
+
if name not in kwargs:
|
|
61
|
+
out.append(name)
|
|
62
|
+
return (*out,)
|
|
63
|
+
|
|
64
|
+
|
|
44
65
|
def flexCall(func: FunctionType) -> FunctionType:
|
|
45
66
|
"""Wrap 'func' with truncating positional-argument dispatch.
|
|
46
67
|
|
|
68
|
+
The descriptor notification callbacks of 'BaseDescriptor' are wrapped
|
|
69
|
+
this way, since a descriptor passes each of them the same arguments
|
|
70
|
+
however few it declares. So is every plain function in the body of a
|
|
71
|
+
class whose namespace declares 'FlexCallHook'. An ordinary method is
|
|
72
|
+
otherwise left as it was written.
|
|
73
|
+
|
|
47
74
|
The returned wrapper:
|
|
48
75
|
- silently drops positional args beyond the wrapped function's
|
|
49
76
|
declared positional arity,
|
|
50
|
-
- raises TypeError when
|
|
51
|
-
|
|
77
|
+
- raises TypeError when a required positional parameter receives no
|
|
78
|
+
value, neither by position nor by keyword (parameters with
|
|
52
79
|
defaults are not counted as required); a missing required
|
|
53
|
-
keyword-only argument
|
|
54
|
-
|
|
80
|
+
keyword-only argument, or a positional-only parameter named by
|
|
81
|
+
keyword, is not checked here and surfaces as the wrapped
|
|
82
|
+
function's own TypeError,
|
|
55
83
|
- passes keyword args through unchanged,
|
|
84
|
+
- carries the attributes set on the wrapped function,
|
|
56
85
|
- caches arity at wrap time; per-call cost is one length check,
|
|
57
86
|
one comparison, and a slice only when truncation fires.
|
|
58
87
|
|
|
@@ -100,22 +129,25 @@ def flexCall(func: FunctionType) -> FunctionType:
|
|
|
100
129
|
# re-introspected per call.
|
|
101
130
|
maxPos: int = code.co_argcount
|
|
102
131
|
minPos: int = code.co_argcount - len(func.__defaults__ or ())
|
|
103
|
-
|
|
132
|
+
requiredNames: tuple[str, ...] = code.co_varnames[:minPos]
|
|
104
133
|
|
|
105
134
|
def wrapper(*args, **kwargs) -> Any:
|
|
106
135
|
n: int = len(args)
|
|
107
136
|
if n < minPos:
|
|
108
|
-
missing
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
137
|
+
missing = _missingNames(requiredNames, n, kwargs)
|
|
138
|
+
if missing:
|
|
139
|
+
infoSpec: str = """Function '%s' requires at least '%d' positional
|
|
140
|
+
arguments but received only '%d', missing arguments: (%s)!"""
|
|
141
|
+
missingStr = joinWords(*missing, )
|
|
142
|
+
info = infoSpec % (name, minPos, n, missingStr)
|
|
143
|
+
raise TypeError(textFmt(info))
|
|
114
144
|
if n > maxPos:
|
|
115
145
|
args = args[:maxPos]
|
|
116
146
|
return func(*args, **kwargs)
|
|
117
147
|
|
|
118
|
-
# Manual metadata copy: explicit, dependency-free.
|
|
148
|
+
# Manual metadata copy: explicit, dependency-free. The attributes set
|
|
149
|
+
# on 'func' come first, so the ones the wrapper sets below win.
|
|
150
|
+
wrapper.__dict__.update(func.__dict__)
|
|
119
151
|
wrapper.__name__ = func.__name__
|
|
120
152
|
wrapper.__qualname__ = func.__qualname__
|
|
121
153
|
wrapper.__module__ = func.__module__
|
|
@@ -56,10 +56,10 @@ class overload: # NOQA
|
|
|
56
56
|
matches in three kinds of pass (see 'Dispatcher' docstring):
|
|
57
57
|
exact-type hash lookup, then isinstance iteration, then 'typeCast'
|
|
58
58
|
iteration. Only the first is constant-time; the other two scan
|
|
59
|
-
every registered overload in
|
|
60
|
-
|
|
59
|
+
every registered overload in order and return the first match.
|
|
60
|
+
Within one class body that order is the declaration order.
|
|
61
61
|
|
|
62
|
-
|
|
62
|
+
Three consequences for how you stack '@overload' decorators:
|
|
63
63
|
|
|
64
64
|
1. Register against the exact concrete types callers will pass.
|
|
65
65
|
'@overload(int)' for a caller that supplies 'int' values keeps
|
|
@@ -75,6 +75,14 @@ class overload: # NOQA
|
|
|
75
75
|
Order your decorators to match the dispatch you want; treat
|
|
76
76
|
overlapping isinstance coverage as deliberate, not as a bug
|
|
77
77
|
for the dispatcher to second-guess.
|
|
78
|
+
|
|
79
|
+
3. A subclass overloading an inherited name keeps the inherited
|
|
80
|
+
overloads and adds its own. Its own come first in the exact-type
|
|
81
|
+
and isinstance passes, so an override wins wherever it matches,
|
|
82
|
+
and an equal signature replaces the inherited one outright. The
|
|
83
|
+
'typeCast' pass tries the inherited overloads first, so a
|
|
84
|
+
signature the subclass adds never takes a call its parent
|
|
85
|
+
already handled through a cast.
|
|
78
86
|
"""
|
|
79
87
|
|
|
80
88
|
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|