dycw-utilities 0.112.7__tar.gz → 0.112.9__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.112.7 → dycw_utilities-0.112.9}/PKG-INFO +5 -5
  2. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/pyproject.toml +9 -9
  3. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/test_redis.py +73 -12
  4. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/test_text.py +30 -5
  5. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/utilities/__init__.py +1 -1
  6. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/utilities/redis.py +107 -39
  7. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/utilities/text.py +2 -2
  8. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/.gitignore +0 -0
  9. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/LICENSE +0 -0
  10. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/README.md +0 -0
  11. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/__init__.py +0 -0
  12. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/conftest.py +0 -0
  13. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/modules/__init__.py +0 -0
  14. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/modules/package_missing/__init__.py +0 -0
  15. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/modules/package_missing/module.py +0 -0
  16. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/modules/package_with/__init__.py +0 -0
  17. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/modules/package_with/outer_1.py +0 -0
  18. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/modules/package_with/outer_2.py +0 -0
  19. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/modules/package_with/subpackage/__init__.py +0 -0
  20. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/modules/package_with/subpackage/inner_1.py +0 -0
  21. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/modules/package_with/subpackage/inner_2.py +0 -0
  22. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/modules/package_with/subpackage/inner_3.py +0 -0
  23. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/modules/package_without/__init__.py +0 -0
  24. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/modules/package_without/module_1.py +0 -0
  25. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/modules/package_without/module_2.py +0 -0
  26. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/modules/standalone.py +0 -0
  27. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/modules/with_imports.py +0 -0
  28. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/regressions/test_pytest_regressions/TestMultipleRegressionFixtures__test_main__obj.json +0 -0
  29. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/regressions/test_pytest_regressions/TestMultipleRegressionFixtures__test_main__series.json +0 -0
  30. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_int.json +0 -0
  31. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_literal__false.json +0 -0
  32. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_literal__true.json +0 -0
  33. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_nested.json +0 -0
  34. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/regressions/test_pytest_regressions/TestPolarsRegressionFixture__test_dataframe.json +0 -0
  35. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/regressions/test_pytest_regressions/TestPolarsRegressionFixture__test_series.json +0 -0
  36. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/scripts/__init__.py +0 -0
  37. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/scripts/test_async_service/__init__.py +0 -0
  38. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/scripts/test_async_service/__main__.py +0 -0
  39. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/scripts/test_async_service/run.sh +0 -0
  40. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/scripts/test_queue_processor/__init__.py +0 -0
  41. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/scripts/test_queue_processor/__main__.py +0 -0
  42. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/scripts/test_queue_processor/run.sh +0 -0
  43. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/test_altair.py +0 -0
  44. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/test_astor.py +0 -0
  45. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/test_asyncio.py +0 -0
  46. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/test_atomicwrites.py +0 -0
  47. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/test_atools.py +0 -0
  48. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/test_cachetools.py +0 -0
  49. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/test_click.py +0 -0
  50. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/test_concurrent.py +0 -0
  51. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/test_contextlib.py +0 -0
  52. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/test_contextvars.py +0 -0
  53. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/test_cryptography.py +0 -0
  54. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/test_cvxpy.py +0 -0
  55. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/test_dataclasses.py +0 -0
  56. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/test_datetime.py +0 -0
  57. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/test_enum.py +0 -0
  58. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/test_errors.py +0 -0
  59. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/test_eventkit.py +0 -0
  60. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/test_fastapi.py +0 -0
  61. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/test_fpdf2.py +0 -0
  62. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/test_functions.py +0 -0
  63. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/test_functools.py +0 -0
  64. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/test_getpass.py +0 -0
  65. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/test_git.py +0 -0
  66. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/test_hashlib.py +0 -0
  67. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/test_http.py +0 -0
  68. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/test_hypothesis.py +0 -0
  69. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/test_ipython.py +0 -0
  70. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/test_iterables.py +0 -0
  71. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/test_jupyter.py +0 -0
  72. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/test_lightweight_charts.py +0 -0
  73. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/test_logging.py +0 -0
  74. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/test_loguru.py +0 -0
  75. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/test_luigi.py +0 -0
  76. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/test_math.py +0 -0
  77. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/test_memory_profiler.py +0 -0
  78. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/test_modules.py +0 -0
  79. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/test_more_itertools.py +0 -0
  80. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/test_numpy.py +0 -0
  81. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/test_operator.py +0 -0
  82. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/test_optuna.py +0 -0
  83. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/test_orjson.py +0 -0
  84. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/test_os.py +0 -0
  85. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/test_parse.py +0 -0
  86. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/test_pathlib.py +0 -0
  87. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/test_period.py +0 -0
  88. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/test_pickle.py +0 -0
  89. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/test_platform.py +0 -0
  90. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/test_polars.py +0 -0
  91. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/test_polars_ols.py +0 -0
  92. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/test_pqdm.py +0 -0
  93. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/test_pydantic.py +0 -0
  94. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/test_pyinstrument.py +0 -0
  95. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/test_pyrsistent.py +0 -0
  96. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/test_pytest.py +0 -0
  97. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/test_pytest_regressions.py +0 -0
  98. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/test_python_dotenv.py +0 -0
  99. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/test_random.py +0 -0
  100. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/test_re.py +0 -0
  101. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/test_reprlib.py +0 -0
  102. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/test_rich.py +0 -0
  103. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/test_scipy.py +0 -0
  104. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/test_sentinel.py +0 -0
  105. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/test_shelve.py +0 -0
  106. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/test_slack_sdk.py +0 -0
  107. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/test_socket.py +0 -0
  108. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/test_sqlalchemy.py +0 -0
  109. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/test_sqlalchemy_polars.py +0 -0
  110. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/test_statsmodel.py +0 -0
  111. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/test_streamlit.py +0 -0
  112. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/test_sys.py +0 -0
  113. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/test_tempfile.py +0 -0
  114. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/test_tenacity.py +0 -0
  115. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/test_threading.py +0 -0
  116. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/test_timer.py +0 -0
  117. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/test_traceback.py +0 -0
  118. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/test_traceback_funcs/__init__.py +0 -0
  119. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/test_traceback_funcs/chain.py +0 -0
  120. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/test_traceback_funcs/decorated_async.py +0 -0
  121. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/test_traceback_funcs/decorated_sync.py +0 -0
  122. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/test_traceback_funcs/error_bind.py +0 -0
  123. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/test_traceback_funcs/many.py +0 -0
  124. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/test_traceback_funcs/one.py +0 -0
  125. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/test_traceback_funcs/recursive.py +0 -0
  126. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/test_traceback_funcs/task_group_one.py +0 -0
  127. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/test_traceback_funcs/task_group_two.py +0 -0
  128. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/test_traceback_funcs/two.py +0 -0
  129. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/test_traceback_funcs/untraced.py +0 -0
  130. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/test_types.py +0 -0
  131. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/test_typing.py +0 -0
  132. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/test_typing_funcs/__init__.py +0 -0
  133. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/test_typing_funcs/no_future.py +0 -0
  134. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/test_typing_funcs/with_future.py +0 -0
  135. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/test_tzdata.py +0 -0
  136. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/test_tzlocal.py +0 -0
  137. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/test_uuid.py +0 -0
  138. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/test_version.py +0 -0
  139. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/test_warnings.py +0 -0
  140. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/test_whenever.py +0 -0
  141. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/test_zipfile.py +0 -0
  142. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/tests/test_zoneinfo.py +0 -0
  143. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/utilities/altair.py +0 -0
  144. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/utilities/astor.py +0 -0
  145. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/utilities/asyncio.py +0 -0
  146. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/utilities/atomicwrites.py +0 -0
  147. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/utilities/atools.py +0 -0
  148. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/utilities/cachetools.py +0 -0
  149. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/utilities/click.py +0 -0
  150. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/utilities/concurrent.py +0 -0
  151. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/utilities/contextlib.py +0 -0
  152. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/utilities/contextvars.py +0 -0
  153. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/utilities/cryptography.py +0 -0
  154. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/utilities/cvxpy.py +0 -0
  155. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/utilities/dataclasses.py +0 -0
  156. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/utilities/datetime.py +0 -0
  157. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/utilities/enum.py +0 -0
  158. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/utilities/errors.py +0 -0
  159. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/utilities/eventkit.py +0 -0
  160. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/utilities/fastapi.py +0 -0
  161. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/utilities/fpdf2.py +0 -0
  162. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/utilities/functions.py +0 -0
  163. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/utilities/functools.py +0 -0
  164. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/utilities/getpass.py +0 -0
  165. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/utilities/git.py +0 -0
  166. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/utilities/hashlib.py +0 -0
  167. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/utilities/http.py +0 -0
  168. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/utilities/hypothesis.py +0 -0
  169. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/utilities/ipython.py +0 -0
  170. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/utilities/iterables.py +0 -0
  171. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/utilities/jupyter.py +0 -0
  172. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/utilities/lightweight_charts.py +0 -0
  173. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/utilities/logging.py +0 -0
  174. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/utilities/loguru.py +0 -0
  175. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/utilities/luigi.py +0 -0
  176. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/utilities/math.py +0 -0
  177. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/utilities/memory_profiler.py +0 -0
  178. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/utilities/modules.py +0 -0
  179. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/utilities/more_itertools.py +0 -0
  180. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/utilities/numpy.py +0 -0
  181. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/utilities/operator.py +0 -0
  182. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/utilities/optuna.py +0 -0
  183. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/utilities/orjson.py +0 -0
  184. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/utilities/os.py +0 -0
  185. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/utilities/parse.py +0 -0
  186. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/utilities/pathlib.py +0 -0
  187. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/utilities/period.py +0 -0
  188. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/utilities/pickle.py +0 -0
  189. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/utilities/platform.py +0 -0
  190. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/utilities/polars.py +0 -0
  191. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/utilities/polars_ols.py +0 -0
  192. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/utilities/pqdm.py +0 -0
  193. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/utilities/py.typed +0 -0
  194. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/utilities/pydantic.py +0 -0
  195. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/utilities/pyinstrument.py +0 -0
  196. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/utilities/pyrsistent.py +0 -0
  197. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/utilities/pytest.py +0 -0
  198. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/utilities/pytest_regressions.py +0 -0
  199. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/utilities/python_dotenv.py +0 -0
  200. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/utilities/random.py +0 -0
  201. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/utilities/re.py +0 -0
  202. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/utilities/reprlib.py +0 -0
  203. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/utilities/rich.py +0 -0
  204. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/utilities/scipy.py +0 -0
  205. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/utilities/sentinel.py +0 -0
  206. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/utilities/shelve.py +0 -0
  207. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/utilities/slack_sdk.py +0 -0
  208. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/utilities/socket.py +0 -0
  209. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/utilities/sqlalchemy.py +0 -0
  210. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/utilities/sqlalchemy_polars.py +0 -0
  211. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/utilities/statsmodels.py +0 -0
  212. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/utilities/streamlit.py +0 -0
  213. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/utilities/sys.py +0 -0
  214. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/utilities/tempfile.py +0 -0
  215. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/utilities/tenacity.py +0 -0
  216. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/utilities/threading.py +0 -0
  217. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/utilities/timer.py +0 -0
  218. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/utilities/traceback.py +0 -0
  219. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/utilities/types.py +0 -0
  220. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/utilities/typing.py +0 -0
  221. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/utilities/tzdata.py +0 -0
  222. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/utilities/tzlocal.py +0 -0
  223. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/utilities/uuid.py +0 -0
  224. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/utilities/version.py +0 -0
  225. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/utilities/warnings.py +0 -0
  226. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/utilities/whenever.py +0 -0
  227. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/utilities/zipfile.py +0 -0
  228. {dycw_utilities-0.112.7 → dycw_utilities-0.112.9}/src/utilities/zoneinfo.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dycw-utilities
