dycw-utilities 0.162.5__tar.gz → 0.162.7__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 (212) hide show
  1. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/PKG-INFO +1 -1
  2. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/pyproject.toml +2 -2
  3. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/test_orjson.py +10 -2
  4. dycw_utilities-0.162.7/src/tests/test_testbook.py +38 -0
  5. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/test_text.py +120 -110
  6. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/utilities/__init__.py +1 -1
  7. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/utilities/orjson.py +22 -2
  8. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/utilities/testbook.py +11 -7
  9. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/utilities/text.py +33 -18
  10. dycw_utilities-0.162.5/src/tests/test_testbook.py +0 -22
  11. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/.gitignore +0 -0
  12. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/LICENSE +0 -0
  13. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/README.md +0 -0
  14. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/__init__.py +0 -0
  15. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/conftest.py +0 -0
  16. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/modules/__init__.py +0 -0
  17. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/modules/package_missing/__init__.py +0 -0
  18. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/modules/package_missing/module.py +0 -0
  19. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/modules/package_with/__init__.py +0 -0
  20. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/modules/package_with/outer_1.py +0 -0
  21. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/modules/package_with/outer_2.py +0 -0
  22. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/modules/package_with/subpackage/__init__.py +0 -0
  23. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/modules/package_with/subpackage/inner_1.py +0 -0
  24. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/modules/package_with/subpackage/inner_2.py +0 -0
  25. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/modules/package_with/subpackage/inner_3.py +0 -0
  26. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/modules/package_without/__init__.py +0 -0
  27. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/modules/package_without/module_1.py +0 -0
  28. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/modules/package_without/module_2.py +0 -0
  29. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/modules/standalone.py +0 -0
  30. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/modules/with_imports.py +0 -0
  31. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/regressions/test_pytest_regressions/TestMultipleRegressionFixtures__test_main__obj.json +0 -0
  32. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/regressions/test_pytest_regressions/TestMultipleRegressionFixtures__test_main__series.json +0 -0
  33. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_int.json +0 -0
  34. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_literal__false.json +0 -0
  35. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_literal__true.json +0 -0
  36. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_nested.json +0 -0
  37. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/regressions/test_pytest_regressions/TestPolarsRegressionFixture__test_dataframe.json +0 -0
  38. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/regressions/test_pytest_regressions/TestPolarsRegressionFixture__test_series.json +0 -0
  39. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/test_aeventkit.py +0 -0
  40. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/test_altair.py +0 -0
  41. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/test_asyncio.py +0 -0
  42. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/test_atomicwrites.py +0 -0
  43. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/test_atools.py +0 -0
  44. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/test_cachetools.py +0 -0
  45. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/test_click.py +0 -0
  46. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/test_concurrent.py +0 -0
  47. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/test_contextlib.py +0 -0
  48. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/test_contextvars.py +0 -0
  49. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/test_cryptography.py +0 -0
  50. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/test_cvxpy.py +0 -0
  51. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/test_dataclasses.py +0 -0
  52. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/test_enum.py +0 -0
  53. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/test_errors.py +0 -0
  54. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/test_fastapi.py +0 -0
  55. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/test_fpdf2.py +0 -0
  56. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/test_functions.py +0 -0
  57. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/test_functools.py +0 -0
  58. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/test_getpass.py +0 -0
  59. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/test_gzip.py +0 -0
  60. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/test_hashlib.py +0 -0
  61. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/test_http.py +0 -0
  62. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/test_hypothesis.py +0 -0
  63. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/test_importlib.py +0 -0
  64. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/test_inflect.py +0 -0
  65. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/test_ipython.py +0 -0
  66. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/test_iterables.py +0 -0
  67. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/test_json.py +0 -0
  68. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/test_jupyter.py +0 -0
  69. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/test_libcst.py +0 -0
  70. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/test_lightweight_charts.py +0 -0
  71. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/test_logging.py +0 -0
  72. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/test_math.py +0 -0
  73. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/test_memory_profiler.py +0 -0
  74. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/test_modules.py +0 -0
  75. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/test_more_itertools.py +0 -0
  76. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/test_numpy.py +0 -0
  77. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/test_objects/__init__.py +0 -0
  78. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/test_objects/objects.py +0 -0
  79. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/test_operator.py +0 -0
  80. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/test_optuna.py +0 -0
  81. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/test_os.py +0 -0
  82. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/test_parse.py +0 -0
  83. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/test_pathlib.py +0 -0
  84. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/test_pickle.py +0 -0
  85. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/test_platform.py +0 -0
  86. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/test_polars.py +0 -0
  87. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/test_polars_ols.py +0 -0
  88. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/test_postgres.py +0 -0
  89. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/test_pottery.py +0 -0
  90. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/test_pqdm.py +0 -0
  91. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/test_psutil.py +0 -0
  92. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/test_pyinstrument.py +0 -0
  93. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/test_pytest.py +0 -0
  94. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/test_pytest_randomly.py +0 -0
  95. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/test_pytest_regressions.py +0 -0
  96. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/test_random.py +0 -0
  97. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/test_re.py +0 -0
  98. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/test_redis.py +0 -0
  99. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/test_reprlib.py +0 -0
  100. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/test_scipy.py +0 -0
  101. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/test_sentinel.py +0 -0
  102. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/test_shelve.py +0 -0
  103. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/test_slack_sdk.py +0 -0
  104. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/test_socket.py +0 -0
  105. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/test_sqlalchemy.py +0 -0
  106. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/test_sqlalchemy_polars.py +0 -0
  107. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/test_statsmodels.py +0 -0
  108. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/test_string.py +0 -0
  109. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/test_tempfile.py +0 -0
  110. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/test_threading.py +0 -0
  111. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/test_timer.py +0 -0
  112. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/test_traceback.py +0 -0
  113. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/test_typed_settings.py +0 -0
  114. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/test_types.py +0 -0
  115. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/test_typing.py +0 -0
  116. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/test_typing_funcs/__init__.py +0 -0
  117. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/test_typing_funcs/no_future.py +0 -0
  118. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/test_typing_funcs/with_future.py +0 -0
  119. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/test_tzdata.py +0 -0
  120. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/test_tzlocal.py +0 -0
  121. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/test_uuid.py +0 -0
  122. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/test_version.py +0 -0
  123. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/test_warnings.py +0 -0
  124. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/test_whenever.py +0 -0
  125. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/test_zipfile.py +0 -0
  126. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/tests/test_zoneinfo.py +0 -0
  127. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/utilities/aeventkit.py +0 -0
  128. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/utilities/altair.py +0 -0
  129. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/utilities/asyncio.py +0 -0
  130. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/utilities/atomicwrites.py +0 -0
  131. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/utilities/atools.py +0 -0
  132. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/utilities/cachetools.py +0 -0
  133. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/utilities/click.py +0 -0
  134. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/utilities/concurrent.py +0 -0
  135. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/utilities/contextlib.py +0 -0
  136. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/utilities/contextvars.py +0 -0
  137. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/utilities/cryptography.py +0 -0
  138. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/utilities/cvxpy.py +0 -0
  139. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/utilities/dataclasses.py +0 -0
  140. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/utilities/enum.py +0 -0
  141. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/utilities/errors.py +0 -0
  142. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/utilities/fastapi.py +0 -0
  143. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/utilities/fpdf2.py +0 -0
  144. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/utilities/functions.py +0 -0
  145. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/utilities/functools.py +0 -0
  146. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/utilities/getpass.py +0 -0
  147. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/utilities/gzip.py +0 -0
  148. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/utilities/hashlib.py +0 -0
  149. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/utilities/http.py +0 -0
  150. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/utilities/hypothesis.py +0 -0
  151. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/utilities/importlib.py +0 -0
  152. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/utilities/inflect.py +0 -0
  153. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/utilities/ipython.py +0 -0
  154. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/utilities/iterables.py +0 -0
  155. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/utilities/json.py +0 -0
  156. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/utilities/jupyter.py +0 -0
  157. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/utilities/libcst.py +0 -0
  158. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/utilities/lightweight_charts.py +0 -0
  159. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/utilities/logging.py +0 -0
  160. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/utilities/math.py +0 -0
  161. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/utilities/memory_profiler.py +0 -0
  162. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/utilities/modules.py +0 -0
  163. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/utilities/more_itertools.py +0 -0
  164. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/utilities/numpy.py +0 -0
  165. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/utilities/operator.py +0 -0
  166. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/utilities/optuna.py +0 -0
  167. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/utilities/os.py +0 -0
  168. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/utilities/parse.py +0 -0
  169. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/utilities/pathlib.py +0 -0
  170. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/utilities/pickle.py +0 -0
  171. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/utilities/platform.py +0 -0
  172. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/utilities/polars.py +0 -0
  173. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/utilities/polars_ols.py +0 -0
  174. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/utilities/postgres.py +0 -0
  175. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/utilities/pottery.py +0 -0
  176. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/utilities/pqdm.py +0 -0
  177. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/utilities/psutil.py +0 -0
  178. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/utilities/py.typed +0 -0
  179. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/utilities/pyinstrument.py +0 -0
  180. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/utilities/pytest.py +0 -0
  181. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/utilities/pytest_plugins/__init__.py +0 -0
  182. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/utilities/pytest_plugins/pytest_randomly.py +0 -0
  183. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/utilities/pytest_plugins/pytest_regressions.py +0 -0
  184. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/utilities/pytest_regressions.py +0 -0
  185. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/utilities/random.py +0 -0
  186. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/utilities/re.py +0 -0
  187. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/utilities/redis.py +0 -0
  188. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/utilities/reprlib.py +0 -0
  189. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/utilities/scipy.py +0 -0
  190. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/utilities/sentinel.py +0 -0
  191. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/utilities/shelve.py +0 -0
  192. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/utilities/slack_sdk.py +0 -0
  193. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/utilities/socket.py +0 -0
  194. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/utilities/sqlalchemy.py +0 -0
  195. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/utilities/sqlalchemy_polars.py +0 -0
  196. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/utilities/statsmodels.py +0 -0
  197. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/utilities/string.py +0 -0
  198. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/utilities/tempfile.py +0 -0
  199. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/utilities/threading.py +0 -0
  200. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/utilities/timer.py +0 -0
  201. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/utilities/traceback.py +0 -0
  202. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/utilities/typed_settings.py +0 -0
  203. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/utilities/types.py +0 -0
  204. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/utilities/typing.py +0 -0
  205. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/utilities/tzdata.py +0 -0
  206. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/utilities/tzlocal.py +0 -0
  207. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/utilities/uuid.py +0 -0
  208. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/utilities/version.py +0 -0
  209. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/utilities/warnings.py +0 -0
  210. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/utilities/whenever.py +0 -0
  211. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/utilities/zipfile.py +0 -0
  212. {dycw_utilities-0.162.5 → dycw_utilities-0.162.7}/src/utilities/zoneinfo.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dycw-utilities
