dycw-utilities 0.162.4__tar.gz → 0.162.6__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 (211) hide show
  1. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/PKG-INFO +3 -2
  2. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/pyproject.toml +9 -5
  3. dycw_utilities-0.162.6/src/tests/test_testbook.py +38 -0
  4. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/test_text.py +120 -110
  5. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/utilities/__init__.py +1 -1
  6. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/utilities/hypothesis.py +1 -1
  7. dycw_utilities-0.162.6/src/utilities/testbook.py +50 -0
  8. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/utilities/text.py +33 -18
  9. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/.gitignore +0 -0
  10. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/LICENSE +0 -0
  11. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/README.md +0 -0
  12. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/__init__.py +0 -0
  13. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/conftest.py +0 -0
  14. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/modules/__init__.py +0 -0
  15. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/modules/package_missing/__init__.py +0 -0
  16. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/modules/package_missing/module.py +0 -0
  17. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/modules/package_with/__init__.py +0 -0
  18. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/modules/package_with/outer_1.py +0 -0
  19. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/modules/package_with/outer_2.py +0 -0
  20. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/modules/package_with/subpackage/__init__.py +0 -0
  21. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/modules/package_with/subpackage/inner_1.py +0 -0
  22. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/modules/package_with/subpackage/inner_2.py +0 -0
  23. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/modules/package_with/subpackage/inner_3.py +0 -0
  24. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/modules/package_without/__init__.py +0 -0
  25. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/modules/package_without/module_1.py +0 -0
  26. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/modules/package_without/module_2.py +0 -0
  27. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/modules/standalone.py +0 -0
  28. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/modules/with_imports.py +0 -0
  29. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/regressions/test_pytest_regressions/TestMultipleRegressionFixtures__test_main__obj.json +0 -0
  30. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/regressions/test_pytest_regressions/TestMultipleRegressionFixtures__test_main__series.json +0 -0
  31. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_int.json +0 -0
  32. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_literal__false.json +0 -0
  33. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_literal__true.json +0 -0
  34. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_nested.json +0 -0
  35. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/regressions/test_pytest_regressions/TestPolarsRegressionFixture__test_dataframe.json +0 -0
  36. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/regressions/test_pytest_regressions/TestPolarsRegressionFixture__test_series.json +0 -0
  37. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/test_aeventkit.py +0 -0
  38. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/test_altair.py +0 -0
  39. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/test_asyncio.py +0 -0
  40. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/test_atomicwrites.py +0 -0
  41. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/test_atools.py +0 -0
  42. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/test_cachetools.py +0 -0
  43. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/test_click.py +0 -0
  44. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/test_concurrent.py +0 -0
  45. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/test_contextlib.py +0 -0
  46. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/test_contextvars.py +0 -0
  47. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/test_cryptography.py +0 -0
  48. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/test_cvxpy.py +0 -0
  49. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/test_dataclasses.py +0 -0
  50. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/test_enum.py +0 -0
  51. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/test_errors.py +0 -0
  52. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/test_fastapi.py +0 -0
  53. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/test_fpdf2.py +0 -0
  54. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/test_functions.py +0 -0
  55. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/test_functools.py +0 -0
  56. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/test_getpass.py +0 -0
  57. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/test_gzip.py +0 -0
  58. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/test_hashlib.py +0 -0
  59. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/test_http.py +0 -0
  60. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/test_hypothesis.py +0 -0
  61. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/test_importlib.py +0 -0
  62. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/test_inflect.py +0 -0
  63. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/test_ipython.py +0 -0
  64. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/test_iterables.py +0 -0
  65. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/test_json.py +0 -0
  66. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/test_jupyter.py +0 -0
  67. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/test_libcst.py +0 -0
  68. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/test_lightweight_charts.py +0 -0
  69. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/test_logging.py +0 -0
  70. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/test_math.py +0 -0
  71. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/test_memory_profiler.py +0 -0
  72. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/test_modules.py +0 -0
  73. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/test_more_itertools.py +0 -0
  74. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/test_numpy.py +0 -0
  75. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/test_objects/__init__.py +0 -0
  76. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/test_objects/objects.py +0 -0
  77. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/test_operator.py +0 -0
  78. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/test_optuna.py +0 -0
  79. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/test_orjson.py +0 -0
  80. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/test_os.py +0 -0
  81. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/test_parse.py +0 -0
  82. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/test_pathlib.py +0 -0
  83. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/test_pickle.py +0 -0
  84. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/test_platform.py +0 -0
  85. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/test_polars.py +0 -0
  86. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/test_polars_ols.py +0 -0
  87. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/test_postgres.py +0 -0
  88. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/test_pottery.py +0 -0
  89. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/test_pqdm.py +0 -0
  90. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/test_psutil.py +0 -0
  91. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/test_pyinstrument.py +0 -0
  92. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/test_pytest.py +0 -0
  93. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/test_pytest_randomly.py +0 -0
  94. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/test_pytest_regressions.py +0 -0
  95. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/test_random.py +0 -0
  96. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/test_re.py +0 -0
  97. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/test_redis.py +0 -0
  98. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/test_reprlib.py +0 -0
  99. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/test_scipy.py +0 -0
  100. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/test_sentinel.py +0 -0
  101. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/test_shelve.py +0 -0
  102. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/test_slack_sdk.py +0 -0
  103. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/test_socket.py +0 -0
  104. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/test_sqlalchemy.py +0 -0
  105. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/test_sqlalchemy_polars.py +0 -0
  106. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/test_statsmodels.py +0 -0
  107. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/test_string.py +0 -0
  108. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/test_tempfile.py +0 -0
  109. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/test_threading.py +0 -0
  110. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/test_timer.py +0 -0
  111. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/test_traceback.py +0 -0
  112. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/test_typed_settings.py +0 -0
  113. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/test_types.py +0 -0
  114. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/test_typing.py +0 -0
  115. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/test_typing_funcs/__init__.py +0 -0
  116. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/test_typing_funcs/no_future.py +0 -0
  117. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/test_typing_funcs/with_future.py +0 -0
  118. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/test_tzdata.py +0 -0
  119. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/test_tzlocal.py +0 -0
  120. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/test_uuid.py +0 -0
  121. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/test_version.py +0 -0
  122. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/test_warnings.py +0 -0
  123. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/test_whenever.py +0 -0
  124. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/test_zipfile.py +0 -0
  125. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/tests/test_zoneinfo.py +0 -0
  126. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/utilities/aeventkit.py +0 -0
  127. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/utilities/altair.py +0 -0
  128. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/utilities/asyncio.py +0 -0
  129. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/utilities/atomicwrites.py +0 -0
  130. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/utilities/atools.py +0 -0
  131. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/utilities/cachetools.py +0 -0
  132. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/utilities/click.py +0 -0
  133. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/utilities/concurrent.py +0 -0
  134. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/utilities/contextlib.py +0 -0
  135. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/utilities/contextvars.py +0 -0
  136. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/utilities/cryptography.py +0 -0
  137. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/utilities/cvxpy.py +0 -0
  138. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/utilities/dataclasses.py +0 -0
  139. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/utilities/enum.py +0 -0
  140. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/utilities/errors.py +0 -0
  141. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/utilities/fastapi.py +0 -0
  142. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/utilities/fpdf2.py +0 -0
  143. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/utilities/functions.py +0 -0
  144. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/utilities/functools.py +0 -0
  145. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/utilities/getpass.py +0 -0
  146. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/utilities/gzip.py +0 -0
  147. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/utilities/hashlib.py +0 -0
  148. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/utilities/http.py +0 -0
  149. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/utilities/importlib.py +0 -0
  150. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/utilities/inflect.py +0 -0
  151. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/utilities/ipython.py +0 -0
  152. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/utilities/iterables.py +0 -0
  153. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/utilities/json.py +0 -0
  154. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/utilities/jupyter.py +0 -0
  155. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/utilities/libcst.py +0 -0
  156. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/utilities/lightweight_charts.py +0 -0
  157. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/utilities/logging.py +0 -0
  158. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/utilities/math.py +0 -0
  159. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/utilities/memory_profiler.py +0 -0
  160. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/utilities/modules.py +0 -0
  161. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/utilities/more_itertools.py +0 -0
  162. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/utilities/numpy.py +0 -0
  163. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/utilities/operator.py +0 -0
  164. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/utilities/optuna.py +0 -0
  165. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/utilities/orjson.py +0 -0
  166. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/utilities/os.py +0 -0
  167. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/utilities/parse.py +0 -0
  168. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/utilities/pathlib.py +0 -0
  169. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/utilities/pickle.py +0 -0
  170. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/utilities/platform.py +0 -0
  171. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/utilities/polars.py +0 -0
  172. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/utilities/polars_ols.py +0 -0
  173. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/utilities/postgres.py +0 -0
  174. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/utilities/pottery.py +0 -0
  175. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/utilities/pqdm.py +0 -0
  176. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/utilities/psutil.py +0 -0
  177. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/utilities/py.typed +0 -0
  178. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/utilities/pyinstrument.py +0 -0
  179. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/utilities/pytest.py +0 -0
  180. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/utilities/pytest_plugins/__init__.py +0 -0
  181. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/utilities/pytest_plugins/pytest_randomly.py +0 -0
  182. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/utilities/pytest_plugins/pytest_regressions.py +0 -0
  183. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/utilities/pytest_regressions.py +0 -0
  184. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/utilities/random.py +0 -0
  185. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/utilities/re.py +0 -0
  186. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/utilities/redis.py +0 -0
  187. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/utilities/reprlib.py +0 -0
  188. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/utilities/scipy.py +0 -0
  189. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/utilities/sentinel.py +0 -0
  190. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/utilities/shelve.py +0 -0
  191. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/utilities/slack_sdk.py +0 -0
  192. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/utilities/socket.py +0 -0
  193. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/utilities/sqlalchemy.py +0 -0
  194. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/utilities/sqlalchemy_polars.py +0 -0
  195. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/utilities/statsmodels.py +0 -0
  196. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/utilities/string.py +0 -0
  197. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/utilities/tempfile.py +0 -0
  198. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/utilities/threading.py +0 -0
  199. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/utilities/timer.py +0 -0
  200. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/utilities/traceback.py +0 -0
  201. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/utilities/typed_settings.py +0 -0
  202. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/utilities/types.py +0 -0
  203. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/utilities/typing.py +0 -0
  204. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/utilities/tzdata.py +0 -0
  205. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/utilities/tzlocal.py +0 -0
  206. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/utilities/uuid.py +0 -0
  207. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/utilities/version.py +0 -0
  208. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/utilities/warnings.py +0 -0
  209. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/utilities/whenever.py +0 -0
  210. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/utilities/zipfile.py +0 -0
  211. {dycw_utilities-0.162.4 → dycw_utilities-0.162.6}/src/utilities/zoneinfo.py +0 -0