3
- Version: 0.112.7
3
+ Version: 0.112.9
4
4
  Author-email: Derek Wan <d.wan@icloud.com>
5
5
  License-File: LICENSE
6
6
  Requires-Python: >=3.12
@@ -74,7 +74,7 @@ Requires-Dist: polars-lts-cpu<1.29,>=1.28.1; extra == 'zzz-test-hashlib'
74
74
  Requires-Dist: whenever<0.8,>=0.7.3; extra == 'zzz-test-hashlib'
75
75
  Provides-Extra: zzz-test-http
76
76
  Requires-Dist: atomicwrites<1.5,>=1.4.1; extra == 'zzz-test-http'
77
- Requires-Dist: orjson<3.11,>=3.10.16; extra == 'zzz-test-http'
77
+ Requires-Dist: orjson<3.11,>=3.10.18; extra == 'zzz-test-http'
78
78
  Requires-Dist: whenever<0.8,>=0.7.3; extra == 'zzz-test-http'
79
79
  Provides-Extra: zzz-test-hypothesis
80
80
  Requires-Dist: aiosqlite<0.22,>=0.21.0; extra == 'zzz-test-hypothesis'
@@ -144,7 +144,7 @@ Provides-Extra: zzz-test-pqdm
144
144
  Requires-Dist: pqdm<0.3,>=0.2.0; extra == 'zzz-test-pqdm'
