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.
Files changed (173) hide show
  1. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/PKG-INFO +2 -2
  2. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/README.md +1 -1
  3. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/pyproject.toml +1 -1
  4. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/__init__.py +1 -1
  5. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/core/sentinels/_args.py +25 -1
  6. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/desc/_attri_box.py +46 -5
  7. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/desc/_base_descriptor.py +12 -6
  8. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/desc/_fix_box.py +1 -1
  9. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/dispatch/_dispatcher.py +97 -33
  10. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/dispatch/_flex_call.py +44 -12
  11. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/dispatch/_overload.py +11 -3
  12. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/dispatch/_type_sig.py +24 -7
  13. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/ezdata/__init__.py +5 -0
  14. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/ezdata/_ez_data.py +11 -6
  15. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/ezdata/_ez_field.py +48 -2
  16. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/ezdata/_ez_hook.py +171 -49
  17. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/ezdata/_ez_space.py +31 -26
  18. worktoy-1.0.1.dev1/src/worktoy/ezdata/_ez_store.py +67 -0
  19. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/keenum/_kee_box.py +32 -10
  20. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/keenum/_kee_flags.py +3 -1
  21. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/keenum/_kee_flags_meta.py +9 -2
  22. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/keenum/_kee_flags_space.py +9 -1
  23. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/keenum/_kee_meta.py +23 -4
  24. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/keenum/_kee_meta_meta.py +0 -1
  25. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/mcls/_abstract_namespace.py +1 -2
  26. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/mcls/_base_meta.py +2 -2
  27. worktoy-1.0.1.dev1/src/worktoy/mcls/_base_space.py +466 -0
  28. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/mcls/space_hooks/_flex_call_hook.py +11 -2
  29. worktoy-1.0.1.dev1/src/worktoy/mcls/space_hooks/_load_space_hook.py +125 -0
  30. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/mcls/space_hooks/_reserved_names.py +4 -0
  31. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/mcls/space_hooks/_reserved_namespace_hook.py +4 -0
  32. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/utilities/_type_cast.py +9 -3
  33. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/waitaminute/ezdata/__init__.py +4 -0
  34. worktoy-1.0.1.dev1/src/worktoy/waitaminute/ezdata/_class_field_error.py +53 -0
  35. worktoy-1.0.1.dev1/src/worktoy/waitaminute/ezdata/_reserved_method_error.py +50 -0
  36. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/waitaminute/keenum/__init__.py +2 -0
  37. worktoy-1.0.1.dev1/src/worktoy/waitaminute/keenum/_kee_flag_name_error.py +44 -0
  38. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/work_test/_base_test.py +15 -18
  39. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/work_test/_sub_test.py +23 -1
  40. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/work_test/samplers/_lorem_sampler.py +7 -5
  41. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy.egg-info/PKG-INFO +2 -2
  42. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy.egg-info/SOURCES.txt +4 -0
  43. worktoy-1.0.1.dev0/src/worktoy/mcls/_base_space.py +0 -346
  44. worktoy-1.0.1.dev0/src/worktoy/mcls/space_hooks/_load_space_hook.py +0 -121
  45. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/LICENSE +0 -0
  46. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/setup.cfg +0 -0
  47. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/core/__init__.py +0 -0
  48. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/core/_instance_owner.py +0 -0
  49. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/core/_meta_type.py +0 -0
  50. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/core/_object.py +0 -0
  51. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/core/sentinels/__init__.py +0 -0
  52. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/core/sentinels/_deleted.py +0 -0
  53. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/core/sentinels/_desc.py +0 -0
  54. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/core/sentinels/_meta_call.py +0 -0
  55. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/core/sentinels/_owner.py +0 -0
  56. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/core/sentinels/_sentinel.py +0 -0
  57. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/core/sentinels/_this.py +0 -0
  58. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/desc/__init__.py +0 -0
  59. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/desc/_alias.py +0 -0
  60. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/desc/_fast_box.py +0 -0
  61. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/desc/_field.py +0 -0
  62. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/desc/_symbolic_name.py +0 -0
  63. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/dispatch/__init__.py +0 -0
  64. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/dispatch/_call_me_maybe.py +0 -0
  65. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/dispatch/_permuter.py +0 -0
  66. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/dispatch/_permuter_method.py +0 -0
  67. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/ezdata/_ez_meta.py +0 -0
  68. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/ezdata/_fields.py +0 -0
  69. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/keenum/__init__.py +0 -0
  70. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/keenum/_access_num.py +0 -0
  71. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/keenum/_kee_flag.py +0 -0
  72. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/keenum/_kee_flags_hook.py +0 -0
  73. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/keenum/_kee_member.py +0 -0
  74. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/keenum/_kee_num.py +0 -0
  75. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/keenum/_kee_space.py +0 -0
  76. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/keenum/_kee_space_hook.py +0 -0
  77. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/lorem_ipsum/__init__.py +0 -0
  78. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/lorem_ipsum/_base_generator.py +0 -0
  79. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/lorem_ipsum/_clause.py +0 -0
  80. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/lorem_ipsum/_gaussian_lengths.py +0 -0
  81. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/lorem_ipsum/_paragraph.py +0 -0
  82. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/lorem_ipsum/_sentence.py +0 -0
  83. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/lorem_ipsum/_stochastic_variable.py +0 -0
  84. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/lorem_ipsum/_stochastic_word.py +0 -0
  85. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/lorem_ipsum/_words.py +0 -0
  86. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/mcls/__init__.py +0 -0
  87. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/mcls/_abstract_metaclass.py +0 -0
  88. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/mcls/_base_object.py +0 -0
  89. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/mcls/space_hooks/__init__.py +0 -0
  90. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/mcls/space_hooks/_abstract_space_hook.py +0 -0
  91. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/mcls/space_hooks/_name_hook.py +0 -0
  92. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/mcls/space_hooks/_space_desc.py +0 -0
  93. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/py.typed +0 -0
  94. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/utilities/__init__.py +0 -0
  95. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/utilities/_args_count.py +0 -0
  96. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/utilities/_bipartite_matching.py +0 -0
  97. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/utilities/_class_body_template.py +0 -0
  98. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/utilities/_directory.py +0 -0
  99. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/utilities/_exception_info.py +0 -0
  100. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/utilities/_join_words.py +0 -0
  101. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/utilities/_maybe.py +0 -0
  102. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/utilities/_quick_desc.py +0 -0
  103. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/utilities/_replace_flex.py +0 -0
  104. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/utilities/_resolve_mro.py +0 -0
  105. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/utilities/_slice_len.py +0 -0
  106. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/utilities/_string_list.py +0 -0
  107. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/utilities/_text_fmt.py +0 -0
  108. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/utilities/_unpack.py +0 -0
  109. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/utilities/_valid_slice.py +0 -0
  110. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/utilities/_word_wrap.py +0 -0
  111. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/utilities/combinatorics/__init__.py +0 -0
  112. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/utilities/combinatorics/_arrangement.py +0 -0
  113. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/utilities/combinatorics/_arrangements.py +0 -0
  114. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/utilities/combinatorics/_index_permutations.py +0 -0
  115. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/waitaminute/__init__.py +0 -0
  116. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/waitaminute/_missing_variable.py +0 -0
  117. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/waitaminute/_subclass_exception.py +0 -0
  118. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/waitaminute/_type_exception.py +0 -0
  119. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/waitaminute/_unpack_exception.py +0 -0
  120. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/waitaminute/_variable_not_none.py +0 -0
  121. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/waitaminute/control_flow/__init__.py +0 -0
  122. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/waitaminute/control_flow/_control_class_error.py +0 -0
  123. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/waitaminute/control_flow/_control_flow.py +0 -0
  124. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/waitaminute/control_flow/_control_space.py +0 -0
  125. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/waitaminute/control_flow/_meta_flow.py +0 -0
  126. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/waitaminute/control_flow/_skip_set.py +0 -0
  127. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/waitaminute/desc/__init__.py +0 -0
  128. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/waitaminute/desc/_access_error.py +0 -0
  129. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/waitaminute/desc/_descriptor_exception.py +0 -0
  130. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/waitaminute/desc/_phantom_box_error.py +0 -0
  131. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/waitaminute/desc/_protected_error.py +0 -0
  132. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/waitaminute/desc/_read_only_error.py +0 -0
  133. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/waitaminute/desc/_without_exception.py +0 -0
  134. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/waitaminute/desc/_write_once_error.py +0 -0
  135. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/waitaminute/dispatch/__init__.py +0 -0
  136. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/waitaminute/dispatch/_dispatch_exception.py +0 -0
  137. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/waitaminute/dispatch/_duplicate_signature.py +0 -0
  138. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/waitaminute/dispatch/_type_cast_exception.py +0 -0
  139. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/waitaminute/ezdata/_duplicate_error.py +0 -0
  140. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/waitaminute/ezdata/_extra_positional_exception.py +0 -0
  141. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/waitaminute/ezdata/_incomplete_field_exception.py +0 -0
  142. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/waitaminute/ezdata/_kw_only_exception.py +0 -0
  143. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/waitaminute/ezdata/_reserved_field_error.py +0 -0
  144. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/waitaminute/keenum/_kee_box_exception.py +0 -0
  145. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/waitaminute/keenum/_kee_box_type_error.py +0 -0
  146. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/waitaminute/keenum/_kee_box_value_error.py +0 -0
  147. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/waitaminute/keenum/_kee_case_exception.py +0 -0
  148. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/waitaminute/keenum/_kee_duplicate.py +0 -0
  149. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/waitaminute/keenum/_kee_flag_duplicate.py +0 -0
  150. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/waitaminute/keenum/_kee_name_conflict.py +0 -0
  151. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/waitaminute/keenum/_kee_resolve_error.py +0 -0
  152. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/waitaminute/keenum/_kee_type_exception.py +0 -0
  153. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/waitaminute/keenum/_kee_write_once_error.py +0 -0
  154. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/waitaminute/meta/__init__.py +0 -0
  155. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/waitaminute/meta/_del_exception.py +0 -0
  156. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/waitaminute/meta/_duplicate_hook.py +0 -0
  157. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/waitaminute/meta/_hook_exception.py +0 -0
  158. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/waitaminute/meta/_illegal_instantiation.py +0 -0
  159. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/waitaminute/meta/_questionable_syntax.py +0 -0
  160. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/waitaminute/meta/_reserved_name.py +0 -0
  161. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/waitaminute/meta/_unbound_class_hook.py +0 -0
  162. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/work_test/__init__.py +0 -0
  163. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/work_test/_complex_mixin.py +0 -0
  164. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/work_test/_complex_test.py +0 -0
  165. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/work_test/samplers/__init__.py +0 -0
  166. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/work_test/samplers/_base_sampler.py +0 -0
  167. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/work_test/samplers/_float_sampler.py +0 -0
  168. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/work_test/samplers/_gaussian_sampler.py +0 -0
  169. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/work_test/samplers/_int_sampler.py +0 -0
  170. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/work_test/samplers/_symbolic_sampler.py +0 -0
  171. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy/work_test/samplers/_word_sampler.py +0 -0
  172. {worktoy-1.0.1.dev0 → worktoy-1.0.1.dev1}/src/worktoy.egg-info/dependency_links.txt +0 -0
  173. {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.dev0
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
  [![PyPI version](https://img.shields.io/pypi/v/worktoy)](https://pypi.org/project/worktoy/)
33
33
  [![License: Apache 2.0](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
34
34
 
35
- # worktoy v1.0.1-dev0 (development build)
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
  [![PyPI version](https://img.shields.io/pypi/v/worktoy)](https://pypi.org/project/worktoy/)
5
5
  [![License: Apache 2.0](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
6
6
 
7
- # worktoy v1.0.1-dev0 (development build)
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
@@ -5,7 +5,7 @@ build-backend = 'setuptools.build_meta'
5
5
 
6
6
  [project]
7
7
  name = "worktoy"
8
- version = "1.0.1-dev0"
8
+ version = "1.0.1-dev1"
9
9
  authors = [
10
10
  { name = "Asger Jon Vistisen", email = "asgerjon2@gmail.com" },
11
11
  ]
@@ -39,7 +39,7 @@ chain.
39
39
  # Copyright (c) 2025-2026 Asger Jon Vistisen
40
40
  from __future__ import annotations
41
41
 
42
- __version__ = '1.0.1-dev0'
42
+ __version__ = '1.0.1-dev1'
43
43
 
44
44
  from . import utilities
45
45
  from . import waitaminute
@@ -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'. Assigning a value:
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.getPrivateName()
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.getPrivateName()
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.getPrivateName()
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.getPrivateName()
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 sigFuncMap.items():
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 variadicFuncs:
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(self, sig: TypeSig, func: Method) -> Method:
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(self, sig: TypeSig, func: Method) -> Method:
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, **kwargs) -> Callable:
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 'MethodType'
451
- wrapping that compiled function. The bound object is created on
452
- first access and cached on the instance under a reserved key
453
- derived from '_getCachedKey', so subsequent accesses on the
454
- same instance return the same bound object.
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
- key = self._getCachedKey()
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 fewer than the required number of
51
- positional-or-keyword arguments is supplied (parameters with
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 is not checked here and surfaces as the
54
- wrapped function's own TypeError,
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
- posNames: tuple[str, ...] = code.co_varnames[:maxPos]
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: tuple[str, ...] = posNames[n:minPos]
109
- infoSpec: str = """Function '%s' requires at least '%d' positional
110
- arguments but received only '%d', missing arguments: (%s)!"""
111
- missingStr = joinWords(*missing, )
112
- info = infoSpec % (name, minPos, n, missingStr)
113
- raise TypeError(textFmt(info))
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 registration order and return the
60
- first match.
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
- Two consequences for how you stack '@overload' decorators:
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
  # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #