dycw-utilities 0.108.1__tar.gz → 0.108.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (223) hide show
  1. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/PKG-INFO +1 -1
  2. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/pyproject.toml +2 -2
  3. dycw_utilities-0.108.2/src/tests/test_parse.py +234 -0
  4. dycw_utilities-0.108.2/src/tests/test_sentinel.py +45 -0
  5. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/test_text.py +21 -4
  6. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/utilities/__init__.py +1 -1
  7. dycw_utilities-0.108.2/src/utilities/parse.py +129 -0
  8. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/utilities/sentinel.py +28 -1
  9. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/utilities/text.py +22 -1
  10. dycw_utilities-0.108.1/src/tests/test_sentinel.py +0 -22
  11. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/.gitignore +0 -0
  12. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/LICENSE +0 -0
  13. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/README.md +0 -0
  14. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/__init__.py +0 -0
  15. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/conftest.py +0 -0
  16. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/modules/__init__.py +0 -0
  17. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/modules/package_missing/__init__.py +0 -0
  18. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/modules/package_missing/module.py +0 -0
  19. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/modules/package_with/__init__.py +0 -0
  20. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/modules/package_with/outer_1.py +0 -0
  21. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/modules/package_with/outer_2.py +0 -0
  22. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/modules/package_with/subpackage/__init__.py +0 -0
  23. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/modules/package_with/subpackage/inner_1.py +0 -0
  24. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/modules/package_with/subpackage/inner_2.py +0 -0
  25. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/modules/package_with/subpackage/inner_3.py +0 -0
  26. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/modules/package_without/__init__.py +0 -0
  27. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/modules/package_without/module_1.py +0 -0
  28. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/modules/package_without/module_2.py +0 -0
  29. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/modules/standalone.py +0 -0
  30. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/modules/with_imports.py +0 -0
  31. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/regressions/test_pytest_regressions/TestMultipleRegressionFixtures__test_main__obj.json +0 -0
  32. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/regressions/test_pytest_regressions/TestMultipleRegressionFixtures__test_main__series.json +0 -0
  33. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_int.json +0 -0
  34. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_literal__false.json +0 -0
  35. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_literal__true.json +0 -0
  36. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_nested.json +0 -0
  37. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/regressions/test_pytest_regressions/TestPolarsRegressionFixture__test_dataframe.json +0 -0
  38. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/regressions/test_pytest_regressions/TestPolarsRegressionFixture__test_series.json +0 -0
  39. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/scripts/__init__.py +0 -0
  40. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/scripts/test_async_service/__init__.py +0 -0
  41. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/scripts/test_async_service/__main__.py +0 -0
  42. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/scripts/test_async_service/run.sh +0 -0
  43. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/scripts/test_queue_processor/__init__.py +0 -0
  44. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/scripts/test_queue_processor/__main__.py +0 -0
  45. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/scripts/test_queue_processor/run.sh +0 -0
  46. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/test_altair.py +0 -0
  47. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/test_astor.py +0 -0
  48. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/test_asyncio.py +0 -0
  49. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/test_atomicwrites.py +0 -0
  50. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/test_atools.py +0 -0
  51. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/test_cachetools.py +0 -0
  52. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/test_click.py +0 -0
  53. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/test_concurrent.py +0 -0
  54. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/test_contextlib.py +0 -0
  55. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/test_contextvars.py +0 -0
  56. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/test_cryptography.py +0 -0
  57. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/test_cvxpy.py +0 -0
  58. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/test_dataclasses.py +0 -0
  59. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/test_datetime.py +0 -0
  60. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/test_enum.py +0 -0
  61. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/test_errors.py +0 -0
  62. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/test_eventkit.py +0 -0
  63. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/test_fastapi.py +0 -0
  64. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/test_fpdf2.py +0 -0
  65. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/test_functions.py +0 -0
  66. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/test_functools.py +0 -0
  67. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/test_getpass.py +0 -0
  68. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/test_git.py +0 -0
  69. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/test_hashlib.py +0 -0
  70. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/test_http.py +0 -0
  71. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/test_hypothesis.py +0 -0
  72. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/test_ipython.py +0 -0
  73. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/test_iterables.py +0 -0
  74. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/test_jupyter.py +0 -0
  75. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/test_logging.py +0 -0
  76. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/test_loguru.py +0 -0
  77. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/test_luigi.py +0 -0
  78. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/test_math.py +0 -0
  79. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/test_memory_profiler.py +0 -0
  80. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/test_modules.py +0 -0
  81. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/test_more_itertools.py +0 -0
  82. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/test_numpy.py +0 -0
  83. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/test_operator.py +0 -0
  84. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/test_optuna.py +0 -0
  85. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/test_orjson.py +0 -0
  86. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/test_os.py +0 -0
  87. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/test_pathlib.py +0 -0
  88. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/test_period.py +0 -0
  89. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/test_pickle.py +0 -0
  90. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/test_platform.py +0 -0
  91. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/test_polars.py +0 -0
  92. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/test_pqdm.py +0 -0
  93. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/test_pydantic.py +0 -0
  94. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/test_pyinstrument.py +0 -0
  95. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/test_pyrsistent.py +0 -0
  96. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/test_pytest.py +0 -0
  97. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/test_pytest_regressions.py +0 -0
  98. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/test_python_dotenv.py +0 -0
  99. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/test_random.py +0 -0
  100. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/test_re.py +0 -0
  101. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/test_redis.py +0 -0
  102. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/test_reprlib.py +0 -0
  103. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/test_rich.py +0 -0
  104. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/test_scipy.py +0 -0
  105. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/test_shelve.py +0 -0
  106. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/test_slack_sdk.py +0 -0
  107. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/test_socket.py +0 -0
  108. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/test_sqlalchemy.py +0 -0
  109. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/test_sqlalchemy_polars.py +0 -0
  110. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/test_streamlit.py +0 -0
  111. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/test_sys.py +0 -0
  112. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/test_tempfile.py +0 -0
  113. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/test_tenacity.py +0 -0
  114. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/test_threading.py +0 -0
  115. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/test_timer.py +0 -0
  116. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/test_traceback.py +0 -0
  117. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/test_traceback_funcs/__init__.py +0 -0
  118. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/test_traceback_funcs/chain.py +0 -0
  119. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/test_traceback_funcs/decorated_async.py +0 -0
  120. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/test_traceback_funcs/decorated_sync.py +0 -0
  121. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/test_traceback_funcs/error_bind.py +0 -0
  122. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/test_traceback_funcs/many.py +0 -0
  123. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/test_traceback_funcs/one.py +0 -0
  124. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/test_traceback_funcs/recursive.py +0 -0
  125. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/test_traceback_funcs/task_group_one.py +0 -0
  126. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/test_traceback_funcs/task_group_two.py +0 -0
  127. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/test_traceback_funcs/two.py +0 -0
  128. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/test_traceback_funcs/untraced.py +0 -0
  129. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/test_types.py +0 -0
  130. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/test_typing.py +0 -0
  131. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/test_typing_funcs/__init__.py +0 -0
  132. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/test_typing_funcs/no_future.py +0 -0
  133. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/test_typing_funcs/with_future.py +0 -0
  134. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/test_tzdata.py +0 -0
  135. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/test_tzlocal.py +0 -0
  136. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/test_uuid.py +0 -0
  137. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/test_version.py +0 -0
  138. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/test_warnings.py +0 -0
  139. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/test_whenever.py +0 -0
  140. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/test_zipfile.py +0 -0
  141. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/tests/test_zoneinfo.py +0 -0
  142. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/utilities/altair.py +0 -0
  143. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/utilities/astor.py +0 -0
  144. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/utilities/asyncio.py +0 -0
  145. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/utilities/atomicwrites.py +0 -0
  146. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/utilities/atools.py +0 -0
  147. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/utilities/cachetools.py +0 -0
  148. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/utilities/click.py +0 -0
  149. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/utilities/concurrent.py +0 -0
  150. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/utilities/contextlib.py +0 -0
  151. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/utilities/contextvars.py +0 -0
  152. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/utilities/cryptography.py +0 -0
  153. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/utilities/cvxpy.py +0 -0
  154. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/utilities/dataclasses.py +0 -0
  155. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/utilities/datetime.py +0 -0
  156. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/utilities/enum.py +0 -0
  157. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/utilities/errors.py +0 -0
  158. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/utilities/eventkit.py +0 -0
  159. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/utilities/fastapi.py +0 -0
  160. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/utilities/fpdf2.py +0 -0
  161. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/utilities/functions.py +0 -0
  162. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/utilities/functools.py +0 -0
  163. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/utilities/getpass.py +0 -0
  164. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/utilities/git.py +0 -0
  165. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/utilities/hashlib.py +0 -0
  166. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/utilities/http.py +0 -0
  167. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/utilities/hypothesis.py +0 -0
  168. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/utilities/ipython.py +0 -0
  169. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/utilities/iterables.py +0 -0
  170. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/utilities/jupyter.py +0 -0
  171. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/utilities/logging.py +0 -0
  172. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/utilities/loguru.py +0 -0
  173. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/utilities/luigi.py +0 -0
  174. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/utilities/math.py +0 -0
  175. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/utilities/memory_profiler.py +0 -0
  176. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/utilities/modules.py +0 -0
  177. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/utilities/more_itertools.py +0 -0
  178. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/utilities/numpy.py +0 -0
  179. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/utilities/operator.py +0 -0
  180. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/utilities/optuna.py +0 -0
  181. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/utilities/orjson.py +0 -0
  182. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/utilities/os.py +0 -0
  183. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/utilities/pathlib.py +0 -0
  184. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/utilities/period.py +0 -0
  185. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/utilities/pickle.py +0 -0
  186. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/utilities/platform.py +0 -0
  187. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/utilities/polars.py +0 -0
  188. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/utilities/pqdm.py +0 -0
  189. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/utilities/py.typed +0 -0
  190. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/utilities/pydantic.py +0 -0
  191. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/utilities/pyinstrument.py +0 -0
  192. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/utilities/pyrsistent.py +0 -0
  193. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/utilities/pytest.py +0 -0
  194. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/utilities/pytest_regressions.py +0 -0
  195. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/utilities/python_dotenv.py +0 -0
  196. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/utilities/random.py +0 -0
  197. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/utilities/re.py +0 -0
  198. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/utilities/redis.py +0 -0
  199. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/utilities/reprlib.py +0 -0
  200. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/utilities/rich.py +0 -0
  201. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/utilities/scipy.py +0 -0
  202. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/utilities/shelve.py +0 -0
  203. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/utilities/slack_sdk.py +0 -0
  204. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/utilities/socket.py +0 -0
  205. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/utilities/sqlalchemy.py +0 -0
  206. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/utilities/sqlalchemy_polars.py +0 -0
  207. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/utilities/streamlit.py +0 -0
  208. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/utilities/sys.py +0 -0
  209. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/utilities/tempfile.py +0 -0
  210. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/utilities/tenacity.py +0 -0
  211. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/utilities/threading.py +0 -0
  212. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/utilities/timer.py +0 -0
  213. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/utilities/traceback.py +0 -0
  214. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/utilities/types.py +0 -0
  215. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/utilities/typing.py +0 -0
  216. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/utilities/tzdata.py +0 -0
  217. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/utilities/tzlocal.py +0 -0
  218. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/utilities/uuid.py +0 -0
  219. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/utilities/version.py +0 -0
  220. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/utilities/warnings.py +0 -0
  221. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/utilities/whenever.py +0 -0
  222. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/utilities/zipfile.py +0 -0
  223. {dycw_utilities-0.108.1 → dycw_utilities-0.108.2}/src/utilities/zoneinfo.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dycw-utilities
