dycw-utilities 0.113.0__tar.gz → 0.113.2__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 (228) hide show
  1. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/PKG-INFO +4 -4
  2. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/pyproject.toml +7 -7
  3. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/test_asyncio.py +38 -3
  4. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/test_dataclasses.py +31 -2
  5. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/test_iterables.py +24 -0
  6. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/utilities/__init__.py +1 -1
  7. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/utilities/asyncio.py +42 -2
  8. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/utilities/dataclasses.py +24 -1
  9. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/utilities/iterables.py +30 -14
  10. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/utilities/math.py +2 -2
  11. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/utilities/types.py +5 -0
  12. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/.gitignore +0 -0
  13. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/LICENSE +0 -0
  14. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/README.md +0 -0
  15. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/__init__.py +0 -0
  16. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/conftest.py +0 -0
  17. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/modules/__init__.py +0 -0
  18. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/modules/package_missing/__init__.py +0 -0
  19. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/modules/package_missing/module.py +0 -0
  20. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/modules/package_with/__init__.py +0 -0
  21. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/modules/package_with/outer_1.py +0 -0
  22. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/modules/package_with/outer_2.py +0 -0
  23. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/modules/package_with/subpackage/__init__.py +0 -0
  24. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/modules/package_with/subpackage/inner_1.py +0 -0
  25. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/modules/package_with/subpackage/inner_2.py +0 -0
  26. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/modules/package_with/subpackage/inner_3.py +0 -0
  27. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/modules/package_without/__init__.py +0 -0
  28. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/modules/package_without/module_1.py +0 -0
  29. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/modules/package_without/module_2.py +0 -0
  30. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/modules/standalone.py +0 -0
  31. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/modules/with_imports.py +0 -0
  32. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/regressions/test_pytest_regressions/TestMultipleRegressionFixtures__test_main__obj.json +0 -0
  33. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/regressions/test_pytest_regressions/TestMultipleRegressionFixtures__test_main__series.json +0 -0
  34. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_int.json +0 -0
  35. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_literal__false.json +0 -0
  36. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_literal__true.json +0 -0
  37. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_nested.json +0 -0
  38. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/regressions/test_pytest_regressions/TestPolarsRegressionFixture__test_dataframe.json +0 -0
  39. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/regressions/test_pytest_regressions/TestPolarsRegressionFixture__test_series.json +0 -0
  40. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/scripts/__init__.py +0 -0
  41. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/scripts/test_async_service/__init__.py +0 -0
  42. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/scripts/test_async_service/__main__.py +0 -0
  43. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/scripts/test_async_service/run.sh +0 -0
  44. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/scripts/test_queue_processor/__init__.py +0 -0
  45. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/scripts/test_queue_processor/__main__.py +0 -0
  46. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/scripts/test_queue_processor/run.sh +0 -0
  47. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/test_altair.py +0 -0
  48. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/test_astor.py +0 -0
  49. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/test_atomicwrites.py +0 -0
  50. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/test_atools.py +0 -0
  51. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/test_cachetools.py +0 -0
  52. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/test_click.py +0 -0
  53. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/test_concurrent.py +0 -0
  54. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/test_contextlib.py +0 -0
  55. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/test_contextvars.py +0 -0
  56. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/test_cryptography.py +0 -0
  57. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/test_cvxpy.py +0 -0
  58. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/test_datetime.py +0 -0
  59. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/test_enum.py +0 -0
  60. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/test_errors.py +0 -0
  61. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/test_eventkit.py +0 -0
  62. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/test_fastapi.py +0 -0
  63. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/test_fpdf2.py +0 -0
  64. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/test_functions.py +0 -0
  65. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/test_functools.py +0 -0
  66. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/test_getpass.py +0 -0
  67. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/test_git.py +0 -0
  68. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/test_hashlib.py +0 -0
  69. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/test_http.py +0 -0
  70. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/test_hypothesis.py +0 -0
  71. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/test_ipython.py +0 -0
  72. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/test_jupyter.py +0 -0
  73. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/test_lightweight_charts.py +0 -0
  74. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/test_logging.py +0 -0
  75. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/test_loguru.py +0 -0
  76. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/test_luigi.py +0 -0
  77. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/test_math.py +0 -0
  78. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/test_memory_profiler.py +0 -0
  79. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/test_modules.py +0 -0
  80. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/test_more_itertools.py +0 -0
  81. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/test_numpy.py +0 -0
  82. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/test_operator.py +0 -0
  83. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/test_optuna.py +0 -0
  84. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/test_orjson.py +0 -0
  85. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/test_os.py +0 -0
  86. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/test_parse.py +0 -0
  87. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/test_pathlib.py +0 -0
  88. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/test_period.py +0 -0
  89. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/test_pickle.py +0 -0
  90. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/test_platform.py +0 -0
  91. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/test_polars.py +0 -0
  92. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/test_polars_ols.py +0 -0
  93. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/test_pqdm.py +0 -0
  94. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/test_pydantic.py +0 -0
  95. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/test_pyinstrument.py +0 -0
  96. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/test_pyrsistent.py +0 -0
  97. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/test_pytest.py +0 -0
  98. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/test_pytest_regressions.py +0 -0
  99. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/test_python_dotenv.py +0 -0
  100. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/test_random.py +0 -0
  101. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/test_re.py +0 -0
  102. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/test_redis.py +0 -0
  103. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/test_reprlib.py +0 -0
  104. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/test_rich.py +0 -0
  105. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/test_scipy.py +0 -0
  106. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/test_sentinel.py +0 -0
  107. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/test_shelve.py +0 -0
  108. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/test_slack_sdk.py +0 -0
  109. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/test_socket.py +0 -0
  110. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/test_sqlalchemy.py +0 -0
  111. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/test_sqlalchemy_polars.py +0 -0
  112. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/test_statsmodel.py +0 -0
  113. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/test_streamlit.py +0 -0
  114. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/test_sys.py +0 -0
  115. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/test_tempfile.py +0 -0
  116. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/test_tenacity.py +0 -0
  117. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/test_text.py +0 -0
  118. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/test_threading.py +0 -0
  119. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/test_timer.py +0 -0
  120. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/test_traceback.py +0 -0
  121. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/test_traceback_funcs/__init__.py +0 -0
  122. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/test_traceback_funcs/chain.py +0 -0
  123. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/test_traceback_funcs/decorated_async.py +0 -0
  124. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/test_traceback_funcs/decorated_sync.py +0 -0
  125. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/test_traceback_funcs/error_bind.py +0 -0
  126. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/test_traceback_funcs/many.py +0 -0
  127. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/test_traceback_funcs/one.py +0 -0
  128. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/test_traceback_funcs/recursive.py +0 -0
  129. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/test_traceback_funcs/task_group_one.py +0 -0
  130. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/test_traceback_funcs/task_group_two.py +0 -0
  131. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/test_traceback_funcs/two.py +0 -0
  132. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/test_traceback_funcs/untraced.py +0 -0
  133. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/test_types.py +0 -0
  134. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/test_typing.py +0 -0
  135. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/test_typing_funcs/__init__.py +0 -0
  136. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/test_typing_funcs/no_future.py +0 -0
  137. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/test_typing_funcs/with_future.py +0 -0
  138. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/test_tzdata.py +0 -0
  139. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/test_tzlocal.py +0 -0
  140. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/test_uuid.py +0 -0
  141. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/test_version.py +0 -0
  142. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/test_warnings.py +0 -0
  143. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/test_whenever.py +0 -0
  144. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/test_zipfile.py +0 -0
  145. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/tests/test_zoneinfo.py +0 -0
  146. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/utilities/altair.py +0 -0
  147. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/utilities/astor.py +0 -0
  148. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/utilities/atomicwrites.py +0 -0
  149. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/utilities/atools.py +0 -0
  150. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/utilities/cachetools.py +0 -0
  151. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/utilities/click.py +0 -0
  152. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/utilities/concurrent.py +0 -0
  153. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/utilities/contextlib.py +0 -0
  154. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/utilities/contextvars.py +0 -0
  155. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/utilities/cryptography.py +0 -0
  156. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/utilities/cvxpy.py +0 -0
  157. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/utilities/datetime.py +0 -0
  158. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/utilities/enum.py +0 -0
  159. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/utilities/errors.py +0 -0
  160. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/utilities/eventkit.py +0 -0
  161. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/utilities/fastapi.py +0 -0
  162. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/utilities/fpdf2.py +0 -0
  163. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/utilities/functions.py +0 -0
  164. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/utilities/functools.py +0 -0
  165. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/utilities/getpass.py +0 -0
  166. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/utilities/git.py +0 -0
  167. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/utilities/hashlib.py +0 -0
  168. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/utilities/http.py +0 -0
  169. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/utilities/hypothesis.py +0 -0
  170. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/utilities/ipython.py +0 -0
  171. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/utilities/jupyter.py +0 -0
  172. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/utilities/lightweight_charts.py +0 -0
  173. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/utilities/logging.py +0 -0
  174. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/utilities/loguru.py +0 -0
  175. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/utilities/luigi.py +0 -0
  176. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/utilities/memory_profiler.py +0 -0
  177. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/utilities/modules.py +0 -0
  178. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/utilities/more_itertools.py +0 -0
  179. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/utilities/numpy.py +0 -0
  180. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/utilities/operator.py +0 -0
  181. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/utilities/optuna.py +0 -0
  182. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/utilities/orjson.py +0 -0
  183. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/utilities/os.py +0 -0
  184. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/utilities/parse.py +0 -0
  185. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/utilities/pathlib.py +0 -0
  186. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/utilities/period.py +0 -0
  187. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/utilities/pickle.py +0 -0
  188. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/utilities/platform.py +0 -0
  189. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/utilities/polars.py +0 -0
  190. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/utilities/polars_ols.py +0 -0
  191. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/utilities/pqdm.py +0 -0
  192. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/utilities/py.typed +0 -0
  193. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/utilities/pydantic.py +0 -0
  194. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/utilities/pyinstrument.py +0 -0
  195. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/utilities/pyrsistent.py +0 -0
  196. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/utilities/pytest.py +0 -0
  197. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/utilities/pytest_regressions.py +0 -0
  198. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/utilities/python_dotenv.py +0 -0
  199. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/utilities/random.py +0 -0
  200. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/utilities/re.py +0 -0
  201. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/utilities/redis.py +0 -0
  202. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/utilities/reprlib.py +0 -0
  203. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/utilities/rich.py +0 -0
  204. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/utilities/scipy.py +0 -0
  205. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/utilities/sentinel.py +0 -0
  206. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/utilities/shelve.py +0 -0
  207. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/utilities/slack_sdk.py +0 -0
  208. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/utilities/socket.py +0 -0
  209. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/utilities/sqlalchemy.py +0 -0
  210. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/utilities/sqlalchemy_polars.py +0 -0
  211. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/utilities/statsmodels.py +0 -0
  212. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/utilities/streamlit.py +0 -0
  213. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/utilities/sys.py +0 -0
  214. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/utilities/tempfile.py +0 -0
  215. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/utilities/tenacity.py +0 -0
  216. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/utilities/text.py +0 -0
  217. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/utilities/threading.py +0 -0
  218. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/utilities/timer.py +0 -0
  219. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/utilities/traceback.py +0 -0
  220. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/utilities/typing.py +0 -0
  221. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/utilities/tzdata.py +0 -0
  222. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/utilities/tzlocal.py +0 -0
  223. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/utilities/uuid.py +0 -0
  224. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/utilities/version.py +0 -0
  225. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/utilities/warnings.py +0 -0
  226. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/utilities/whenever.py +0 -0
  227. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/utilities/zipfile.py +0 -0
  228. {dycw_utilities-0.113.0 → dycw_utilities-0.113.2}/src/utilities/zoneinfo.py +0 -0