145
145
  Provides-Extra: zzz-test-pydantic
146
146
  Requires-Dist: atomicwrites<1.5,>=1.4.1; extra == 'zzz-test-pydantic'
147
- Requires-Dist: pydantic<2.12,>=2.11.2; extra == 'zzz-test-pydantic'
147
+ Requires-Dist: pydantic<2.12,>=2.11.4; extra == 'zzz-test-pydantic'
148
148
  Provides-Extra: zzz-test-pyinstrument
149
149
  Requires-Dist: atomicwrites<1.5,>=1.4.1; extra == 'zzz-test-pyinstrument'
150
150
  Requires-Dist: pyinstrument<5.1,>=5.0.0; extra == 'zzz-test-pyinstrument'
@@ -153,7 +153,7 @@ Provides-Extra: zzz-test-pyrsistent
153
153
  Requires-Dist: pyrsistent<0.21,>=0.20.0; extra == 'zzz-test-pyrsistent'
154
154
  Provides-Extra: zzz-test-pytest
155
155
  Requires-Dist: atomicwrites<1.5,>=1.4.1; extra == 'zzz-test-pytest'
156
- Requires-Dist: orjson<3.11,>=3.10.16; extra == 'zzz-test-pytest'
156
+ Requires-Dist: orjson<3.11,>=3.10.18; extra == 'zzz-test-pytest'
157
157
  Requires-Dist: whenever<0.8,>=0.7.3; extra == 'zzz-test-pytest'
158
158
  Provides-Extra: zzz-test-pytest-regressions
159
159
  Requires-Dist: pytest-regressions<2.8,>=2.7.0; extra == 'zzz-test-pytest-regressions'
@@ -197,7 +197,7 @@ Requires-Dist: sqlalchemy<2.1,>=2.0.40; extra == 'zzz-test-sqlalchemy-polars'
197
197
  Requires-Dist: tenacity<9.0,>=8.5.0; extra == 'zzz-test-sqlalchemy-polars'
198
198
  Requires-Dist: whenever<0.8,>=0.7.3; extra == 'zzz-test-sqlalchemy-polars'
199
199
  Provides-Extra: zzz-test-streamlit
200
- Requires-Dist: streamlit<1.45,>=1.44.1; extra == 'zzz-test-streamlit'
200
+ Requires-Dist: streamlit<1.46,>=1.45.0; extra == 'zzz-test-streamlit'
201
201
  Provides-Extra: zzz-test-sys
202
202
  Requires-Dist: atomicwrites<1.5,>=1.4.1; extra == 'zzz-test-sys'
203
203
  Requires-Dist: rich<14.1,>=14.0.0; extra == 'zzz-test-sys'