@@ -1,13 +1,13 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dycw-utilities
3
- Version: 0.162.4
3
+ Version: 0.162.6
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: atomicwrites<1.5,>=1.4.1
8
8
  Requires-Dist: typing-extensions<4.15,>=4.14.0
9
9
  Requires-Dist: tzlocal<5.4,>=5.3.1
10
- Requires-Dist: whenever<0.9,>=0.8.7
10
+ Requires-Dist: whenever<0.9,>=0.8.8
11
11
  Provides-Extra: logging
12
12
  Requires-Dist: coloredlogs<15.1,>=15.0.1; extra == 'logging'
13
13
  Provides-Extra: test
@@ -25,6 +25,7 @@ Requires-Dist: pytest-rng<1.1,>=1.0.0; extra == 'test'
25
25
  Requires-Dist: pytest-timeout<2.5,>=2.4.0; extra == 'test'
26
26
  Requires-Dist: pytest-xdist<3.9,>=3.8.0; extra == 'test'
27
27
  Requires-Dist: pytest<8.5,>=8.4.1; extra == 'test'
28
+ Requires-Dist: testbook<0.5,>=0.4.2; extra == 'test'
28
29
  Description-Content-Type: text/markdown
29
30
 
30
31
  [![PyPI version](https://badge.fury.io/py/dycw-utilities.svg)](https://badge.fury.io/py/dycw-utilities)
@@ -31,7 +31,7 @@ core = [
31
31
  "atomicwrites >=1.4.1, <1.5",
32
32
  "typing-extensions >=4.14.1, <4.15",
33
33
  "tzlocal >=5.3.1, <5.4",
34
- "whenever >=0.8.7, <0.9",
34
+ "whenever >=0.8.8, <0.9",
35
35
  ]
36
36
  cryptography = [
37
37
  "cryptography >=45.0.4, <45.1",
@@ -208,6 +208,9 @@ sqlalchemy-test = [
208
208
  statsmodels = [
209
209
  "statsmodels >=0.14.4, <0.15",
210
210
  ]
211
+ testbook = [
212
+ "testbook >=0.4.2, <0.5",
213
+ ]
211
214
  typed-settings = [
212
215
  "typed-settings >=25.0.0, <25.1",
213
216
  ]
@@ -222,12 +225,12 @@ dependencies = [
222
225
  "atomicwrites >=1.4.1, <1.5",
223
226
  "typing-extensions >=4.14.0, <4.15",
224
227
  "tzlocal >=5.3.1, <5.4",
225
- "whenever >=0.8.7, <0.9",
228
+ "whenever >=0.8.8, <0.9",
226
229
  ]
227
230
  name = "dycw-utilities"
228
231
  readme = "README.md"
229
232
  requires-python = ">= 3.12"
230
- version = "0.162.4"
233
+ version = "0.162.6"
231
234
 
232
235
  [project.entry-points.pytest11]
233
236
  pytest-randomly = "utilities.pytest_plugins.pytest_randomly"
@@ -252,6 +255,7 @@ test = [
252
255
  "pytest-rng >=1.0.0, <1.1",
253
256
  "pytest-timeout >=2.4.0, <2.5",
254
257
  "pytest-xdist >=3.8.0, <3.9",
258
+ "testbook >=0.4.2, <0.5",
255
259
  ]
256
260
 
257
261
  [project.scripts]
@@ -259,7 +263,7 @@ test = [
259
263
  # bump-my-version
260
264
  [tool.bumpversion]
261
265
  allow_dirty = true
262
- current_version = "0.162.4"
266
+ current_version = "0.162.6"
263
267
 
264
268
  [[tool.bumpversion.files]]
265
269
  filename = "src/utilities/__init__.py"
@@ -373,9 +377,9 @@ filterwarnings = [
373
377
  "error",
374
378
  "ignore:Exception ignored in.* <coroutine object .* at .*>:pytest.PytestUnraisableExceptionWarning",
375
379
  "ignore:Exception in thread Thread-.*:pytest.PytestUnhandledThreadExceptionWarning",
380
+ "ignore:Jupyter is migrating its paths to use standard platformdirs:DeprecationWarning", # jupyter
376
381
  "ignore:ResourceTracker called reentrantly for resource cleanup, which is unsupported:UserWarning",
377
382
  "ignore:The garbage collector is trying to clean up non-checked-in connection <AdaptedConnection <Connection(.*)>:RuntimeWarning", # sqlalchemy
378
- "ignore:There is no current event loop:DeprecationWarning", # eventkit
379
383
  "ignore:Using fork.* can cause Polars to deadlock in the child process:RuntimeWarning", # polars/pqdm
380
384
  "ignore:coroutine 'AsyncConnection.close' was never awaited:RuntimeWarning",
381
385
  "ignore:loop is closed:ResourceWarning", # redis
@@ -0,0 +1,38 @@
1
+ from __future__ import annotations
2
+
3
+ from json import dumps
4
+ from re import search
5
+ from typing import TYPE_CHECKING, ClassVar
6
+
7
+ from pytest import mark, param
8
+
9
+ from utilities.functions import get_class_name
10
+ from utilities.testbook import build_notebook_tester
11
+ from utilities.whenever import HOUR
12
+
13
+ if TYPE_CHECKING:
14
+ from pathlib import Path
15
+
16
+ from utilities.types import Delta
17
+
18
+
19
+ class TestBuildNotebookTester:
20
+ text: ClassVar[str] = dumps({"cells": []})
21
+
22
+ def test_main(self, *, tmp_path: Path) -> None:
23
+ mapping = {
24
+ "notebook": "test_notebook",
25
+ "notebook_with_underscores": "test_notebook_with_underscores",
26
+ "notebook-with-dashes": "test_notebook_with_dashes",
27
+ }
28
+ for stem in mapping:
29
+ _ = tmp_path.joinpath(f"{stem}.ipynb").write_text(self.text)
30
+ tester = build_notebook_tester(tmp_path)
31
+ assert search(r"^TestTestMain\d+$", get_class_name(tester))
32
+ for name in mapping.values():
33
+ assert hasattr(tester, name)
34
+
35
+ @mark.parametrize("throttle", [param(HOUR), param(None)])
36
+ def test_throttle(self, *, tmp_path: Path, throttle: Delta | None) -> None:
37
+ _ = tmp_path.joinpath("notebook.ipynb").write_text(self.text)
38
+ _ = build_notebook_tester(tmp_path, throttle=throttle)
@@ -30,6 +30,7 @@ from utilities.text import (
30
30
  join_strs,
31
31
  parse_bool,
32
32
  parse_none,
33
+ pascal_case,
33
34
  repr_encode,
34
35
  secret_str,
35
36
  snake_case,
@@ -43,8 +44,6 @@ from utilities.text import (
43
44
  )
44
45
 
45
46
  if TYPE_CHECKING:
46
- from collections.abc import Sequence
47
-
48
47
  from utilities.sentinel import Sentinel
49
48
 
50
49
 
@@ -68,20 +67,21 @@ class TestParseBool:
68
67
  result = parse_bool(text)
69
68
  assert result is value
70
69
 
71
- @given(
72
- text=sampled_from([
73
- "00",
74
- "11",
75
- "ffalsee",
76
- "invalid",
77
- "nn",
78
- "nnoo",
79
- "oofff",
80
- "oonn",
81
- "ttruee",
82
- "yy",
83
- "yyess",
84
- ])
70
+ @mark.parametrize(
71
+ "text",
72
+ [
73
+ param("00"),
74
+ param("11"),
75
+ param("ffalsee"),
76
+ param("invalid"),
77
+ param("nn"),
78
+ param("nnoo"),
79
+ param("oofff"),
80
+ param("oonn"),
81
+ param("ttruee"),
82
+ param("yy"),
83
+ param("yyess"),
84
+ ],
85
85
  )
86
86
  def test_error(self, *, text: str) -> None:
87
87
  with raises(ParseBoolError, match="Unable to parse boolean value; got '.*'"):
@@ -96,7 +96,7 @@ class TestParseNone:
96
96
  result = parse_none(text_use)
97
97
  assert result is None
98
98
 
99
- @given(text=sampled_from(["invalid", "nnonee"]))
99
+ @mark.parametrize("text", [param("invalid"), param("nnonee")])
100
100
  def test_error(self, *, text: str) -> None:
101
101
  with raises(ParseNoneError, match="Unable to parse null value; got '.*'"):
102
102
  _ = parse_none(text)
@@ -110,6 +110,62 @@ class TestReprEncode:
110
110
  assert result == expected
111
111
 
112
112
 
113
+ class TestPascalAndSnakeCase:
114
+ @mark.parametrize(
115
+ ("text", "exp_pascal", "exp_snake"),
116
+ [
117
+ param("API", "API", "api"),
118
+ param("APIResponse", "APIResponse", "api_response"),
119
+ param(
120
+ "ApplicationController",
121
+ "ApplicationController",
122
+ "application_controller",
123
+ ),
124
+ param("Area51Controller", "Area51Controller", "area51_controller"),
125
+ param("FreeBSD", "FreeBSD", "free_bsd"),
126
+ param("HTML", "HTML", "html"),
127
+ param("HTMLTidy", "HTMLTidy", "html_tidy"),
128
+ param("HTMLTidyGenerator", "HTMLTidyGenerator", "html_tidy_generator"),
129
+ param("HTMLVersion", "HTMLVersion", "html_version"),
130
+ param("NoHTML", "NoHTML", "no_html"),
131
+ param("One Two", "OneTwo", "one_two"),
132
+ param("One Two", "OneTwo", "one_two"),
133
+ param("One Two", "OneTwo", "one_two"),
134
+ param("OneTwo", "OneTwo", "one_two"),
135
+ param("One_Two", "OneTwo", "one_two"),
136
+ param("One__Two", "OneTwo", "one_two"),
137
+ param("One___Two", "OneTwo", "one_two"),
138
+ param("Product", "Product", "product"),
139
+ param("SpecialGuest", "SpecialGuest", "special_guest"),
140
+ param("Text", "Text", "text"),
141
+ param("Text123", "Text123", "text123"),
142
+ param("Text123Text456", "Text123Text456", "text123_text456"),
143
+ param("_APIResponse_", "APIResponse", "_api_response_"),
144
+ param("_API_", "API", "_api_"),
145
+ param("__APIResponse__", "APIResponse", "_api_response_"),
146
+ param("__API__", "API", "_api_"),
147
+ param("__impliedVolatility_", "ImpliedVolatility", "_implied_volatility_"),
148
+ param("_itemID", "ItemID", "_item_id"),
149
+ param("_lastPrice__", "LastPrice", "_last_price_"),
150
+ param("_symbol", "Symbol", "_symbol"),
151
+ param("aB", "AB", "a_b"),
152
+ param("changePct", "ChangePct", "change_pct"),
153
+ param("changePct_", "ChangePct", "change_pct_"),
154
+ param("impliedVolatility", "ImpliedVolatility", "implied_volatility"),
155
+ param("lastPrice", "LastPrice", "last_price"),
156
+ param("memMB", "MemMB", "mem_mb"),
157
+ param("sizeX", "SizeX", "size_x"),
158
+ param("symbol", "Symbol", "symbol"),
159
+ param("testNTest", "TestNTest", "test_n_test"),
160
+ param("text", "Text", "text"),
161
+ param("text123", "Text123", "text123"),
162
+ ],
163
+ )
164
+ def test_main(self, *, text: str, exp_pascal: str, exp_snake: str) -> None:
165
+ assert pascal_case(text) == exp_pascal
166
+ assert snake_case(text) == exp_snake
167
+
168
+
113
169
  class TestSecretStr:
114
170
  def test_main(self) -> None:
115
171
  s = secret_str("text")
@@ -124,74 +180,26 @@ class TestSecretStr:
124
180
  assert str(s.str) == "text"
125
181
 
126
182
 
127
- class TestSnakeCase:
128
- @given(
129
- case=sampled_from([
130
- ("API", "api"),
131
- ("APIResponse", "api_response"),
132
- ("ApplicationController", "application_controller"),
133
- ("Area51Controller", "area51_controller"),
134
- ("FreeBSD", "free_bsd"),
135
- ("HTML", "html"),
136
- ("HTMLTidy", "html_tidy"),
137
- ("HTMLTidyGenerator", "html_tidy_generator"),
138
- ("HTMLVersion", "html_version"),
139
- ("NoHTML", "no_html"),
140
- ("One Two", "one_two"),
141
- ("One Two", "one_two"),
142
- ("One Two", "one_two"),
143
- ("OneTwo", "one_two"),
144
- ("One_Two", "one_two"),
145
- ("One__Two", "one_two"),
146
- ("One___Two", "one_two"),
147
- ("Product", "product"),
148
- ("SpecialGuest", "special_guest"),
149
- ("Text", "text"),
150
- ("Text123", "text123"),
151
- ("_APIResponse_", "_api_response_"),
152
- ("_API_", "_api_"),
153
- ("__APIResponse__", "_api_response_"),
154
- ("__API__", "_api_"),
155
- ("__impliedVolatility_", "_implied_volatility_"),
156
- ("_itemID", "_item_id"),
157
- ("_lastPrice__", "_last_price_"),
158
- ("_symbol", "_symbol"),
159
- ("aB", "a_b"),
160
- ("changePct", "change_pct"),
161
- ("changePct_", "change_pct_"),
162
- ("impliedVolatility", "implied_volatility"),
163
- ("lastPrice", "last_price"),
164
- ("memMB", "mem_mb"),
165
- ("sizeX", "size_x"),
166
- ("symbol", "symbol"),
167
- ("testNTest", "test_n_test"),
168
- ("text", "text"),
169
- ("text123", "text123"),
170
- ])
171
- )
172
- def test_main(self, *, case: tuple[str, str]) -> None:
173
- text, expected = case
174
- result = snake_case(text)
175
- assert result == expected
176
-
177
-
178
183
  class TestSplitKeyValuePairs:
179
- @given(
180
- case=sampled_from([
181
- ("", []),
182
- ("a=1", [("a", "1")]),
183
- ("a=1,b=22", [("a", "1"), ("b", "22")]),
184
- ("a=1,b=22,c=333", [("a", "1"), ("b", "22"), ("c", "333")]),
185
- ("=1", [("", "1")]),
186
- ("a=", [("a", "")]),
187
- ("a=1,=22,c=333", [("a", "1"), ("", "22"), ("c", "333")]),
188
- ("a=1,b=,c=333", [("a", "1"), ("b", ""), ("c", "333")]),
189
- ("a=1,b=(22,22,22),c=333", [("a", "1"), ("b", "(22,22,22)"), ("c", "333")]),
190
- ("a=1,b=(c=22),c=333", [("a", "1"), ("b", "(c=22)"), ("c", "333")]),
191
- ])
184
+ @mark.parametrize(
185
+ ("text", "expected"),
186
+ [
187
+ param("", []),
188
+ param("a=1", [("a", "1")]),
189
+ param("a=1,b=22", [("a", "1"), ("b", "22")]),
190
+ param("a=1,b=22,c=333", [("a", "1"), ("b", "22"), ("c", "333")]),
191
+ param("=1", [("", "1")]),
192
+ param("a=", [("a", "")]),
193
+ param("a=1,=22,c=333", [("a", "1"), ("", "22"), ("c", "333")]),
194
+ param("a=1,b=,c=333", [("a", "1"), ("b", ""), ("c", "333")]),
195
+ param(
196
+ "a=1,b=(22,22,22),c=333",
197
+ [("a", "1"), ("b", "(22,22,22)"), ("c", "333")],
198
+ ),
199
+ param("a=1,b=(c=22),c=333", [("a", "1"), ("b", "(c=22)"), ("c", "333")]),
200
+ ],
192
201
  )
193
- def test_main(self, *, case: tuple[str, Sequence[tuple[str, str]]]) -> None:
194
- text, expected = case
202
+ def test_main(self, *, text: str, expected: str) -> None:
195
203
  result = split_key_value_pairs(text)
196
204
  assert result == expected
197
205
 
@@ -223,22 +231,24 @@ class TestSplitKeyValuePairs:
223
231
 
224
232
 
225
233
  class TestSplitAndJoinStr:
226
- @given(
227
- data=data(),
228
- case=sampled_from([
229
- ("", 0, []),
230
- (r"\,", 1, [""]),
231
- (",", 2, ["", ""]),
232
- (",,", 3, ["", "", ""]),
233
- ("1", 1, ["1"]),
234
- ("1,22", 2, ["1", "22"]),
235
- ("1,22,333", 3, ["1", "22", "333"]),
236
- ("1,,333", 3, ["1", "", "333"]),
237
- ("1,(22,22,22),333", 5, ["1", "(22", "22", "22)", "333"]),
238
- ]),
234
+ @given(data=data())
235
+ @mark.parametrize(
236
+ ("text", "n", "expected"),
237
+ [
238
+ param("", 0, []),
239
+ param(r"\,", 1, [""]),
240
+ param(",", 2, ["", ""]),
241
+ param(",,", 3, ["", "", ""]),
242
+ param("1", 1, ["1"]),
243
+ param("1,22", 2, ["1", "22"]),
244
+ param("1,22,333", 3, ["1", "22", "333"]),
245
+ param("1,,333", 3, ["1", "", "333"]),
246
+ param("1,(22,22,22),333", 5, ["1", "(22", "22", "22)", "333"]),
247
+ ],
239
248
  )
240
- def test_main(self, *, data: DataObject, case: tuple[str, int, list[str]]) -> None:
241
- text, n, expected = case
249
+ def test_main(
250
+ self, *, data: DataObject, text: str, n: int, expected: list[str]
251
+ ) -> None:
242
252
  n_use = data.draw(just(n) | none())
243
253
  result = split_str(text, n=n_use)
244
254
  if n_use is None:
@@ -247,21 +257,21 @@ class TestSplitAndJoinStr:
247
257
  assert result == tuple(expected)
248
258
  assert join_strs(result) == text
249
259
 
250
- @given(
251
- data=data(),
252
- case=sampled_from([
253
- ("1", 1, ["1"]),
254
- ("1,22", 2, ["1", "22"]),
255
- ("1,22,333", 3, ["1", "22", "333"]),
256
- ("1,(22),333", 3, ["1", "(22)", "333"]),
257
- ("1,(22,22),333", 3, ["1", "(22,22)", "333"]),
258
- ("1,(22,22,22),333", 3, ["1", "(22,22,22)", "333"]),
259
- ]),
260
+ @given(data=data())
261
+ @mark.parametrize(
262
+ ("text", "n", "expected"),
263
+ [
264
+ param("1", 1, ["1"]),
265
+ param("1,22", 2, ["1", "22"]),
266
+ param("1,22,333", 3, ["1", "22", "333"]),
267
+ param("1,(22),333", 3, ["1", "(22)", "333"]),
268
+ param("1,(22,22),333", 3, ["1", "(22,22)", "333"]),
269
+ param("1,(22,22,22),333", 3, ["1", "(22,22,22)", "333"]),
270
+ ],
260
271
  )
261
272
  def test_brackets(
262
- self, *, data: DataObject, case: tuple[str, int, list[str]]
273
+ self, *, data: DataObject, text: str, n: int, expected: list[str]
263
274
  ) -> None:
264
- text, n, expected = case
265
275
  n_use = data.draw(just(n) | none())
266
276
  result = split_str(text, brackets=[("(", ")")], n=n_use)
267
277
  if n_use is None:
@@ -1,3 +1,3 @@
1
1
  from __future__ import annotations
2
2
 
3
- __version__ = "0.162.4"
3
+ __version__ = "0.162.6"
@@ -1483,7 +1483,7 @@ def year_months(
1483
1483
  def zone_infos(draw: DrawFn, /) -> ZoneInfo:
1484
1484
  """Strategy for generating time-zones."""
1485
1485
  time_zone = draw(timezones())
1486
- if IS_LINUX:
1486
+ if IS_LINUX: # skipif-not-linux
1487
1487
  _ = assume(time_zone.key not in {"Etc/UTC", "localtime"})
1488
1488
  with assume_does_not_raise(TimeZoneNotFoundError):
1489
1489
  _ = get_now(time_zone)
@@ -0,0 +1,50 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+ from typing import TYPE_CHECKING, Any
5
+
6
+ from testbook import testbook
7
+
8
+ from utilities.pytest import throttle
9
+ from utilities.text import pascal_case
10
+
11
+ if TYPE_CHECKING:
12
+ from collections.abc import Callable
13
+
14
+ from utilities.types import Delta, PathLike
15
+
16
+
17
+ def build_notebook_tester(
18
+ path: PathLike, /, *, throttle: Delta | None = None, on_try: bool = False
19
+ ) -> type[Any]:
20
+ """Build the notebook tester class."""
21
+ path = Path(path)
22
+ name = f"Test{pascal_case(path.stem)}"
23
+ notebooks = [
24
+ path_i
25
+ for path_i in path.rglob("**/*.ipynb")
26
+ if all(p != ".ipynb_checkpoints" for p in path_i.parts)
27
+ ]
28
+ namespace = {
29
+ f"test_{p.stem.replace('-', '_')}": _build_test_method(
30
+ p, delta=throttle, on_try=on_try
31
+ )
32
+ for p in notebooks
33
+ }
34
+ return type(name, (), namespace)
35
+
36
+
37
+ def _build_test_method(
38
+ path: Path, /, *, delta: Delta | None = None, on_try: bool = False
39
+ ) -> Callable[..., Any]:
40
+ @testbook(path, execute=True)
41
+ def method(self: Any, tb: Any) -> None:
42
+ _ = (self, tb) # pragma: no cover
43
+
44
+ if delta is not None:
45
+ method = throttle(delta=delta, on_try=on_try)(method)
46
+
47
+ return method
48
+
49
+
50
+ __all__ = ["build_notebook_tester"]
@@ -6,7 +6,7 @@ from collections.abc import Callable
6
6
  from dataclasses import dataclass
7
7
  from itertools import chain
8
8
  from os import getpid
9
- from re import IGNORECASE, Match, escape, search
9
+ from re import IGNORECASE, VERBOSE, escape, search
10
10
  from textwrap import dedent
11
11
  from threading import get_ident
12
12
  from time import time_ns
@@ -77,6 +77,21 @@ class ParseNoneError(Exception):
77
77
  ##
78
78
 
79
79
 
80
+ def pascal_case(text: str, /) -> str:
81
+ """Convert text to pascal case."""
82
+ parts = _SPLIT_TEXT.findall(text)
83
+ parts = [p for p in parts if len(p) >= 1]
84
+ parts = list(map(_pascal_case_one, parts))
85
+ return "".join(parts)
86
+
87
+
88
+ def _pascal_case_one(text: str, /) -> str:
89
+ return text if text.isupper() else text.title()
90
+
91
+
92
+ ##
93
+
94
+
80
95
  def repr_encode(obj: Any, /) -> bytes:
81
96
  """Return the representation of the object encoded as bytes."""
82
97
  return repr(obj).encode()
@@ -85,25 +100,24 @@ def repr_encode(obj: Any, /) -> bytes:
85
100
  ##
86
101
 
87
102
 
88
- _ACRONYM_PATTERN = re.compile(r"([A-Z\d]+)(?=[A-Z\d]|$)")
89
- _SPACES_PATTERN = re.compile(r"\s+")
90
- _SPLIT_PATTERN = re.compile(r"([\-_]*[A-Z][^A-Z]*[\-_]*)")
91
-
92
-
93
103
  def snake_case(text: str, /) -> str:
94
104
  """Convert text into snake case."""
95
- text = _SPACES_PATTERN.sub("", text)
96
- if not text.isupper():
97
- text = _ACRONYM_PATTERN.sub(_snake_case_title, text)
98
- text = "_".join(s for s in _SPLIT_PATTERN.split(text) if s)
99
- while search("__", text):
100
- text = text.replace("__", "_")
101
- return text.lower()
102
-
103
-
104
- def _snake_case_title(match: Match[str], /) -> str:
105
- return match.group(0).title()
106
-
105
+ leading = bool(search(r"^_", text))
106
+ trailing = bool(search(r"_$", text))
107
+ parts = _SPLIT_TEXT.findall(text)
108
+ parts = (p for p in parts if len(p) >= 1)
109
+ parts = chain([""] if leading else [], parts, [""] if trailing else [])
110
+ return "_".join(parts).lower()
111
+
112
+
113
+ _SPLIT_TEXT = re.compile(
114
+ r"""
115
+ [A-Z]+(?=[A-Z][a-z0-9]) | # all caps followed by Upper+lower or digit (API in APIResponse2)
116
+ [A-Z]?[a-z]+[0-9]* | # normal words with optional trailing digits (Text123)
117
+ [A-Z]+[0-9]* | # consecutive caps with optional trailing digits (ID2)
118
+ """,
119
+ flags=VERBOSE,
120
+ )
107
121
 
108
122
  ##
109
123
 
@@ -503,6 +517,7 @@ __all__ = [
503
517
  "join_strs",
504
518
  "parse_bool",
505
519
  "parse_none",
520
+ "pascal_case",
506
521
  "repr_encode",
507
522
  "secret_str",
508
523
  "snake_case",