@@ -1,12 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dycw-utilities
3
- Version: 0.113.0
3
+ Version: 0.113.2
4
4
  Author-email: Derek Wan <d.wan@icloud.com>
5
5
  License-File: LICENSE
6
6
  Requires-Python: >=3.12
7
7
  Requires-Dist: typing-extensions<4.14,>=4.13.1
8
8
  Provides-Extra: test
9
- Requires-Dist: hypothesis<6.132,>=6.131.9; extra == 'test'
9
+ Requires-Dist: hypothesis<6.132,>=6.131.10; extra == 'test'
10
10
  Requires-Dist: pytest-asyncio<0.27,>=0.26.0; extra == 'test'
11
11
  Requires-Dist: pytest-cov<6.2,>=6.1.1; extra == 'test'
12
12
  Requires-Dist: pytest-instafail<0.6,>=0.5.0; extra == 'test'
@@ -16,7 +16,7 @@ Requires-Dist: pytest-randomly<3.17,>=3.16.0; extra == 'test'
16
16
  Requires-Dist: pytest-regressions<2.8,>=2.7.0; extra == 'test'
17
17
  Requires-Dist: pytest-rerunfailures<16,>=15.0; extra == 'test'
18
18
  Requires-Dist: pytest-rng<1.1,>=1.0.0; extra == 'test'