@@ -38,13 +38,13 @@ dev = [
38
38
  "nox >= 2025.2.9, < 2025.3",
39
39
  "numpy >= 2.2.5, < 2.3",
40
40
  "optuna >= 4.3.0, < 4.4",
41
- "orjson >= 3.10.16, < 3.11",
41
+ "orjson >= 3.10.18, < 3.11",
42
42
  "pathvalidate >= 3.2.3, < 3.3",
43
43
  "polars-lts-cpu >= 1.28.1, < 1.29",
44
44
  "polars-ols >= 0.3.5, < 0.4",
45
45
  "pqdm >= 0.2.0, < 0.3",
46
46
  "psycopg2-binary >= 2.9.10, < 2.10", # for sqlalchemy
47
- "pydantic >= 2.11.2, < 2.12",
47
+ "pydantic >= 2.11.4, < 2.12",
48
48
  "pyinstrument >= 5.0.0, < 5.1",
49
49
  "pyrsistent >= 0.20.0, < 0.21",
50
50
  "pytest >= 8.3.5, < 8.4",
@@ -57,7 +57,7 @@ dev = [
57
57
  "slack-sdk >= 3.35.0, < 3.36",
58
58
  "sqlalchemy >= 2.0.40, < 2.1",
59
59
  "statsmodels >= 0.14.4, < 0.15",
60
- "streamlit >= 1.44.1, < 1.45",
60
+ "streamlit >= 1.45.0, < 1.46",
61
61
  "tenacity >= 8.5.0, < 9.0", # limited by luigi
62
62
  "tomlkit >= 0.13.2, < 0.14",
63
63
  "tzdata >= 2025.2, < 2025.3",
@@ -93,7 +93,7 @@ dependencies = [
93
93
  name = "dycw-utilities"
94
94
  readme = "README.md"
95
95
  requires-python = ">= 3.12"
96
- version = "0.112.7"
96
+ version = "0.112.9"
97
97
 
98
98
  [project.optional-dependencies]
99
99
  test = [
@@ -168,7 +168,7 @@ zzz-test-hashlib = [
168
168
  ]
169
169
  zzz-test-http = [
170
170
  "atomicwrites >= 1.4.1, < 1.5",
171
- "orjson >= 3.10.16, < 3.11",
171
+ "orjson >= 3.10.18, < 3.11",
172
172
  "whenever >= 0.7.3, < 0.8",
173
173
  ]
174
174
  zzz-test-hypothesis = [
@@ -238,7 +238,7 @@ zzz-test-polars = [
238
238
  zzz-test-pqdm = ["pqdm >= 0.2.0, < 0.3"]
239
239
  zzz-test-pydantic = [
240
240
  "atomicwrites >= 1.4.1, < 1.5",
241
- "pydantic >= 2.11.2, < 2.12",
241
+ "pydantic >= 2.11.4, < 2.12",
242
242
  ]
243
243
  zzz-test-pyinstrument = [
244
244
  "atomicwrites >= 1.4.1, < 1.5",
@@ -248,7 +248,7 @@ zzz-test-pyinstrument = [
248
248
  zzz-test-pyrsistent = ["pyrsistent >= 0.20.0, < 0.21"]
249
249
  zzz-test-pytest = [
250
250
  "atomicwrites >= 1.4.1, < 1.5",
251
- "orjson >= 3.10.16, < 3.11",
251
+ "orjson >= 3.10.18, < 3.11",
252
252
  "whenever >= 0.7.3, < 0.8",
253
253
  ]
254
254
  zzz-test-pytest-regressions = ["pytest-regressions >= 2.7.0, < 2.8"]
@@ -294,7 +294,7 @@ zzz-test-sqlalchemy-polars = [
294
294
  "tenacity >= 8.5.0, < 9.0", # limited by luigi
295
295
  "whenever >= 0.7.3, < 0.8",
296
296
  ]
297
- zzz-test-streamlit = ["streamlit >= 1.44.1, < 1.45"]
297
+ zzz-test-streamlit = ["streamlit >= 1.45.0, < 1.46"]
298
298
  zzz-test-sys = [
299
299
  "atomicwrites >= 1.4.1, < 1.5",
300
300
  "rich >= 14.0.0, < 14.1",
@@ -336,7 +336,7 @@ zzz-test-zoneinfo = [
336
336
  # bump-my-version
337
337
  [tool.bumpversion]
338
338
  allow_dirty = true
339
- current_version = "0.112.7"
339
+ current_version = "0.112.9"
340
340
 
341
341
  [[tool.bumpversion.files]]
342
342
  filename = "src/utilities/__init__.py"
@@ -4,8 +4,15 @@ from asyncio import create_task, get_running_loop, sleep
4
4
  from typing import TYPE_CHECKING, Any
5
5
 
6
6
  from hypothesis import HealthCheck, Phase, given, settings
7
- from hypothesis.strategies import DataObject, booleans, data
8
- from pytest import mark
7
+ from hypothesis.strategies import (
8
+ DataObject,
9
+ booleans,
10
+ data,
11
+ dictionaries,
12
+ lists,
13
+ sampled_from,
14
+ )
15
+ from pytest import mark, raises
9
16
  from redis.asyncio import Redis
10
17
 
11
18
  from tests.conftest import SKIPIF_CI_AND_NOT_LINUX
@@ -30,6 +37,8 @@ from utilities.redis import (
30
37
  from utilities.sentinel import SENTINEL_REPR, Sentinel, sentinel
31
38
 
32
39
  if TYPE_CHECKING:
40
+ from collections.abc import Mapping
41
+
33
42
  from pytest import CaptureFixture
34
43
 
35
44
 
@@ -221,7 +230,6 @@ class TestRedisHashMapKey:
221
230
  ) -> None:
222
231
  async with yield_test_redis(data) as test:
223
232
  hm_key = redis_hash_map_key(test.key, int, bool)
224
- assert await hm_key.get(test.redis, key) is None
225
233
  _ = await hm_key.set(test.redis, key, value)
226
234
  assert await hm_key.get(test.redis, key) is value
227
235
 
@@ -233,7 +241,6 @@ class TestRedisHashMapKey:
233
241
  ) -> None:
234
242
  async with yield_test_redis(data) as test:
235
243
  hm_key = redis_hash_map_key(test.key, (bool, int), bool)
236
- assert await hm_key.get(test.redis, key) is None
237
244
  _ = await hm_key.set(test.redis, key, value)
238
245
  assert await hm_key.get(test.redis, key) is value
239
246
 
@@ -250,7 +257,6 @@ class TestRedisHashMapKey:
250
257
  hm_key = redis_hash_map_key(
251
258
  test.key, Sentinel, bool, key_serializer=serializer
252
259
  )
253
- assert await hm_key.get(test.redis, sentinel) is None
254
260
  _ = await hm_key.set(test.redis, sentinel, value)
255
261
  assert await hm_key.get(test.redis, sentinel) is value
256
262
 
@@ -262,7 +268,6 @@ class TestRedisHashMapKey:
262
268
  ) -> None:
263
269
  async with yield_test_redis(data) as test:
264
270
  hm_key = redis_hash_map_key(test.key, int, (bool, int))
265
- assert await hm_key.get(test.redis, key) is None
266
271
  _ = await hm_key.set(test.redis, key, value)
267
272
  assert await hm_key.get(test.redis, key) == value
268
273
 
@@ -287,10 +292,25 @@ class TestRedisHashMapKey:
287
292
  value_serializer=serializer,
288
293
  value_deserializer=deserializer,
289
294
  )
290
- assert await hm_key.get(test.redis, key) is None
291
295
  _ = await hm_key.set(test.redis, key, sentinel)
292
296
  assert await hm_key.get(test.redis, key) is sentinel
293
297
 
298
+ @given(data=data(), mapping=dictionaries(int64s(), booleans()))
299
+ @settings_with_reduced_examples(phases={Phase.generate})
300
+ @SKIPIF_CI_AND_NOT_LINUX
301
+ async def test_get_and_set_many(
302
+ self, *, data: DataObject, mapping: Mapping[int, bool]
303
+ ) -> None:
304
+ async with yield_test_redis(data) as test:
305
+ hm_key = redis_hash_map_key(test.key, int, bool)
306
+ _ = await hm_key.set_many(test.redis, mapping)
307
+ if len(mapping) == 0:
308
+ keys = []
309
+ else:
310
+ keys = data.draw(lists(sampled_from(list(mapping))))
311
+ expected = [mapping[k] for k in keys]
312
+ assert await hm_key.get_many(test.redis, keys) == expected
313
+
294
314
  @given(data=data(), key=int64s(), value=booleans())
295
315
  @settings_with_reduced_examples(phases={Phase.generate})
296
316
  @SKIPIF_CI_AND_NOT_LINUX
@@ -300,7 +320,8 @@ class TestRedisHashMapKey:
300
320
  _ = await hm_key.set(test.redis, key, value)
301
321
  assert await hm_key.get(test.redis, key) is value
302
322
  _ = await hm_key.delete(test.redis, key)
303
- assert await hm_key.get(test.redis, key) is None
323
+ with raises(KeyError):
324
+ _ = await hm_key.get(test.redis, key)
304
325
 
305
326
  @given(data=data(), key=int64s(), value=booleans())
306
327
  @settings_with_reduced_examples(phases={Phase.generate})
@@ -312,6 +333,37 @@ class TestRedisHashMapKey:
312
333
  _ = await hm_key.set(test.redis, key, value)
313
334
  assert await hm_key.exists(test.redis, key)
314
335
 
336
+ @given(data=data(), mapping=dictionaries(int64s(), booleans()))
337
+ @settings_with_reduced_examples(phases={Phase.generate})
338
+ @SKIPIF_CI_AND_NOT_LINUX
339
+ async def test_get_all(
340
+ self, *, data: DataObject, mapping: Mapping[int, bool]
341
+ ) -> None:
342
+ async with yield_test_redis(data) as test:
343
+ hm_key = redis_hash_map_key(test.key, int, bool)
344
+ _ = await hm_key.set_many(test.redis, mapping)
345
+ assert await hm_key.get_all(test.redis) == mapping
346
+
347
+ @given(data=data(), mapping=dictionaries(int64s(), booleans()))
348
+ @settings_with_reduced_examples(phases={Phase.generate})
349
+ @SKIPIF_CI_AND_NOT_LINUX
350
+ async def test_keys(self, *, data: DataObject, mapping: Mapping[int, bool]) -> None:
351
+ async with yield_test_redis(data) as test:
352
+ hm_key = redis_hash_map_key(test.key, int, bool)
353
+ _ = await hm_key.set_many(test.redis, mapping)
354
+ assert await hm_key.keys(test.redis) == list(mapping)
355
+
356
+ @given(data=data(), mapping=dictionaries(int64s(), booleans()))
357
+ @settings_with_reduced_examples(phases={Phase.generate})
358
+ @SKIPIF_CI_AND_NOT_LINUX
359
+ async def test_length(
360
+ self, *, data: DataObject, mapping: Mapping[int, bool]
361
+ ) -> None:
362
+ async with yield_test_redis(data) as test:
363
+ hm_key = redis_hash_map_key(test.key, int, bool)
364
+ _ = await hm_key.set_many(test.redis, mapping)
365
+ assert await hm_key.length(test.redis) == len(mapping)
366
+
315
367
  @given(data=data(), key=int64s(), value=booleans())
316
368
  @settings(max_examples=1, phases={Phase.generate})
317
369
  @SKIPIF_CI_AND_NOT_LINUX
@@ -324,6 +376,17 @@ class TestRedisHashMapKey:
324
376
  await sleep(0.05)
325
377
  assert not await test.redis.exists(hm_key.name)
326
378
 
379
+ @given(data=data(), mapping=dictionaries(int64s(), booleans()))
380
+ @settings_with_reduced_examples(phases={Phase.generate})
381
+ @SKIPIF_CI_AND_NOT_LINUX
382
+ async def test_values(
383
+ self, *, data: DataObject, mapping: Mapping[int, bool]
384
+ ) -> None:
385
+ async with yield_test_redis(data) as test:
386
+ hm_key = redis_hash_map_key(test.key, int, bool)
387
+ _ = await hm_key.set_many(test.redis, mapping)
388
+ assert await hm_key.values(test.redis) == list(mapping.values())
389
+
327
390
 
328
391
  class TestRedisKey:
329
392
  @given(data=data(), value=booleans())
@@ -332,7 +395,6 @@ class TestRedisKey:
332
395
  async def test_get_and_set_bool(self, *, data: DataObject, value: bool) -> None:
333
396
  async with yield_test_redis(data) as test:
334
397
  key = redis_key(test.key, bool)
335
- assert await key.get(test.redis) is None
336
398
  _ = await key.set(test.redis, value)
337
399
  assert await key.get(test.redis) is value
338
400
 
@@ -344,7 +406,6 @@ class TestRedisKey:
344
406
  ) -> None:
345
407
  async with yield_test_redis(data) as test:
346
408
  key = redis_key(test.key, (bool, int))
347
- assert await key.get(test.redis) is None
348
409
  _ = await key.set(test.redis, value)
349
410
  assert await key.get(test.redis) == value
350
411
 
@@ -365,7 +426,6 @@ class TestRedisKey:
365
426
  key = redis_key(
366
427
  test.key, Sentinel, serializer=serializer, deserializer=deserializer
367
428
  )
368
- assert await key.get(test.redis) is None
369
429
  _ = await key.set(test.redis, sentinel)
370
430
  assert await key.get(test.redis) is sentinel
371
431
 
@@ -378,7 +438,8 @@ class TestRedisKey:
378
438
  _ = await key.set(test.redis, value)
379
439
  assert await key.get(test.redis) is value
380
440
  _ = await key.delete(test.redis)
381
- assert await key.get(test.redis) is None
441
+ with raises(KeyError):
442
+ _ = await key.get(test.redis)
382
443
 
383
444
  @given(data=data(), value=booleans())
384
445
  @settings_with_reduced_examples(phases={Phase.generate})
@@ -1,5 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
+ from itertools import chain
3
4
  from typing import TYPE_CHECKING
4
5
 
5
6
  from hypothesis import given
@@ -44,14 +45,38 @@ if TYPE_CHECKING:
44
45
  class TestParseBool:
45
46
  @given(data=data(), value=booleans())
46
47
  def test_main(self, *, data: DataObject, value: bool) -> None:
47
- text = str(value)
48
- text_use = data.draw(
49
- sampled_from([str(int(value)), text, text.lower(), text.upper()])
48
+ match value:
49
+ case True:
50
+ extra_cased_texts = ["Y", "Yes", "On"]
51
+ case False:
52
+ extra_cased_texts = ["N", "No", "Off"]
53
+ all_cased_texts = list(chain([str(value), str(int(value))], extra_cased_texts))
54
+ all_texts = list(
55
+ chain(
56
+ extra_cased_texts,
57
+ map(str.lower, all_cased_texts),
58
+ map(str.upper, all_cased_texts),
59
+ )
50
60
  )
51
- result = parse_bool(text_use)
61
+ text = data.draw(sampled_from(all_texts))
62
+ result = parse_bool(text)
52
63
  assert result is value
53
64
 
54
- @given(text=sampled_from(["invalid", "11", "00", "ttruee", "ffalsee"]))
65
+ @given(
66
+ text=sampled_from([
67
+ "00",
68
+ "11",
69
+ "ffalsee",
70
+ "invalid",
71
+ "nn",
72
+ "nnoo",
73
+ "oofff",
74
+ "oonn",
75
+ "ttruee",
76
+ "yy",
77
+ "yyess",
78
+ ])
79
+ )
55
80
  def test_error(self, *, text: str) -> None:
56
81
  with raises(ParseBoolError, match="Unable to parse boolean value; got '.*'"):
57
82
  _ = parse_bool(text)
@@ -1,3 +1,3 @@
1
1
  from __future__ import annotations
2
2
 
3
- __version__ = "0.112.7"
3
+ __version__ = "0.112.9"
@@ -31,11 +31,18 @@ from utilities.datetime import (
31
31
  )
32
32
  from utilities.errors import ImpossibleCaseError
33
33
  from utilities.functions import ensure_int
34
- from utilities.iterables import always_iterable
34
+ from utilities.iterables import always_iterable, one
35
35
 
36
36
  if TYPE_CHECKING:
37
37
  import datetime as dt
38
- from collections.abc import AsyncIterator, Awaitable, Callable, Mapping
38
+ from collections.abc import (
39
+ AsyncIterator,
40
+ Awaitable,
41
+ Callable,
42
+ Iterable,
43
+ Mapping,
44
+ Sequence,
45
+ )
39
46
 
40
47
  from redis.asyncio import ConnectionPool
41
48
  from redis.asyncio.client import PubSub
@@ -71,6 +78,7 @@ class RedisHashMapKey(Generic[_K, _V]):
71
78
  name: str
72
79
  key: TypeLike[_K]
73
80
  key_serializer: Callable[[_K], bytes] | None = None
81
+ key_deserializer: Callable[[bytes], _K] | None = None
74
82
  value: TypeLike[_V]
75
83
  value_serializer: Callable[[_V], bytes] | None = None
76
84
  value_deserializer: Callable[[bytes], _V] | None = None
@@ -95,46 +103,102 @@ class RedisHashMapKey(Generic[_K, _V]):
95
103
  "Awaitable[bool]", redis.hexists(self.name, cast("str", key))
96
104
  )
97
105
 
98
- async def get(self, redis: Redis, key: _K, /) -> _V | None:
106
+ async def get(self, redis: Redis, key: _K, /) -> _V:
107
+ """Get a value from a hashmap in `redis`."""
108
+ result = one(await self.get_many(redis, [key])) # skipif-ci-and-not-linux
109
+ if result is None: # skipif-ci-and-not-linux
110
+ raise KeyError(self.name, key)
111
+ return result # skipif-ci-and-not-linux
112
+
113
+ async def get_all(self, redis: Redis, /) -> Mapping[_K, _V]:
99
114
  """Get a value from a hashmap in `redis`."""
100
- ser_key = _serialize( # skipif-ci-and-not-linux
101
- key, serializer=self.key_serializer
102
- )
103
115
  async with timeout_dur( # skipif-ci-and-not-linux
104
116
  duration=self.timeout, error=self.error
105
117
  ):
106
118
  result = await cast( # skipif-ci-and-not-linux
107
- "Awaitable[bytes | None]", redis.hget(self.name, cast("Any", ser_key))
119
+ "Awaitable[Mapping[bytes, bytes]]", redis.hgetall(self.name)
108
120
  )
109
- match result: # skipif-ci-and-not-linux
110
- case None:
111
- return None
112
- case bytes() as data:
113
- return _deserialize(data, deserializer=self.value_deserializer)
114
- case _ as never:
115
- assert_never(never)
121
+ return { # skipif-ci-and-not-linux
122
+ _deserialize(key, deserializer=self.key_deserializer): _deserialize(
123
+ value, deserializer=self.value_deserializer
124
+ )
125
+ for key, value in result.items()
126
+ }
127
+
128
+ async def get_many(
129
+ self, redis: Redis, keys: Iterable[_K], /
130
+ ) -> Sequence[_V | None]:
131
+ """Get multiple values from a hashmap in `redis`."""
132
+ keys = list(keys) # skipif-ci-and-not-linux
133
+ if len(keys) == 0: # skipif-ci-and-not-linux
134
+ return []
135
+ ser = [ # skipif-ci-and-not-linux
136
+ _serialize(key, serializer=self.key_serializer) for key in keys
137
+ ]
138
+ async with timeout_dur( # skipif-ci-and-not-linux
139
+ duration=self.timeout, error=self.error
140
+ ):
141
+ result = await cast( # skipif-ci-and-not-linux
142
+ "Awaitable[Sequence[bytes | None]]", redis.hmget(self.name, ser)
143
+ )
144
+ return [ # skipif-ci-and-not-linux
145
+ None
146
+ if data is None
147
+ else _deserialize(data, deserializer=self.value_deserializer)
148
+ for data in result
149
+ ]
150
+
151
+ async def keys(self, redis: Redis, /) -> Sequence[_K]:
152
+ """Get the keys of a hashmap in `redis`."""
153
+ async with timeout_dur( # skipif-ci-and-not-linux
154
+ duration=self.timeout, error=self.error
155
+ ):
156
+ result = await cast("Awaitable[Sequence[bytes]]", redis.hkeys(self.name))
157
+ return [ # skipif-ci-and-not-linux
158
+ _deserialize(data, deserializer=self.key_deserializer) for data in result
159
+ ]
160
+
161
+ async def length(self, redis: Redis, /) -> int:
162
+ """Get the length of a hashmap in `redis`."""
163
+ async with timeout_dur( # skipif-ci-and-not-linux
164
+ duration=self.timeout, error=self.error
165
+ ):
166
+ return await cast("Awaitable[int]", redis.hlen(self.name))
116
167
 
117
168
  async def set(self, redis: Redis, key: _K, value: _V, /) -> int:
118
169
  """Set a value in a hashmap in `redis`."""
119
- ser_key = _serialize( # skipif-ci-and-not-linux
120
- key, serializer=self.key_serializer
121
- )
122
- ser_value = _serialize( # skipif-ci-and-not-linux
123
- value, serializer=self.value_serializer
124
- )
170
+ return await self.set_many(redis, {key: value}) # skipif-ci-and-not-linux
171
+
172
+ async def set_many(self, redis: Redis, mapping: Mapping[_K, _V], /) -> int:
173
+ """Set multiple value(s) in a hashmap in `redis`."""
174
+ if len(mapping) == 0: # skipif-ci-and-not-linux
175
+ return 0
176
+ ser = { # skipif-ci-and-not-linux
177
+ _serialize(key, serializer=self.key_serializer): _serialize(
178
+ value, serializer=self.value_serializer
179
+ )
180
+ for key, value in mapping.items()
181
+ }
125
182
  async with timeout_dur( # skipif-ci-and-not-linux
126
183
  duration=self.timeout, error=self.error
127
184
  ):
128
185
  result = await cast(
129
- "Awaitable[int]",
130
- redis.hset(
131
- self.name, key=cast("Any", ser_key), value=cast("Any", ser_value)
132
- ),
186
+ "Awaitable[int]", redis.hset(self.name, mapping=cast("Any", ser))
133
187
  )
134
188
  if self.ttl is not None:
135
189
  await redis.pexpire(self.name, datetime_duration_to_timedelta(self.ttl))
136
190
  return result # skipif-ci-and-not-linux
137
191
 
192
+ async def values(self, redis: Redis, /) -> Sequence[_V]:
193
+ """Get the values of a hashmap in `redis`."""
194
+ async with timeout_dur( # skipif-ci-and-not-linux
195
+ duration=self.timeout, error=self.error
196
+ ):
197
+ result = await cast("Awaitable[Sequence[bytes]]", redis.hvals(self.name))
198
+ return [ # skipif-ci-and-not-linux
199
+ _deserialize(data, deserializer=self.value_deserializer) for data in result
200
+ ]
201
+
138
202
 
139
203
  @overload
140
204
  def redis_hash_map_key(
@@ -144,6 +208,7 @@ def redis_hash_map_key(
144
208
  /,
145
209
  *,
146
210
  key_serializer: Callable[[_K], bytes] | None = None,
211
+ key_deserializer: Callable[[bytes], Any] | None = None,
147
212
  value_serializer: Callable[[_V], bytes] | None = None,
148
213
  value_deserializer: Callable[[bytes], _V] | None = None,
149
214
  timeout: Duration | None = None,
@@ -158,6 +223,7 @@ def redis_hash_map_key(
158
223
  /,
159
224
  *,
160
225
  key_serializer: Callable[[_K], bytes] | None = None,
226
+ key_deserializer: Callable[[bytes], Any] | None = None,
161
227
  value_serializer: Callable[[_V1 | _V2], bytes] | None = None,
162
228
  value_deserializer: Callable[[bytes], _V1 | _V2] | None = None,
163
229
  timeout: Duration | None = None,
@@ -172,6 +238,7 @@ def redis_hash_map_key(
172
238
  /,
173
239
  *,
174
240
  key_serializer: Callable[[_K], bytes] | None = None,
241
+ key_deserializer: Callable[[bytes], Any] | None = None,
175
242
  value_serializer: Callable[[_V1 | _V2 | _V3], bytes] | None = None,
176
243
  value_deserializer: Callable[[bytes], _V1 | _V2 | _V3] | None = None,
177
244
  timeout: Duration | None = None,
@@ -186,6 +253,7 @@ def redis_hash_map_key(
186
253
  /,
187
254
  *,
188
255
  key_serializer: Callable[[_K1 | _K2], bytes] | None = None,
256
+ key_deserializer: Callable[[bytes], Any] | None = None,
189
257
  value_serializer: Callable[[_V], bytes] | None = None,
190
258
  value_deserializer: Callable[[bytes], _V] | None = None,
191
259
  timeout: Duration | None = None,
@@ -200,6 +268,7 @@ def redis_hash_map_key(
200
268
  /,
201
269
  *,
202
270
  key_serializer: Callable[[_K1 | _K2], bytes] | None = None,
271
+ key_deserializer: Callable[[bytes], Any] | None = None,
203
272
  value_serializer: Callable[[_V1 | _V2], bytes] | None = None,
204
273
  value_deserializer: Callable[[bytes], _V1 | _V2] | None = None,
205
274
  timeout: Duration | None = None,
@@ -214,6 +283,7 @@ def redis_hash_map_key(
214
283
  /,
215
284
  *,
216
285
  key_serializer: Callable[[_K1 | _K2], bytes] | None = None,
286
+ key_deserializer: Callable[[bytes], Any] | None = None,
217
287
  value_serializer: Callable[[_V1 | _V2 | _V3], bytes] | None = None,
218
288
  value_deserializer: Callable[[bytes], _V1 | _V2 | _V3] | None = None,
219
289
  timeout: Duration | None = None,
@@ -228,6 +298,7 @@ def redis_hash_map_key(
228
298
  /,
229
299
  *,
230
300
  key_serializer: Callable[[_K1 | _K2 | _K3], bytes] | None = None,
301
+ key_deserializer: Callable[[bytes], Any] | None = None,
231
302
  value_serializer: Callable[[_V], bytes] | None = None,
232
303
  value_deserializer: Callable[[bytes], _V] | None = None,
233
304
  timeout: Duration | None = None,
@@ -242,6 +313,7 @@ def redis_hash_map_key(
242
313
  /,
243
314
  *,
244
315
  key_serializer: Callable[[_K1 | _K2 | _K3], bytes] | None = None,
316
+ key_deserializer: Callable[[bytes], Any] | None = None,
245
317
  value_serializer: Callable[[_V1 | _V2], bytes] | None = None,
246
318
  value_deserializer: Callable[[bytes], _V1 | _V2] | None = None,
247
319
  timeout: Duration | None = None,
@@ -256,6 +328,7 @@ def redis_hash_map_key(
256
328
  /,
257
329
  *,
258
330
  key_serializer: Callable[[_K1 | _K2 | _K3], bytes] | None = None,
331
+ key_deserializer: Callable[[bytes], Any] | None = None,
259
332
  value_serializer: Callable[[_V1 | _V2 | _V3], bytes] | None = None,
260
333
  value_deserializer: Callable[[bytes], _V1 | _V2 | _V3] | None = None,
261
334
  timeout: Duration | None = None,
@@ -270,6 +343,7 @@ def redis_hash_map_key(
270
343
  /,
271
344
  *,
272
345
  key_serializer: Callable[[_K1 | _K2 | _K3], bytes] | None = None,
346
+ key_deserializer: Callable[[bytes], Any] | None = None,
273
347
  value_serializer: Callable[[_V1 | _V2 | _V3], bytes] | None = None,
274
348
  value_deserializer: Callable[[bytes], _V1 | _V2 | _V3] | None = None,
275
349
  timeout: Duration | None = None,
@@ -283,6 +357,7 @@ def redis_hash_map_key(
283
357
  /,
284
358
  *,
285
359
  key_serializer: Callable[[Any], bytes] | None = None,
360
+ key_deserializer: Callable[[bytes], Any] | None = None,
286
361
  value_serializer: Callable[[Any], bytes] | None = None,
287
362
  value_deserializer: Callable[[bytes], Any] | None = None,
288
363
  timeout: Duration | None = None,
@@ -294,6 +369,7 @@ def redis_hash_map_key(
294
369
  name=name,
295
370
  key=key,
296
371
  key_serializer=key_serializer,
372
+ key_deserializer=key_deserializer,
297
373
  value=value,
298
374
  value_serializer=value_serializer,
299
375
  value_deserializer=value_deserializer,
@@ -343,23 +419,15 @@ class RedisKey(Generic[_T]):
343
419
  duration=self.timeout, error=self.error
344
420
  ):
345
421
  result = cast("bytes | None", await redis.get(self.name))
346
- match result: # skipif-ci-and-not-linux
347
- case None:
348
- return None
349
- case bytes() as data:
350
- if self.deserializer is None:
351
- from utilities.orjson import deserialize
352
-
353
- return deserialize(data)
354
- return self.deserializer(data)
355
- case _ as never:
356
- assert_never(never)
422
+ if result is None: # skipif-ci-and-not-linux
423
+ raise KeyError(self.name)
424
+ return _deserialize( # skipif-ci-and-not-linux
425
+ result, deserializer=self.deserializer
426
+ )
357
427
 
358
428
  async def set(self, redis: Redis, value: _T, /) -> int:
359
429
  """Set a value in `redis`."""
360
- ser_value = _serialize( # skipif-ci-and-not-linux
361
- value, serializer=self.serializer
362
- )
430
+ ser = _serialize(value, serializer=self.serializer) # skipif-ci-and-not-linux
363
431
  ttl = ( # skipif-ci-and-not-linux
364
432
  None
365
433
  if self.ttl is None
@@ -369,7 +437,7 @@ class RedisKey(Generic[_T]):
369
437
  duration=self.timeout, error=self.error
370
438
  ):
371
439
  result = await redis.set( # skipif-ci-and-not-linux
372
- self.name, ser_value, px=ttl
440
+ self.name, ser, px=ttl
373
441
  )
374
442
  return ensure_int(result) # skipif-ci-and-not-linux
375
443
 
@@ -25,9 +25,9 @@ DEFAULT_SEPARATOR = ","
25
25
 
26
26
  def parse_bool(text: str, /) -> bool:
27
27
  """Parse text into a boolean value."""
28
- if search(r"^(0|False)$", text, flags=IGNORECASE):
28
+ if search(r"^(0|False|N|No|Off)$", text, flags=IGNORECASE):
29
29
  return False
30
- if search(r"^(1|True)$", text, flags=IGNORECASE):
30
+ if search(r"^(1|True|Y|Yes|On)$", text, flags=IGNORECASE):
31
31
  return True
32
32
  raise ParseBoolError(text=text)
33
33