3
- Version: 0.108.1
3
+ Version: 0.108.2
4
4
  Author-email: Derek Wan <d.wan@icloud.com>
5
5
  License-File: LICENSE
6
6
  Requires-Python: >=3.12
@@ -89,7 +89,7 @@ dependencies = [
89
89
  name = "dycw-utilities"
90
90
  readme = "README.md"
91
91
  requires-python = ">= 3.12"
92
- version = "0.108.1"
92
+ version = "0.108.2"
93
93
 
94
94
  [project.optional-dependencies]
95
95
  test = [
@@ -332,7 +332,7 @@ zzz-test-zoneinfo = [
332
332
  # bump-my-version
333
333
  [tool.bumpversion]
334
334
  allow_dirty = true
335
- current_version = "0.108.1"
335
+ current_version = "0.108.2"
336
336
 
337
337
  [[tool.bumpversion.files]]
338
338
  filename = "src/utilities/__init__.py"
@@ -0,0 +1,234 @@
1
+ from __future__ import annotations
2
+
3
+ import datetime as dt
4
+ from collections.abc import Iterable
5
+ from dataclasses import dataclass
6
+ from pathlib import Path
7
+ from types import NoneType
8
+ from typing import Literal
9
+
10
+ from hypothesis import given
11
+ from hypothesis.strategies import booleans, dates, floats, integers, sampled_from, times
12
+ from pytest import raises
13
+
14
+ from tests.test_operator import TruthEnum
15
+ from utilities.hypothesis import (
16
+ local_datetimes,
17
+ paths,
18
+ text_ascii,
19
+ timedeltas_2w,
20
+ versions,
21
+ zoned_datetimes,
22
+ )
23
+ from utilities.math import is_equal
24
+ from utilities.parse import ParseTextError, parse_text
25
+ from utilities.sentinel import Sentinel, sentinel
26
+ from utilities.version import Version
27
+ from utilities.whenever import (
28
+ serialize_date,
29
+ serialize_datetime,
30
+ serialize_time,
31
+ serialize_timedelta,
32
+ )
33
+
34
+
35
+ class TestParseText:
36
+ @given(value=booleans())
37
+ def test_bool(self, *, value: bool) -> None:
38
+ text = str(value)
39
+ result = parse_text(bool, text)
40
+ assert result is value
41
+
42
+ @given(date=dates())
43
+ def test_date(self, *, date: dt.date) -> None:
44
+ text = serialize_date(date)
45
+ result = parse_text(dt.date, text)
46
+ assert result == date
47
+
48
+ @given(datetime=local_datetimes() | zoned_datetimes())
49
+ def test_datetime(self, *, datetime: dt.datetime) -> None:
50
+ text = serialize_datetime(datetime)
51
+ result = parse_text(dt.datetime, text)
52
+ assert result == datetime
53
+
54
+ @given(truth=sampled_from(TruthEnum))
55
+ def test_enum(self, *, truth: TruthEnum) -> None:
56
+ text = truth.name
57
+ result = parse_text(TruthEnum, text)
58
+ assert result is truth
59
+
60
+ @given(value=floats())
61
+ def test_float(self, *, value: float) -> None:
62
+ text = str(value)
63
+ result = parse_text(float, text)
64
+ assert is_equal(result, value)
65
+
66
+ @given(value=integers())
67
+ def test_int(self, *, value: int) -> None:
68
+ text = str(value)
69
+ result = parse_text(int, text)
70
+ assert result == value
71
+
72
+ @given(truth=sampled_from(["true", "false"]))
73
+ def test_literal(self, *, truth: Literal["true", "false"]) -> None:
74
+ result = parse_text(Literal["true", "false"], truth)
75
+ assert result == truth
76
+
77
+ def test_nullable_int_none(self) -> None:
78
+ text = str(None)
79
+ result = parse_text(int | None, text)
80
+ assert result is None
81
+
82
+ @given(value=integers())
83
+ def test_nullable_int_int(self, *, value: int) -> None:
84
+ text = str(value)
85
+ result = parse_text(int | None, text)
86
+ assert result == value
87
+
88
+ def test_none(self) -> None:
89
+ text = str(None)
90
+ result = parse_text(None, text)
91
+ assert result is None
92
+
93
+ def test_none_type(self) -> None:
94
+ text = str(None)
95
+ result = parse_text(NoneType, text)
96
+ assert result is None
97
+
98
+ @given(path=paths())
99
+ def test_path(self, *, path: Path) -> None:
100
+ text = str(path)
101
+ result = parse_text(Path, text)
102
+ assert result == path
103
+
104
+ def test_sentinel(self) -> None:
105
+ text = str(sentinel)
106
+ result = parse_text(Sentinel, text)
107
+ assert result is sentinel
108
+
109
+ @given(text=text_ascii())
110
+ def test_str(self, *, text: str) -> None:
111
+ result = parse_text(str, text)
112
+ assert result == text
113
+
114
+ @given(time=times())
115
+ def test_time(self, *, time: dt.time) -> None:
116
+ text = serialize_time(time)
117
+ result = parse_text(dt.time, text)
118
+ assert result == time
119
+
120
+ @given(timedelta=timedeltas_2w())
121
+ def test_timedelta(self, *, timedelta: dt.timedelta) -> None:
122
+ text = serialize_timedelta(timedelta)
123
+ result = parse_text(dt.timedelta, text)
124
+ assert result == timedelta
125
+
126
+ @given(version=versions())
127
+ def test_version(self, *, version: Version) -> None:
128
+ text = str(version)
129
+ result = parse_text(Version, text)
130
+ assert result == version
131
+
132
+ def test_error_bool(self) -> None:
133
+ with raises(
134
+ ParseTextError, match="Unable to parse <class 'bool'>; got 'invalid'"
135
+ ):
136
+ _ = parse_text(bool, "invalid")
137
+
138
+ def test_error_date(self) -> None:
139
+ with raises(
140
+ ParseTextError,
141
+ match=r"Unable to parse <class 'datetime\.date'>; got 'invalid'",
142
+ ):
143
+ _ = parse_text(dt.date, "invalid")
144
+
145
+ def test_error_datetime(self) -> None:
146
+ with raises(
147
+ ParseTextError,
148
+ match=r"Unable to parse <class 'datetime\.datetime'>; got 'invalid'",
149
+ ):
150
+ _ = parse_text(dt.datetime, "invalid")
151
+
152
+ def test_error_enum(self) -> None:
153
+ with raises(
154
+ ParseTextError, match="Unable to parse <enum 'TruthEnum'>; got 'invalid'"
155
+ ):
156
+ _ = parse_text(TruthEnum, "invalid")
157
+
158
+ def test_error_float(self) -> None:
159
+ with raises(
160
+ ParseTextError, match="Unable to parse <class 'float'>; got 'invalid'"
161
+ ):
162
+ _ = parse_text(float, "invalid")
163
+
164
+ def test_error_int(self) -> None:
165
+ with raises(
166
+ ParseTextError, match="Unable to parse <class 'int'>; got 'invalid'"
167
+ ):
168
+ _ = parse_text(int, "invalid")
169
+
170
+ def test_error_none(self) -> None:
171
+ with raises(ParseTextError, match="Unable to parse None; got 'invalid'"):
172
+ _ = parse_text(None, "invalid")
173
+
174
+ def test_error_none_type(self) -> None:
175
+ with raises(
176
+ ParseTextError, match="Unable to parse <class 'NoneType'>; got 'invalid'"
177
+ ):
178
+ _ = parse_text(NoneType, "invalid")
179
+
180
+ def test_error_nullable_int(self) -> None:
181
+ with raises(
182
+ ParseTextError, match=r"Unable to parse int \| None; got 'invalid'"
183
+ ):
184
+ _ = parse_text(int | None, "invalid")
185
+
186
+ def test_error_nullable_not_type(self) -> None:
187
+ with raises(
188
+ ParseTextError,
189
+ match=r"Unable to parse collections\.abc\.Iterable\[None\] \| None; got 'invalid'",
190
+ ):
191
+ _ = parse_text(Iterable[None] | None, "invalid")
192
+
193
+ def test_error_sentinel(self) -> None:
194
+ with raises(
195
+ ParseTextError,
196
+ match=r"Unable to parse <class 'utilities\.sentinel\.Sentinel'>; got 'invalid'",
197
+ ):
198
+ _ = parse_text(Sentinel, "invalid")
199
+
200
+ def test_error_time(self) -> None:
201
+ with raises(
202
+ ParseTextError,
203
+ match=r"Unable to parse <class 'datetime\.time'>; got 'invalid'",
204
+ ):
205
+ _ = parse_text(dt.time, "invalid")
206
+
207
+ def test_error_timedelta(self) -> None:
208
+ with raises(
209
+ ParseTextError,
210
+ match=r"Unable to parse <class 'datetime\.timedelta'>; got 'invalid'",
211
+ ):
212
+ _ = parse_text(dt.timedelta, "invalid")
213
+
214
+ def test_error_unknown_annotation(self) -> None:
215
+ with raises(ParseTextError, match=r"Unable to parse int \| str; got 'invalid'"):
216
+ _ = parse_text(int | str, "invalid")
217
+
218
+ def test_error_unknown_type(self) -> None:
219
+ @dataclass(kw_only=True)
220
+ class Example:
221
+ pass
222
+
223
+ with raises(
224
+ ParseTextError,
225
+ match=r"Unable to parse <class 'tests\.test_parse\.TestParseText\.test_error_unknown_type\.<locals>\.Example'>; got 'invalid'",
226
+ ):
227
+ _ = parse_text(Example, "invalid")
228
+
229
+ def test_error_version(self) -> None:
230
+ with raises(
231
+ ParseTextError,
232
+ match=r"Unable to parse <class 'utilities\.version\.Version'>; got 'invalid'",
233
+ ):
234
+ _ = parse_text(Version, "invalid")
@@ -0,0 +1,45 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ from hypothesis import given
6
+ from hypothesis.strategies import DataObject, data, sampled_from
7
+ from pytest import mark, param, raises
8
+
9
+ from utilities.sentinel import (
10
+ SENTINEL_REPR,
11
+ ParseSentinelError,
12
+ Sentinel,
13
+ parse_sentinel,
14
+ sentinel,
15
+ )
16
+
17
+ if TYPE_CHECKING:
18
+ from collections.abc import Callable
19
+
20
+
21
+ class TestParseSentinel:
22
+ @given(data=data())
23
+ def test_main(self, *, data: DataObject) -> None:
24
+ text = str(sentinel)
25
+ text_use = data.draw(sampled_from(["", text, text.lower(), text.upper()]))
26
+ result = parse_sentinel(text_use)
27
+ assert result is sentinel
28
+
29
+ def test_error(self) -> None:
30
+ with raises(
31
+ ParseSentinelError, match="Unable to parse sentinel value; got 'invalid'"
32
+ ):
33
+ _ = parse_sentinel("invalid")
34
+
35
+
36
+ class TestSentinel:
37
+ def test_isinstance(self) -> None:
38
+ assert isinstance(sentinel, Sentinel)
39
+
40
+ @mark.parametrize("method", [param(repr), param(str)])
41
+ def test_repr_and_str(self, method: Callable[..., str]) -> None:
42
+ assert method(sentinel) == SENTINEL_REPR
43
+
44
+ def test_singletone(self) -> None:
45
+ assert Sentinel() is sentinel
@@ -16,8 +16,10 @@ from utilities.hypothesis import text_ascii
16
16
  from utilities.sentinel import sentinel
17
17
  from utilities.text import (
18
18
  ParseBoolError,
19
+ ParseNoneError,
19
20
  join_strs,
20
21
  parse_bool,
22
+ parse_none,
21
23
  repr_encode,
22
24
  snake_case,
23
25
  split_str,
@@ -29,18 +31,33 @@ from utilities.text import (
29
31
  class TestParseBool:
30
32
  @given(data=data(), value=booleans())
31
33
  def test_main(self, *, data: DataObject, value: bool) -> None:
32
- text = data.draw(sampled_from([str(value), str(int(value))]))
33
- text = data.draw(sampled_from([text, text.lower(), text.upper()]))
34
- result = parse_bool(text)
34
+ text = str(value)
35
+ text_use = data.draw(
36
+ sampled_from([str(int(value)), text, text.lower(), text.upper()])
37
+ )
38
+ result = parse_bool(text_use)
35
39
  assert result is value
36
40
 
37
41
  def test_error(self) -> None:
38
42
  with raises(
39
- ParseBoolError, match="Unable to parse 'invalid' into a boolean value"
43
+ ParseBoolError, match="Unable to parse boolean value; got 'invalid'"
40
44
  ):
41
45
  _ = parse_bool("invalid")
42
46
 
43
47
 
48
+ class TestParseNone:
49
+ @given(data=data())
50
+ def test_main(self, *, data: DataObject) -> None:
51
+ text = str(None)
52
+ text_use = data.draw(sampled_from(["", text, text.lower(), text.upper()]))
53
+ result = parse_none(text_use)
54
+ assert result is None
55
+
56
+ def test_error(self) -> None:
57
+ with raises(ParseNoneError, match="Unable to parse null value; got 'invalid'"):
58
+ _ = parse_none("invalid")
59
+
60
+
44
61
  class TestReprEncode:
45
62
  @given(n=integers())
46
63
  def test_main(self, *, n: int) -> None:
@@ -1,3 +1,3 @@
1
1
  from __future__ import annotations
2
2
 
3
- __version__ = "0.108.1"
3
+ __version__ = "0.108.2"
@@ -0,0 +1,129 @@
1
+ from __future__ import annotations
2
+
3
+ import datetime as dt
4
+ from contextlib import suppress
5
+ from dataclasses import dataclass
6
+ from enum import Enum
7
+ from pathlib import Path
8
+ from types import NoneType
9
+ from typing import Any, get_args, override
10
+
11
+ from utilities.datetime import is_subclass_date_not_datetime
12
+ from utilities.enum import ParseEnumError, parse_enum
13
+ from utilities.functions import is_subclass_int_not_bool
14
+ from utilities.iterables import one, one_str
15
+ from utilities.sentinel import ParseSentinelError, Sentinel, parse_sentinel
16
+ from utilities.text import ParseBoolError, ParseNoneError, parse_bool, parse_none
17
+ from utilities.typing import is_literal_type, is_optional_type
18
+ from utilities.version import ParseVersionError, Version, parse_version
19
+
20
+
21
+ def parse_text(
22
+ obj: Any, text: str, /, *, case_sensitive: bool = False, head: bool = False
23
+ ) -> Any:
24
+ """Parse text."""
25
+ if obj is None:
26
+ try:
27
+ return parse_none(text)
28
+ except ParseNoneError:
29
+ raise ParseTextError(obj=obj, text=text) from None
30
+ if isinstance(obj, type):
31
+ return _parse_text_type(obj, text, case_sensitive=case_sensitive)
32
+ if is_literal_type(obj):
33
+ return one_str(get_args(obj), text, head=head, case_sensitive=case_sensitive)
34
+ if is_optional_type(obj):
35
+ with suppress(ParseNoneError):
36
+ return parse_none(text)
37
+ inner = one(arg for arg in get_args(obj) if arg is not NoneType)
38
+ if isinstance(
39
+ inner := one(arg for arg in get_args(obj) if arg is not NoneType), type
40
+ ):
41
+ try:
42
+ return _parse_text_type(inner, text, case_sensitive=case_sensitive)
43
+ except ParseTextError:
44
+ raise ParseTextError(obj=obj, text=text) from None
45
+ raise ParseTextError(obj=obj, text=text) from None
46
+
47
+
48
+ def _parse_text_type(
49
+ cls: type[Any], text: str, /, *, case_sensitive: bool = False
50
+ ) -> Any:
51
+ """Parse text."""
52
+ if issubclass(cls, NoneType):
53
+ try:
54
+ return parse_none(text)
55
+ except ParseNoneError:
56
+ raise ParseTextError(obj=cls, text=text) from None
57
+ if issubclass(cls, str):
58
+ return text
59
+ if issubclass(cls, bool):
60
+ try:
61
+ return parse_bool(text)
62
+ except ParseBoolError:
63
+ raise ParseTextError(obj=cls, text=text) from None
64
+ if is_subclass_int_not_bool(cls):
65
+ try:
66
+ return int(text)
67
+ except ValueError:
68
+ raise ParseTextError(obj=cls, text=text) from None
69
+ if issubclass(cls, float):
70
+ try:
71
+ return float(text)
72
+ except ValueError:
73
+ raise ParseTextError(obj=cls, text=text) from None
74
+ if issubclass(cls, Enum):
75
+ try:
76
+ return parse_enum(text, cls, case_sensitive=case_sensitive)
77
+ except ParseEnumError:
78
+ raise ParseTextError(obj=cls, text=text) from None
79
+ if issubclass(cls, Path):
80
+ return Path(text).expanduser()
81
+ if issubclass(cls, Sentinel):
82
+ try:
83
+ return parse_sentinel(text)
84
+ except ParseSentinelError:
85
+ raise ParseTextError(obj=cls, text=text) from None
86
+ if issubclass(cls, Version):
87
+ try:
88
+ return parse_version(text)
89
+ except ParseVersionError:
90
+ raise ParseTextError(obj=cls, text=text) from None
91
+ if is_subclass_date_not_datetime(cls):
92
+ from utilities.whenever import ParseDateError, parse_date
93
+
94
+ try:
95
+ return parse_date(text)
96
+ except ParseDateError:
97
+ raise ParseTextError(obj=cls, text=text) from None
98
+ if issubclass(cls, dt.datetime):
99
+ from utilities.whenever import ParseDateTimeError, parse_datetime
100
+
101
+ try:
102
+ return parse_datetime(text)
103
+ except ParseDateTimeError:
104
+ raise ParseTextError(obj=cls, text=text) from None
105
+ if issubclass(cls, dt.time):
106
+ from utilities.whenever import ParseTimeError, parse_time
107
+
108
+ try:
109
+ return parse_time(text)
110
+ except ParseTimeError:
111
+ raise ParseTextError(obj=cls, text=text) from None
112
+ if issubclass(cls, dt.timedelta):
113
+ from utilities.whenever import ParseTimedeltaError, parse_timedelta
114
+
115
+ try:
116
+ return parse_timedelta(text)
117
+ except ParseTimedeltaError:
118
+ raise ParseTextError(obj=cls, text=text) from None
119
+ raise ParseTextError(obj=cls, text=text) from None
120
+
121
+
122
+ @dataclass
123
+ class ParseTextError(Exception):
124
+ obj: Any
125
+ text: str
126
+
127
+ @override
128
+ def __str__(self) -> str:
129
+ return f"Unable to parse {self.obj!r}; got {self.text!r}"
@@ -1,5 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
+ from dataclasses import dataclass
4
+ from re import IGNORECASE, search
3
5
  from typing import Any, override
4
6
 
5
7
 
@@ -33,4 +35,29 @@ class Sentinel(metaclass=_Meta):
33
35
  sentinel = Sentinel()
34
36
 
35
37
 
36
- __all__ = ["SENTINEL_REPR", "Sentinel", "sentinel"]
38
+ ##
39
+
40
+
41
+ def parse_sentinel(text: str, /) -> Sentinel:
42
+ """Parse text into the Sentinel value."""
43
+ if text == "" or search("Sentinel", text, flags=IGNORECASE):
44
+ return sentinel
45
+ raise ParseSentinelError(text=text)
46
+
47
+
48
+ @dataclass(kw_only=True, slots=True)
49
+ class ParseSentinelError(Exception):
50
+ text: str
51
+
52
+ @override
53
+ def __str__(self) -> str:
54
+ return f"Unable to parse sentinel value; got {self.text!r}"
55
+
56
+
57
+ __all__ = [
58
+ "SENTINEL_REPR",
59
+ "ParseSentinelError",
60
+ "Sentinel",
61
+ "parse_sentinel",
62
+ "sentinel",
63
+ ]
@@ -45,7 +45,26 @@ class ParseBoolError(Exception):
45
45
 
46
46
  @override
47
47
  def __str__(self) -> str:
48
- return f"Unable to parse {self.text!r} into a boolean value"
48
+ return f"Unable to parse boolean value; got {self.text!r}"
49
+
50
+
51
+ ##
52
+
53
+
54
+ def parse_none(text: str, /) -> None:
55
+ """Parse text into the None value."""
56
+ if text == "" or search("None", text, flags=IGNORECASE):
57
+ return
58
+ raise ParseNoneError(text=text)
59
+
60
+
61
+ @dataclass(kw_only=True, slots=True)
62
+ class ParseNoneError(Exception):
63
+ text: str
64
+
65
+ @override
66
+ def __str__(self) -> str:
67
+ return f"Unable to parse null value; got {self.text!r}"
49
68
 
50
69
 
51
70
  ##
@@ -108,8 +127,10 @@ def strip_and_dedent(text: str, /, *, trailing: bool = False) -> str:
108
127
 
109
128
  __all__ = [
110
129
  "ParseBoolError",
130
+ "ParseNoneError",
111
131
  "join_strs",
112
132
  "parse_bool",
133
+ "parse_none",
113
134
  "repr_encode",
114
135
  "snake_case",
115
136
  "split_str",
@@ -1,22 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from typing import TYPE_CHECKING
4
-
5
- from pytest import mark, param
6
-
7
- from utilities.sentinel import SENTINEL_REPR, Sentinel, sentinel
8
-
9
- if TYPE_CHECKING:
10
- from collections.abc import Callable
11
-
12
-
13
- class TestSentinel:
14
- def test_isinstance(self) -> None:
15
- assert isinstance(sentinel, Sentinel)
16
-
17
- @mark.parametrize("method", [param(repr), param(str)])
18
- def test_repr_and_str(self, method: Callable[..., str]) -> None:
19
- assert method(sentinel) == SENTINEL_REPR
20
-
21
- def test_singletone(self) -> None:
22
- assert Sentinel() is sentinel