19
- Requires-Dist: pytest-timeout<2.4,>=2.3.1; extra == 'test'
19
+ Requires-Dist: pytest-timeout<2.5,>=2.4.0; extra == 'test'
20
20
  Requires-Dist: pytest-xdist<3.7,>=3.6.1; extra == 'test'
21
21
  Requires-Dist: pytest<8.4,>=8.3.5; extra == 'test'
22
22
  Requires-Dist: setuptools>=78.0.2; extra == 'test'
@@ -80,7 +80,7 @@ Provides-Extra: zzz-test-hypothesis
80
80
  Requires-Dist: aiosqlite<0.22,>=0.21.0; extra == 'zzz-test-hypothesis'
81
81
  Requires-Dist: asyncpg<0.31,>=0.30.0; extra == 'zzz-test-hypothesis'
82
82
  Requires-Dist: greenlet<3.3,>=3.2.0; extra == 'zzz-test-hypothesis'
83
- Requires-Dist: hypothesis<6.132,>=6.131.9; extra == 'zzz-test-hypothesis'
83
+ Requires-Dist: hypothesis<6.132,>=6.131.10; extra == 'zzz-test-hypothesis'
84
84
  Requires-Dist: luigi<3.7,>=3.6.0; extra == 'zzz-test-hypothesis'
85
85
  Requires-Dist: numpy<2.3,>=2.2.5; extra == 'zzz-test-hypothesis'
86
86
  Requires-Dist: pathvalidate<3.3,>=3.2.3; extra == 'zzz-test-hypothesis'