3
- Version: 0.162.5
3
+ Version: 0.162.7
4
4
  Author-email: Derek Wan <d.wan@icloud.com>
5
5
  License-File: LICENSE
6
6
  Requires-Python: >=3.12
@@ -230,7 +230,7 @@ dependencies = [
230
230
  name = "dycw-utilities"
231
231
  readme = "README.md"
232
232
  requires-python = ">= 3.12"
233
- version = "0.162.5"
233
+ version = "0.162.7"
234
234
 
235
235
  [project.entry-points.pytest11]
236
236
  pytest-randomly = "utilities.pytest_plugins.pytest_randomly"
@@ -263,7 +263,7 @@ test = [
263
263
  # bump-my-version
264
264
  [tool.bumpversion]
265
265
  allow_dirty = true
266
- current_version = "0.162.5"
266
+ current_version = "0.162.7"
267
267
 
268
268
  [[tool.bumpversion.files]]
269
269
  filename = "src/utilities/__init__.py"
@@ -20,7 +20,6 @@ from hypothesis.strategies import (
20
20
  sets,
21
21
  tuples,
22
22
  )
23
- from orjson import JSONDecodeError
24
23
  from polars import Object, String, UInt64
25
24
  from pytest import approx, mark, param, raises
26
25
  from whenever import ZonedDateTime
@@ -73,6 +72,7 @@ from utilities.orjson import (
73
72
  OrjsonFormatter,
74
73
  OrjsonLogRecord,
75
74
  Unserializable,
75
+ _DeserializeInvalidJSONError,
76
76
  _DeserializeNoObjectsError,
77
77
  _DeserializeObjectNotFoundError,
78
78
  _object_hook_get_object,
@@ -428,7 +428,7 @@ class TestGetLogRecords:
428
428
  assert result.num_lines_error == 1
429
429
  assert result.missing == set()
430
430
  assert len(result.other_errors) == 1
431
- assert isinstance(one(result.other_errors), JSONDecodeError)
431
+ assert isinstance(one(result.other_errors), _DeserializeInvalidJSONError)
432
432
 
433
433
 
434
434
  class TestOrjsonFormatter:
@@ -753,6 +753,14 @@ class TestSerialize:
753
753
  assert result == expected
754
754
 
755
755
 
756
+ class TestDeserialize:
757
+ def test_error_invalid_json(self) -> None:
758
+ with raises(
759
+ _DeserializeInvalidJSONError, match="Invalid JSON: b'invalid json'"
760
+ ):
761
+ _ = deserialize(b"invalid json")
762
+
763
+
756
764
  class TestObjectHookGetObject:
757
765
  def test_main(self) -> None:
758
766
  result = _object_hook_get_object(Sentinel.__qualname__, objects={Sentinel})
@@ -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.5"
3
+ __version__ = "0.162.7"
@@ -20,6 +20,7 @@ from orjson import (
20
20
  OPT_PASSTHROUGH_DATACLASS,
21
21
  OPT_PASSTHROUGH_DATETIME,
22
22
  OPT_SORT_KEYS,
23
+ JSONDecodeError,
23
24
  dumps,
24
25
  loads,
25
26
  )
@@ -371,8 +372,12 @@ def deserialize(
371
372
  redirects: Mapping[str, type[Any]] | None = None,
372
373
  ) -> Any:
373
374
  """Deserialize an object."""
375
+ try:
376
+ obj = loads(data)
377
+ except JSONDecodeError:
378
+ raise _DeserializeInvalidJSONError(data=data) from None
374
379
  return _object_hook(
375
- loads(data),
380
+ obj,
376
381
  data=data,
377
382
  dataclass_hook=dataclass_hook,
378
383
  objects=objects,
@@ -380,6 +385,11 @@ def deserialize(
380
385
  )
381
386
 
382
387
 
388
+ @dataclass(kw_only=True, slots=True)
389
+ class DeerializeError(Exception):
390
+ obj: Any
391
+
392
+
383
393
  (
384
394
  _DATE_PATTERN,
385
395
  _DATE_DELTA_PATTERN,
@@ -739,11 +749,19 @@ def _object_hook_get_object(
739
749
  @dataclass(kw_only=True, slots=True)
740
750
  class DeserializeError(Exception):
741
751
  data: bytes
742
- qualname: str
752
+
753
+
754
+ @dataclass(kw_only=True, slots=True)
755
+ class _DeserializeInvalidJSONError(DeserializeError):
756
+ @override
757
+ def __str__(self) -> str:
758
+ return f"Invalid JSON: {self.data!r}"
743
759
 
744
760
 
745
761
  @dataclass(kw_only=True, slots=True)
746
762
  class _DeserializeNoObjectsError(DeserializeError):
763
+ qualname: str
764
+
747
765
  @override
748
766
  def __str__(self) -> str:
749
767
  return f"Objects required to deserialize {self.qualname!r} from {self.data!r}"
@@ -751,6 +769,8 @@ class _DeserializeNoObjectsError(DeserializeError):
751
769
 
752
770
  @dataclass(kw_only=True, slots=True)
753
771
  class _DeserializeObjectNotFoundError(DeserializeError):
772
+ qualname: str
773
+
754
774
  @override
755
775
  def __str__(self) -> str:
756
776
  return (
@@ -6,6 +6,7 @@ from typing import TYPE_CHECKING, Any
6
6
  from testbook import testbook
7
7
 
8
8
  from utilities.pytest import throttle
9
+ from utilities.text import pascal_case
9
10
 
10
11
  if TYPE_CHECKING:
11
12
  from collections.abc import Callable
@@ -15,16 +16,19 @@ if TYPE_CHECKING:
15
16
 
16
17
  def build_notebook_tester(
17
18
  path: PathLike, /, *, throttle: Delta | None = None, on_try: bool = False
18
- ) -> object:
19
- """Build the notebook test class."""
20
- name = f"Test{Path(path).stem}"
19
+ ) -> type[Any]:
20
+ """Build the notebook tester class."""
21
+ path = Path(path)
22
+ name = f"Test{pascal_case(path.stem)}"
21
23
  notebooks = [
22
24
  path_i
23
- for path_i in Path(path).rglob("**/*.ipynb")
25
+ for path_i in path.rglob("**/*.ipynb")
24
26
  if all(p != ".ipynb_checkpoints" for p in path_i.parts)
25
27
  ]
26
28
  namespace = {
27
- f"test_{p.stem}": _build_test_method(p, delta=throttle, on_try=on_try)
29
+ f"test_{p.stem.replace('-', '_')}": _build_test_method(
30
+ p, delta=throttle, on_try=on_try
31
+ )
28
32
  for p in notebooks
29
33
  }
30
34
  return type(name, (), namespace)
@@ -33,14 +37,14 @@ def build_notebook_tester(
33
37
  def _build_test_method(
34
38
  path: Path, /, *, delta: Delta | None = None, on_try: bool = False
35
39
  ) -> Callable[..., Any]:
40
+ @testbook(path, execute=True)
36
41
  def method(self: Any, tb: Any) -> None:
37
42
  _ = (self, tb) # pragma: no cover
38
43
 
39
44
  if delta is not None:
40
45
  method = throttle(delta=delta, on_try=on_try)(method)
41
46
 
42
- tbook = testbook(path, execute=True)
43
- return tbook(method)
47
+ return method
44
48
 
45
49
 
46
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",
@@ -1,22 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from json import dumps
4
- from typing import TYPE_CHECKING
5
-
6
- from pytest import mark, param
7
-
8
- from utilities.testbook import build_notebook_tester
9
- from utilities.whenever import HOUR
10
-
11
- if TYPE_CHECKING:
12
- from pathlib import Path
13
-
14
- from utilities.types import Delta
15
-
16
-
17
- class TestBuildNotebookTester:
18
- @mark.parametrize("throttle", [param(HOUR), param(None)])
19
- def test_main(self, *, tmp_path: Path, throttle: Delta | None) -> None:
20
- data = {"cells": []}
21
- _ = tmp_path.joinpath("notebook.ipynb").write_text(dumps(data))
22
- _ = build_notebook_tester(tmp_path, throttle=throttle)