@@ -26,7 +26,7 @@ dev = [
26
26
  "fpdf2 >= 2.8.3, < 2.9",
27
27
  "greenlet >= 3.2.0, < 3.3", # for sqlalchemy async
28
28
  "httpx >= 0.28.1, < 0.29", # for fastapi
29
- "hypothesis >= 6.131.9, < 6.132",
29
+ "hypothesis >= 6.131.10, < 6.132",
30
30
  "img2pdf >= 0.6.0, < 0.7",
31
31
  "lightweight-charts >= 2.1, < 2.2",
32
32
  "loguru >= 0.7.3, < 0.8",
@@ -76,7 +76,7 @@ dev = [
76
76
  "pytest-randomly >= 3.16.0, < 3.17",
77
77
  "pytest-rerunfailures >= 15.0, < 16",
78
78
  "pytest-rng >= 1.0.0, < 1.1",
79
- "pytest-timeout >= 2.3.1, < 2.4",
79
+ "pytest-timeout >= 2.4.0, < 2.5",
80
80
  "pytest-xdist >= 3.6.1, < 3.7",
81
81
  # CI
82
82
  "win32_setctime >= 1.2.0, < 1.3", # https://github.com/Delgan/loguru/issues/147
@@ -92,11 +92,11 @@ dependencies = [
92
92
  name = "dycw-utilities"
93
93
  readme = "README.md"
94
94
  requires-python = ">= 3.12"
95
- version = "0.113.0"
95
+ version = "0.113.2"
96
96
 
97
97
  [project.optional-dependencies]
98
98
  test = [
99
- "hypothesis >= 6.131.9, < 6.132",
99
+ "hypothesis >= 6.131.10, < 6.132",
100
100
  "pytest >= 8.3.5, < 8.4",
101
101
  "pytest-asyncio >= 0.26.0, < 0.27",
102
102
  "pytest-cov >= 6.1.1, < 6.2",
@@ -107,7 +107,7 @@ test = [
107
107
  "pytest-regressions >= 2.7.0, < 2.8",
108
108
  "pytest-rerunfailures >= 15.0, < 16",
109
109
  "pytest-rng >= 1.0.0, < 1.1",
110
- "pytest-timeout >= 2.3.1, < 2.4",
110
+ "pytest-timeout >= 2.4.0, < 2.5",
111
111
  "pytest-xdist >= 3.6.1, < 3.7",
112
112
  "setuptools >= 78.0.2", # https://github.com/theY4Kman/pytest-only/issues/14
113
113
  ]
@@ -174,7 +174,7 @@ zzz-test-hypothesis = [
174
174
  "aiosqlite >= 0.21.0, < 0.22",
175
175
  "asyncpg >= 0.30.0, < 0.31", # for sqlalchemy async
176
176
  "greenlet >= 3.2.0, < 3.3", # for sqlalchemy async
177
- "hypothesis >= 6.131.9, < 6.132",
177
+ "hypothesis >= 6.131.10, < 6.132",
178
178
  "luigi >= 3.6.0, < 3.7",
179
179
  "numpy >= 2.2.5, < 2.3",
180
180
  "pathvalidate >= 3.2.3, < 3.3",
@@ -334,7 +334,7 @@ zzz-test-zoneinfo = [
334
334
  # bump-my-version
335
335
  [tool.bumpversion]
336
336
  allow_dirty = true
337
- current_version = "0.113.0"
337
+ current_version = "0.113.2"
338
338
 
339
339
  [[tool.bumpversion.files]]
340
340
  filename = "src/utilities/__init__.py"
@@ -2,6 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  from asyncio import (
4
4
  CancelledError,
5
+ Event,
5
6
  Lock,
6
7
  PriorityQueue,
7
8
  Queue,
@@ -14,7 +15,7 @@ from collections import Counter
14
15
  from dataclasses import dataclass, field
15
16
  from itertools import chain
16
17
  from re import search
17
- from typing import TYPE_CHECKING, override
18
+ from typing import TYPE_CHECKING, Self, override
18
19
 
19
20
  from hypothesis import Phase, given, settings
20
21
  from hypothesis.strategies import (
@@ -37,20 +38,23 @@ from utilities.asyncio import (
37
38
  QueueProcessor,
38
39
  UniquePriorityQueue,
39
40
  UniqueQueue,
41
+ get_event,
40
42
  get_items,
41
43
  get_items_nowait,
42
44
  sleep_dur,
43
45
  stream_command,
44
46
  timeout_dur,
45
47
  )
48
+ from utilities.dataclasses import replace_non_sentinel
46
49
  from utilities.datetime import MILLISECOND, datetime_duration_to_timedelta
47
- from utilities.hypothesis import text_ascii
50
+ from utilities.hypothesis import sentinels, text_ascii
48
51
  from utilities.iterables import one, unique_everseen
49
52
  from utilities.pytest import skipif_windows
53
+ from utilities.sentinel import Sentinel, sentinel
50
54
  from utilities.timer import Timer
51
55
 
52
56
  if TYPE_CHECKING:
53
- from utilities.types import Duration
57
+ from utilities.types import Duration, MaybeCallableEvent
54
58
 
55
59
 
56
60
  class TestAsyncLoopingService:
@@ -297,6 +301,37 @@ class TestExceptionProcessor:
297
301
  await sleep(0.1)
298
302
 
299
303
 
304
+ class TestGetEvent:
305
+ def test_event(self) -> None:
306
+ event = Event()
307
+ assert get_event(event=event) is event
308
+
309
+ @given(event=none() | sentinels())
310
+ def test_none_or_sentinel(self, *, event: None | Sentinel) -> None:
311
+ assert get_event(event=event) is event
312
+
313
+ def test_replace_non_sentinel(self) -> None:
314
+ @dataclass(kw_only=True, slots=True)
315
+ class Example:
316
+ event: Event = field(default_factory=Event)
317
+
318
+ def replace(
319
+ self, *, event: MaybeCallableEvent | Sentinel = sentinel
320
+ ) -> Self:
321
+ return replace_non_sentinel(self, event=get_event(event=event))
322
+
323
+ event1, event2, event3 = Event(), Event(), Event()
324
+ obj = Example(event=event1)
325
+ assert obj.event is event1
326
+ assert obj.replace().event is event1
327
+ assert obj.replace(event=event2).event is event2
328
+ assert obj.replace(event=lambda: event3).event is event3
329
+
330
+ def test_callable(self) -> None:
331
+ event = Event()
332
+ assert get_event(event=lambda: event) is event
333
+
334
+
300
335
  class TestGetItems:
301
336
  @given(
302
337
  xs=lists(integers(), min_size=1),
@@ -1,12 +1,21 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from dataclasses import dataclass, field
4
+ from functools import total_ordering
4
5
  from pathlib import Path
5
6
  from types import NoneType
6
- from typing import Any, Literal, cast, override
7
+ from typing import Any, Literal, Self, cast, override
7
8
 
8
9
  from hypothesis import given
9
- from hypothesis.strategies import booleans, integers, lists, sampled_from
10
+ from hypothesis.strategies import (
11
+ DataObject,
12
+ booleans,
13
+ data,
14
+ integers,
15
+ lists,
16
+ permutations,
17
+ sampled_from,
18
+ )
10
19
  from polars import DataFrame
11
20
  from pytest import raises
12
21
 
@@ -53,6 +62,7 @@ from utilities.dataclasses import (
53
62
  _YieldFieldsInstance,
54
63
  dataclass_repr,
55
64
  dataclass_to_dict,
65
+ is_nullable_lt,
56
66
  mapping_to_dataclass,
57
67
  one_field,
58
68
  parse_dataclass,
@@ -272,6 +282,25 @@ class TestDataClassToDictAndDataClassRepr:
272
282
  assert repr_res == repr_exp
273
283
 
274
284
 
285
+ class TestIsNullableLT:
286
+ @given(data=data())
287
+ def test_main(self, *, data: DataObject) -> None:
288
+ @dataclass(kw_only=True)
289
+ @total_ordering
290
+ class Example:
291
+ x: int | None = None
292
+
293
+ def __lt__(self, other: Self) -> bool:
294
+ if (cmp := is_nullable_lt(self.x, other.x)) is not None:
295
+ return cmp
296
+ return False
297
+
298
+ obj_none, obj1, obj2 = [Example(x=x) for x in [None, 1, 2]]
299
+ expected = [obj_none, obj_none, obj1, obj1, obj2, obj2]
300
+ result = sorted(data.draw(permutations(expected)))
301
+ assert result == expected
302
+
303
+
275
304
  class TestMappingToDataClass:
276
305
  @given(key=sampled_from(["int_", "INT_"]), int_=integers())
277
306
  def test_exact_match_case_insensitive(self, *, key: str, int_: int) -> None:
@@ -3,6 +3,7 @@ from __future__ import annotations
3
3
  import re
4
4
  from dataclasses import dataclass, replace
5
5
  from enum import Enum, auto
6
+ from functools import cmp_to_key
6
7
  from itertools import chain, repeat
7
8
  from math import isfinite, isinf, isnan, nan
8
9
  from operator import add, neg, sub
@@ -92,6 +93,7 @@ from utilities.iterables import (
92
93
  check_superset,
93
94
  check_unique_modulo_case,
94
95
  chunked,
96
+ cmp_nullable,
95
97
  ensure_hashables,
96
98
  ensure_iterable,
97
99
  ensure_iterable_not_str,
@@ -613,6 +615,28 @@ class TestChunked:
613
615
  assert result == expected
614
616
 
615
617
 
618
+ class TestCmpNullable:
619
+ @given(
620
+ data=data(),
621
+ case=sampled_from([
622
+ ([None, None], [None, None]),
623
+ ([1, None], [None, 1]),
624
+ ([1, None, None], [None, None, 1]),
625
+ ([2, 1, None], [None, 1, 2]),
626
+ ([2, 1, None, None], [None, None, 1, 2]),
627
+ ]),
628
+ )
629
+ def test_main(
630
+ self,
631
+ *,
632
+ data: DataObject,
633
+ case: tuple[Sequence[int | None], Sequence[int | None]],
634
+ ) -> None:
635
+ values, expected = case
636
+ result = sorted(data.draw(permutations(values)), key=cmp_to_key(cmp_nullable))
637
+ assert result == expected
638
+
639
+
616
640
  @dataclass(unsafe_hash=True, slots=True)
617
641
  class _Item:
618
642
  n: int
@@ -1,3 +1,3 @@
1
1
  from __future__ import annotations
2
2
 
3
- __version__ = "0.113.0"
3
+ __version__ = "0.113.2"
@@ -17,6 +17,7 @@ from asyncio import (
17
17
  sleep,
18
18
  timeout,
19
19
  )
20
+ from collections.abc import Callable
20
21
  from contextlib import (
21
22
  AsyncExitStack,
22
23
  _AsyncGeneratorContextManager,
@@ -27,12 +28,22 @@ from dataclasses import dataclass, field
27
28
  from io import StringIO
28
29
  from subprocess import PIPE
29
30
  from sys import stderr, stdout
30
- from typing import TYPE_CHECKING, Generic, Self, TextIO, TypeVar, override
31
+ from typing import (
32
+ TYPE_CHECKING,
33
+ Generic,
34
+ Self,
35
+ TextIO,
36
+ TypeVar,
37
+ assert_never,
38
+ overload,
39
+ override,
40
+ )
31
41
 
32
42
  from utilities.datetime import MILLISECOND, datetime_duration_to_float
33
43
  from utilities.errors import ImpossibleCaseError
34
44
  from utilities.functions import ensure_int, ensure_not_none
35
- from utilities.types import THashable, TSupportsRichComparison
45
+ from utilities.sentinel import Sentinel, sentinel
46
+ from utilities.types import MaybeCallableEvent, THashable, TSupportsRichComparison
36
47
 
37
48
  if TYPE_CHECKING:
38
49
  from asyncio import _CoroutineLike
@@ -356,6 +367,34 @@ class UniqueQueue(Queue[THashable]):
356
367
  ##
357
368
 
358
369
 
370
+ @overload
371
+ def get_event(*, event: MaybeCallableEvent) -> Event: ...
372
+ @overload
373
+ def get_event(*, event: None) -> None: ...
374
+ @overload
375
+ def get_event(*, event: Sentinel) -> Sentinel: ...
376
+ @overload
377
+ def get_event(*, event: MaybeCallableEvent | Sentinel) -> Event | Sentinel: ...
378
+ @overload
379
+ def get_event(
380
+ *, event: MaybeCallableEvent | None | Sentinel = sentinel
381
+ ) -> Event | None | Sentinel: ...
382
+ def get_event(
383
+ *, event: MaybeCallableEvent | None | Sentinel = sentinel
384
+ ) -> Event | None | Sentinel:
385
+ """Get the event."""
386
+ match event:
387
+ case Event() | None | Sentinel():
388
+ return event
389
+ case Callable() as func:
390
+ return get_event(event=func())
391
+ case _ as never:
392
+ assert_never(never)
393
+
394
+
395
+ ##
396
+
397
+
359
398
  async def get_items(
360
399
  queue: Queue[_T], /, *, max_size: int | None = None, lock: Lock | None = None
361
400
  ) -> list[_T]:
@@ -490,6 +529,7 @@ __all__ = [
490
529
  "StreamCommandOutput",
491
530
  "UniquePriorityQueue",
492
531
  "UniqueQueue",
532
+ "get_event",
493
533
  "get_items",
494
534
  "get_items_nowait",
495
535
  "sleep_dur",
@@ -20,7 +20,12 @@ from utilities.functions import (
20
20
  is_dataclass_class,
21
21
  is_dataclass_instance,
22
22
  )
23
- from utilities.iterables import OneStrEmptyError, OneStrNonUniqueError, one_str
23
+ from utilities.iterables import (
24
+ OneStrEmptyError,
25
+ OneStrNonUniqueError,
26
+ cmp_nullable,
27
+ one_str,
28
+ )
24
29
  from utilities.operator import is_equal
25
30
  from utilities.parse import (
26
31
  _ParseObjectExtraNonUniqueError,
@@ -43,6 +48,7 @@ from utilities.types import (
43
48
  SerializeObjectExtra,
44
49
  StrStrMapping,
45
50
  TDataclass,
51
+ TSupportsLT,
46
52
  )
47
53
  from utilities.typing import get_type_hints
48
54
 
@@ -215,6 +221,22 @@ def dataclass_to_dict(
215
221
  ##
216
222
 
217
223
 
224
+ def is_nullable_lt(x: TSupportsLT | None, y: TSupportsLT | None, /) -> bool | None:
225
+ """Compare two nullable fields."""
226
+ match cmp_nullable(x, y):
227
+ case 1:
228
+ return False
229
+ case -1:
230
+ return True
231
+ case 0:
232
+ return None
233
+ case _ as never:
234
+ assert_never(never)
235
+
236
+
237
+ ##
238
+
239
+
218
240
  def mapping_to_dataclass(
219
241
  cls: type[TDataclass],
220
242
  mapping: StrMapping,
@@ -1056,6 +1078,7 @@ __all__ = [
1056
1078
  "YieldFieldsError",
1057
1079
  "dataclass_repr",
1058
1080
  "dataclass_to_dict",
1081
+ "is_nullable_lt",
1059
1082
  "mapping_to_dataclass",
1060
1083
  "one_field",
1061
1084
  "parse_dataclass",
@@ -45,7 +45,7 @@ from utilities.math import (
45
45
  )
46
46
  from utilities.reprlib import get_repr
47
47
  from utilities.sentinel import Sentinel, sentinel
48
- from utilities.types import THashable, THashable2, TSupportsAdd
48
+ from utilities.types import Sign, THashable, THashable2, TSupportsAdd, TSupportsLT
49
49
  from utilities.zoneinfo import UTC
50
50
 
51
51
  if TYPE_CHECKING:
@@ -696,6 +696,24 @@ class _CheckUniqueModuloCaseDuplicateLowerCaseStringsError(CheckUniqueModuloCase
696
696
  ##
697
697
 
698
698
 
699
+ def cmp_nullable(x: TSupportsLT | None, y: TSupportsLT | None, /) -> Sign:
700
+ """Compare two nullable objects."""
701
+ match x, y:
702
+ case None, None:
703
+ return 0
704
+ case None, _:
705
+ return -1
706
+ case _, None:
707
+ return 1
708
+ case _, _:
709
+ return cast("Sign", (x > y) - (x < y))
710
+ case _ as never:
711
+ assert_never(never)
712
+
713
+
714
+ ##
715
+
716
+
699
717
  def chunked(iterable: Iterable[_T], n: int, /) -> Iterator[Sequence[_T]]:
700
718
  """Break an iterable into lists of length n."""
701
719
  return iter(partial(take, n, iter(iterable)), [])
@@ -1293,7 +1311,7 @@ def sort_iterable(iterable: Iterable[_T], /) -> list[_T]:
1293
1311
  return sorted(iterable, key=cmp_to_key(_sort_iterable_cmp))
1294
1312
 
1295
1313
 
1296
- def _sort_iterable_cmp(x: Any, y: Any, /) -> Literal[-1, 0, 1]:
1314
+ def _sort_iterable_cmp(x: Any, y: Any, /) -> Sign:
1297
1315
  """Compare two quantities."""
1298
1316
  if type(x) is not type(y):
1299
1317
  x_qualname = type(x).__qualname__
@@ -1318,7 +1336,7 @@ def _sort_iterable_cmp(x: Any, y: Any, /) -> Literal[-1, 0, 1]:
1318
1336
  return _sort_iterable_cmp_floats(x, y)
1319
1337
  if isinstance(x, str): # else Sequence
1320
1338
  y = cast("str", y)
1321
- return cast("Literal[-1, 0, 1]", (x > y) - (x < y))
1339
+ return cast("Sign", (x > y) - (x < y))
1322
1340
 
1323
1341
  # collections
1324
1342
  if isinstance(x, Sized):
@@ -1333,14 +1351,14 @@ def _sort_iterable_cmp(x: Any, y: Any, /) -> Literal[-1, 0, 1]:
1333
1351
  return _sort_iterable_cmp(sort_iterable(x), sort_iterable(y))
1334
1352
  if isinstance(x, Sequence):
1335
1353
  y = cast("Sequence[Any]", y)
1336
- it: Iterable[Literal[-1, 0, 1]] = (
1354
+ it: Iterable[Sign] = (
1337
1355
  _sort_iterable_cmp(x_i, y_i) for x_i, y_i in zip(x, y, strict=True)
1338
1356
  )
1339
1357
  with suppress(StopIteration):
1340
1358
  return next(r for r in it if r != 0)
1341
1359
 
1342
1360
  try:
1343
- return cast("Literal[-1, 0, 1]", (x > y) - (x < y))
1361
+ return cast("Sign", (x > y) - (x < y))
1344
1362
  except TypeError:
1345
1363
  raise SortIterableError(x=x, y=y) from None
1346
1364
 
@@ -1355,13 +1373,11 @@ class SortIterableError(Exception):
1355
1373
  return f"Unable to sort {get_repr(self.x)} and {get_repr(self.y)}"
1356
1374
 
1357
1375
 
1358
- def _sort_iterable_cmp_datetimes(
1359
- x: dt.datetime, y: dt.datetime, /
1360
- ) -> Literal[-1, 0, 1]:
1376
+ def _sort_iterable_cmp_datetimes(x: dt.datetime, y: dt.datetime, /) -> Sign:
1361
1377
  """Compare two datetimes."""
1362
1378
  match x.tzinfo, y.tzinfo:
1363
1379
  case None, None:
1364
- return cast("Literal[-1, 0, 1]", (x > y) - (x < y))
1380
+ return cast("Sign", (x > y) - (x < y))
1365
1381
  case dt.tzinfo(), None:
1366
1382
  return 1
1367
1383
  case None, dt.tzinfo():
@@ -1369,20 +1385,19 @@ def _sort_iterable_cmp_datetimes(
1369
1385
  case dt.tzinfo(), dt.tzinfo():
1370
1386
  x_utc = x.astimezone(tz=UTC)
1371
1387
  y_utc = y.astimezone(tz=UTC)
1372
- result = cast("Literal[-1, 0, 1]", (x_utc > y_utc) - (x_utc < y_utc))
1388
+ result = cast("Sign", (x_utc > y_utc) - (x_utc < y_utc))
1373
1389
  if result != 0:
1374
1390
  return result
1375
1391
  x_time_zone = ensure_not_none(ensure_not_none(x.tzinfo).tzname(x))
1376
1392
  y_time_zone = ensure_not_none(ensure_not_none(y.tzinfo).tzname(y))
1377
1393
  return cast(
1378
- "Literal[-1, 0, 1]",
1379
- (x_time_zone > y_time_zone) - (x_time_zone < y_time_zone),
1394
+ "Sign", (x_time_zone > y_time_zone) - (x_time_zone < y_time_zone)
1380
1395
  )
1381
1396
  case _ as never:
1382
1397
  assert_never(never)
1383
1398
 
1384
1399
 
1385
- def _sort_iterable_cmp_floats(x: float, y: float, /) -> Literal[-1, 0, 1]:
1400
+ def _sort_iterable_cmp_floats(x: float, y: float, /) -> Sign:
1386
1401
  """Compare two floats."""
1387
1402
  x_nan, y_nan = map(isnan, [x, y])
1388
1403
  match x_nan, y_nan:
@@ -1393,7 +1408,7 @@ def _sort_iterable_cmp_floats(x: float, y: float, /) -> Literal[-1, 0, 1]:
1393
1408
  case False, True:
1394
1409
  return -1
1395
1410
  case False, False:
1396
- return cast("Literal[-1, 0, 1]", (x > y) - (x < y))
1411
+ return cast("Sign", (x > y) - (x < y))
1397
1412
  case _ as never:
1398
1413
  assert_never(never)
1399
1414
 
@@ -1517,6 +1532,7 @@ __all__ = [
1517
1532
  "check_superset",
1518
1533
  "check_unique_modulo_case",
1519
1534
  "chunked",
1535
+ "cmp_nullable",
1520
1536
  "ensure_hashables",
1521
1537
  "ensure_iterable",
1522
1538
  "ensure_iterable_not_str",
@@ -10,7 +10,7 @@ from typing import TYPE_CHECKING, Literal, assert_never, overload, override
10
10
  from utilities.errors import ImpossibleCaseError
11
11
 
12
12
  if TYPE_CHECKING:
13
- from utilities.types import Number, RoundMode
13
+ from utilities.types import Number, RoundMode, Sign
14
14
 
15
15
 
16
16
  MIN_FLOAT32, MAX_FLOAT32 = -3.4028234663852886e38, 3.4028234663852886e38
@@ -860,7 +860,7 @@ class SafeRoundError(Exception):
860
860
 
861
861
  def sign(
862
862
  x: float, /, *, rel_tol: float | None = None, abs_tol: float | None = None
863
- ) -> Literal[-1, 0, 1]:
863
+ ) -> Sign:
864
864
  """Get the sign of an integer/float."""
865
865
  match x:
866
866
  case int():
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import datetime as dt
4
+ from asyncio import Event
4
5
  from collections.abc import Awaitable, Callable, Coroutine, Hashable, Iterable, Mapping
5
6
  from enum import Enum
6
7
  from logging import Logger
@@ -57,6 +58,7 @@ type TupleOrStrMapping = tuple[Any, ...] | StrMapping
57
58
  # asyncio
58
59
  type Coroutine1[_T] = Coroutine[Any, Any, _T]
59
60
  type MaybeAwaitable[_T] = _T | Awaitable[_T]
61
+ type MaybeCallableEvent = MaybeCallable[Event]
60
62
  type MaybeCoroutine1[_T] = _T | Coroutine1[_T]
61
63
 
62
64
 
@@ -140,6 +142,7 @@ type RoundMode = Literal[
140
142
  "standard-tie-toward-zero",
141
143
  "standard-tie-away-zero",
142
144
  ]
145
+ type Sign = Literal[-1, 0, 1]
143
146
 
144
147
 
145
148
  # operator
@@ -276,6 +279,7 @@ __all__ = [
276
279
  "MaybeCallable",
277
280
  "MaybeCallableDate",
278
281
  "MaybeCallableDateTime",
282
+ "MaybeCallableEvent",
279
283
  "MaybeCoroutine1",
280
284
  "MaybeIterable",
281
285
  "MaybeIterableHashable",
@@ -291,6 +295,7 @@ __all__ = [
291
295
  "RoundMode",
292
296
  "Seed",
293
297
  "SerializeObjectExtra",
298
+ "Sign",
294
299
  "StrMapping",
295
300
  "StrStrMapping",
296
301
  "SupportsAbs",